This commit is contained in:
2026-08-08 00:52:52 +08:00
3 changed files with 121 additions and 39 deletions

114
src/utils/rosMapYaml.ts Normal file
View File

@@ -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,
};
}

View File

@@ -7,6 +7,7 @@ import {
updateRosCarMapUrls, updateRosCarMapUrls,
uploadFile, uploadFile,
} from "@/api/rosCar"; } from "@/api/rosCar";
import { parseRosMapYaml } from "@/utils/rosMapYaml";
type DeviceStatus = "空闲" | "巡检" | "离线" | "充电中"; type DeviceStatus = "空闲" | "巡检" | "离线" | "充电中";
type PatrolPointType = "业务点" | "途经点"; type PatrolPointType = "业务点" | "途经点";
@@ -436,27 +437,6 @@ function loadRosMapPreview(previewUrl: string, imageName: string) {
scheduleMapCanvasRender(); 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) { function readFileAsArrayBuffer(file: File) {
return new Promise<ArrayBuffer>((resolve, reject) => { return new Promise<ArrayBuffer>((resolve, reject) => {
const reader = new FileReader(); const reader = new FileReader();

View File

@@ -14,6 +14,7 @@ import {
stopDispatchRosInspectionTask, stopDispatchRosInspectionTask,
} from "@/api/rosCar"; } from "@/api/rosCar";
import { getToken } from "@/utils/auth"; import { getToken } from "@/utils/auth";
import { parseRosMapYaml } from "@/utils/rosMapYaml";
import { BASE_URL } from "@/utils/request"; import { BASE_URL } from "@/utils/request";
import { getRecentAlertPhoto } from "../shared/alertPhotos"; import { getRecentAlertPhoto } from "../shared/alertPhotos";
@@ -376,23 +377,6 @@ function buildFileAccessUrl(fileUrl: string) {
return `${BASE_URL}${fileUrl.startsWith("/") ? fileUrl : `/${fileUrl}`}`; 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 }) { function readPgmToken(bytes: Uint8Array, cursor: { index: number }) {
while (cursor.index < bytes.length) { while (cursor.index < bytes.length) {
const code = bytes[cursor.index]; const code = bytes[cursor.index];
@@ -737,7 +721,11 @@ async function loadRobotMap(device: DeviceItem) {
} }
const styledMap = buildStyledPgmMap(await pgmResponse.arrayBuffer()); 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 = { rosMap.value = {
...meta, ...meta,