|
@@ -1,118 +1,73 @@
|
|
|
/**
|
|
/**
|
|
|
- * 多条选段 由点id组合 确定多边形
|
|
|
|
|
- * @param segments
|
|
|
|
|
- * @returns
|
|
|
|
|
|
|
+ * 高效提取连通段(包括闭合多边形和开放折线)
|
|
|
*/
|
|
*/
|
|
|
export function extractConnectedSegments(
|
|
export function extractConnectedSegments(
|
|
|
segments: { a: number; b: number }[]
|
|
segments: { a: number; b: number }[]
|
|
|
): number[][] {
|
|
): number[][] {
|
|
|
- // 1. 构建无向图邻接表
|
|
|
|
|
- const graph: Record<number, number[]> = {};
|
|
|
|
|
|
|
+ if (segments.length === 0) return [];
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 构建邻接表
|
|
|
|
|
+ const adj: Map<number, Set<number>> = new Map();
|
|
|
for (const { a, b } of segments) {
|
|
for (const { a, b } of segments) {
|
|
|
- if (!graph[a]) graph[a] = [];
|
|
|
|
|
- if (!graph[b]) graph[b] = [];
|
|
|
|
|
- graph[a].push(b);
|
|
|
|
|
- graph[b].push(a);
|
|
|
|
|
|
|
+ if (a === b) continue; // 忽略自环
|
|
|
|
|
+ if (!adj.has(a)) adj.set(a, new Set());
|
|
|
|
|
+ if (!adj.has(b)) adj.set(b, new Set());
|
|
|
|
|
+ adj.get(a)!.add(b);
|
|
|
|
|
+ adj.get(b)!.add(a);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 2. 边访问记录
|
|
|
|
|
- const visitedEdges = new Set<string>();
|
|
|
|
|
- const edgeKey = (a: number, b: number) => (a < b ? `${a},${b}` : `${b},${a}`);
|
|
|
|
|
-
|
|
|
|
|
- // 3. 结果收集
|
|
|
|
|
const result: number[][] = [];
|
|
const result: number[][] = [];
|
|
|
- const polygonSet = new Set<string>();
|
|
|
|
|
|
|
|
|
|
- // 4. 多边形标准化函数(按点集合的排序字符串标识)
|
|
|
|
|
- const normalizePolygon = (polygon: number[]) => {
|
|
|
|
|
- const points = [...new Set(polygon.slice(0, -1))].sort((a, b) => a - b);
|
|
|
|
|
- return JSON.stringify(points);
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // 5. 查找闭合环的迭代DFS实现
|
|
|
|
|
- function findPolygons(start: number) {
|
|
|
|
|
- const stack: {
|
|
|
|
|
- current: number;
|
|
|
|
|
- path: number[];
|
|
|
|
|
- visited: Set<string>;
|
|
|
|
|
- }[] = [{ current: start, path: [start], visited: new Set() }];
|
|
|
|
|
-
|
|
|
|
|
- while (stack.length > 0) {
|
|
|
|
|
- const { current, path, visited } = stack.pop()!;
|
|
|
|
|
-
|
|
|
|
|
- for (const neighbor of graph[current]) {
|
|
|
|
|
- const edge = edgeKey(current, neighbor);
|
|
|
|
|
-
|
|
|
|
|
- // 跳过已访问的边
|
|
|
|
|
- if (visited.has(edge)) continue;
|
|
|
|
|
-
|
|
|
|
|
- // 发现闭合环
|
|
|
|
|
- if (neighbor === start && path.length > 2) {
|
|
|
|
|
- const closedPath = [...path, start];
|
|
|
|
|
- const polyKey = normalizePolygon(closedPath);
|
|
|
|
|
-
|
|
|
|
|
- if (!polygonSet.has(polyKey)) {
|
|
|
|
|
- polygonSet.add(polyKey);
|
|
|
|
|
- result.push(closedPath);
|
|
|
|
|
- }
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 避免重复访问点(起点除外)
|
|
|
|
|
- if (neighbor !== start && path.includes(neighbor)) continue;
|
|
|
|
|
-
|
|
|
|
|
- const newVisited = new Set(visited);
|
|
|
|
|
- newVisited.add(edge);
|
|
|
|
|
- stack.push({
|
|
|
|
|
- current: neighbor,
|
|
|
|
|
- path: [...path, neighbor],
|
|
|
|
|
- visited: newVisited,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ // 2. 首先提取所有“开放路径” (从度数为 1 的点开始)
|
|
|
|
|
+ // 这样做可以消除干扰,剩下的一定是纯粹的环结构
|
|
|
|
|
+ for (const [node, neighbors] of adj.entries()) {
|
|
|
|
|
+ if (neighbors.size === 1) {
|
|
|
|
|
+ const path: number[] = [node];
|
|
|
|
|
+ let curr = node;
|
|
|
|
|
+
|
|
|
|
|
+ while (adj.has(curr) && adj.get(curr)!.size > 0) {
|
|
|
|
|
+ const next = adj.get(curr)!.values().next().value!;
|
|
|
|
|
+ path.push(next);
|
|
|
|
|
+
|
|
|
|
|
+ // 删除已经处理的边
|
|
|
|
|
+ adj.get(curr)!.delete(next);
|
|
|
|
|
+ adj.get(next)!.delete(curr);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果 next 点没边了,从图中移除
|
|
|
|
|
+ if (adj.get(curr)!.size === 0) adj.delete(curr);
|
|
|
|
|
+ curr = next;
|
|
|
|
|
+
|
|
|
|
|
+ // 如果遇到了分叉点(度数大于1)或终点,停止
|
|
|
|
|
+ if (!adj.has(curr) || adj.get(curr)!.size !== 1) break;
|
|
|
}
|
|
}
|
|
|
|
|
+ if (path.length > 1) result.push(path);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 6. 查找所有闭合环
|
|
|
|
|
- for (const node in graph) {
|
|
|
|
|
- const nodeId = Number(node);
|
|
|
|
|
- if (graph[nodeId].length >= 2) {
|
|
|
|
|
- // 至少需要两个连接才能形成环
|
|
|
|
|
- findPolygons(nodeId);
|
|
|
|
|
|
|
+ // 3. 处理剩下的部分(此时图中只剩下闭合环)
|
|
|
|
|
+ // 采用简单的深度优先遍历提取环
|
|
|
|
|
+ const nodes = Array.from(adj.keys());
|
|
|
|
|
+ for (const startNode of nodes) {
|
|
|
|
|
+ if (!adj.has(startNode)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ const path: number[] = [startNode];
|
|
|
|
|
+ let curr = startNode;
|
|
|
|
|
+
|
|
|
|
|
+ while (adj.has(curr) && adj.get(curr)!.size > 0) {
|
|
|
|
|
+ const next = adj.get(curr)!.values().next().value!;
|
|
|
|
|
+ path.push(next);
|
|
|
|
|
+
|
|
|
|
|
+ adj.get(curr)!.delete(next);
|
|
|
|
|
+ adj.get(next)!.delete(curr);
|
|
|
|
|
+
|
|
|
|
|
+ if (adj.get(curr)!.size === 0) adj.delete(curr);
|
|
|
|
|
+
|
|
|
|
|
+ curr = next;
|
|
|
|
|
+ if (curr === startNode) break; // 环闭合
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 7. 查找开放路径
|
|
|
|
|
- const visitedNodes = new Set<number>();
|
|
|
|
|
- for (const node in graph) {
|
|
|
|
|
- const nodeId = Number(node);
|
|
|
|
|
- if (visitedNodes.has(nodeId)) continue;
|
|
|
|
|
-
|
|
|
|
|
- // 开放路径必须从端点开始(度数为1的点)
|
|
|
|
|
- if (graph[nodeId].length === 1) {
|
|
|
|
|
- const path: number[] = [];
|
|
|
|
|
- let current: number | null = nodeId;
|
|
|
|
|
- let prev: number | null = null;
|
|
|
|
|
-
|
|
|
|
|
- while (current !== null) {
|
|
|
|
|
- visitedNodes.add(current);
|
|
|
|
|
- path.push(current);
|
|
|
|
|
-
|
|
|
|
|
- // 找到下一个未访问的相邻点
|
|
|
|
|
- let next: number | null = null;
|
|
|
|
|
- for (const neighbor of graph[current]) {
|
|
|
|
|
- if (neighbor !== prev && !visitedNodes.has(neighbor)) {
|
|
|
|
|
- next = neighbor;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- prev = current;
|
|
|
|
|
- current = next;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (path.length >= 2) {
|
|
|
|
|
- result.push(path);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (path.length > 1) {
|
|
|
|
|
+ result.push(path);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -120,7 +75,6 @@ export function extractConnectedSegments(
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
/**
|
|
/**
|
|
|
* 从无向线段中提取所有闭合环(多边形)和开放路径
|
|
* 从无向线段中提取所有闭合环(多边形)和开放路径
|
|
|
* @param segments 线段数组,每条线段由两个点组成(无方向性)
|
|
* @param segments 线段数组,每条线段由两个点组成(无方向性)
|