diff --git a/src/utils/rosMapYaml.ts b/src/utils/rosMapYaml.ts new file mode 100644 index 0000000..57bcab3 --- /dev/null +++ b/src/utils/rosMapYaml.ts @@ -0,0 +1,114 @@ +export interface ParsedRosMapYaml { + image?: string; + resolution: number | null; + origin: [number, number, number] | null; +} + +function stripInlineComment(value: string) { + let quote: string | null = null; + + for (let index = 0; index < value.length; index += 1) { + const char = value[index]; + const previousChar = value[index - 1]; + + if ((char === "'" || char === "\"") && previousChar !== "\\") { + quote = quote === char ? null : quote || char; + } + + if (char === "#" && !quote) { + return value.slice(0, index).trim(); + } + } + + return value.trim(); +} + +function unquoteYamlString(value: string) { + const trimmedValue = stripInlineComment(value); + + return trimmedValue.replace(/^['"]|['"]$/g, ""); +} + +function readScalarValue(content: string, key: string) { + const match = content.match(new RegExp(`^\\s*${key}\\s*:\\s*(.*?)\\s*$`, "m")); + + return match ? stripInlineComment(match[1]) : ""; +} + +function parseFiniteNumber(value: string) { + const parsedValue = Number(stripInlineComment(value)); + + return Number.isFinite(parsedValue) ? parsedValue : null; +} + +function parseInlineOrigin(content: string) { + const originValue = content.match(/^\s*origin\s*:\s*\[([^\]]+)\]\s*(?:#.*)?$/m)?.[1]; + + if (!originValue) { + return null; + } + + const values = originValue + .split(",") + .map((value) => parseFiniteNumber(value.trim())) + .slice(0, 3); + + return values.length === 3 && values.every((value) => value !== null) + ? values as [number, number, number] + : null; +} + +function parseBlockOrigin(content: string) { + const lines = content.split(/\r?\n/); + const values: number[] = []; + + for (let index = 0; index < lines.length; index += 1) { + const originLine = lines[index].match(/^(\s*)origin\s*:\s*(?:#.*)?$/); + + if (!originLine) { + continue; + } + + const originIndent = originLine[1].length; + + for (let nextIndex = index + 1; nextIndex < lines.length; nextIndex += 1) { + const line = lines[nextIndex]; + + if (!line.trim() || /^\s*#/.test(line)) { + continue; + } + + const lineIndent = line.match(/^\s*/)?.[0].length ?? 0; + if (lineIndent <= originIndent) { + break; + } + + const itemValue = line.match(/^\s*-\s*(.*?)\s*$/)?.[1]; + const parsedValue = itemValue ? parseFiniteNumber(itemValue) : null; + + if (parsedValue === null) { + break; + } + + values.push(parsedValue); + + if (values.length === 3) { + return values as [number, number, number]; + } + } + } + + return null; +} + +export function parseRosMapYaml(content: string): ParsedRosMapYaml { + const imageValue = readScalarValue(content, "image"); + const resolution = parseFiniteNumber(readScalarValue(content, "resolution")); + const origin = parseInlineOrigin(content) || parseBlockOrigin(content); + + return { + image: imageValue ? unquoteYamlString(imageValue) : undefined, + resolution: resolution && resolution > 0 ? resolution : null, + origin, + }; +} diff --git a/src/views/dashboard1/pages/EquipmentManagementPage.vue b/src/views/dashboard1/pages/EquipmentManagementPage.vue index 2e77755..bfffd7f 100644 --- a/src/views/dashboard1/pages/EquipmentManagementPage.vue +++ b/src/views/dashboard1/pages/EquipmentManagementPage.vue @@ -7,6 +7,7 @@ import { updateRosCarMapUrls, uploadFile, } from "@/api/rosCar"; +import { parseRosMapYaml } from "@/utils/rosMapYaml"; type DeviceStatus = "空闲" | "巡检" | "离线" | "充电中"; type PatrolPointType = "业务点" | "途经点"; @@ -436,27 +437,6 @@ function loadRosMapPreview(previewUrl: string, imageName: string) { scheduleMapCanvasRender(); } -// 解析 ROS YAML 配置中的 image、resolution 和 origin 字段 -function parseRosMapYaml(content: string) { - const image = content.match(/^\s*image\s*:\s*(.+?)\s*$/m)?.[1]?.replace(/^['"]|['"]$/g, ""); - const resolution = Number(content.match(/^\s*resolution\s*:\s*([^\s#]+)\s*$/m)?.[1]); - const originValue = content.match(/^\s*origin\s*:\s*\[([^\]]+)\]\s*$/m)?.[1]; - const origin = originValue - ?.split(",") - .map((value) => Number(value.trim())) - .slice(0, 3); - - return { - image, - resolution: Number.isFinite(resolution) && resolution > 0 - ? resolution - : null, - origin: origin?.length === 3 && origin.every((value) => Number.isFinite(value)) - ? origin as [number, number, number] - : null, - }; -} - function readFileAsArrayBuffer(file: File) { return new Promise((resolve, reject) => { const reader = new FileReader(); diff --git a/src/views/dashboard1/pages/IntegratedCenterPage.vue b/src/views/dashboard1/pages/IntegratedCenterPage.vue index 21173bd..2ed525a 100644 --- a/src/views/dashboard1/pages/IntegratedCenterPage.vue +++ b/src/views/dashboard1/pages/IntegratedCenterPage.vue @@ -14,6 +14,7 @@ import { stopDispatchRosInspectionTask, } from "@/api/rosCar"; import { getToken } from "@/utils/auth"; +import { parseRosMapYaml } from "@/utils/rosMapYaml"; import { BASE_URL } from "@/utils/request"; import { getRecentAlertPhoto } from "../shared/alertPhotos"; @@ -376,23 +377,6 @@ function buildFileAccessUrl(fileUrl: string) { return `${BASE_URL}${fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`}`; } -// 解析 ROS 地图 YAML 中的分辨率和原点,供真实坐标映射使用 -function parseRosMapYaml(content: string): RosMapMeta { - const resolution = Number(content.match(/^\s*resolution\s*:\s*([^\s#]+)\s*$/m)?.[1]); - const originValue = content.match(/^\s*origin\s*:\s*\[([^\]]+)\]\s*$/m)?.[1]; - const origin = originValue - ?.split(",") - .map((value) => Number(value.trim())) - .slice(0, 3); - - return { - resolution: Number.isFinite(resolution) && resolution > 0 ? resolution : 0.05, - origin: origin?.length === 3 && origin.every((value) => Number.isFinite(value)) - ? origin as [number, number, number] - : [0, 0, 0], - }; -} - function readPgmToken(bytes: Uint8Array, cursor: { index: number }) { while (cursor.index < bytes.length) { const code = bytes[cursor.index]; @@ -737,7 +721,11 @@ async function loadRobotMap(device: DeviceItem) { } const styledMap = buildStyledPgmMap(await pgmResponse.arrayBuffer()); - const meta = parseRosMapYaml(await yamlResponse.text()); + const parsedMeta = parseRosMapYaml(await yamlResponse.text()); + const meta: RosMapMeta = { + resolution: parsedMeta.resolution ?? 0.05, + origin: parsedMeta.origin ?? [0, 0, 0], + }; rosMap.value = { ...meta,