polygon.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. /**
  2. * 高效提取连通段(包括闭合多边形和开放折线)
  3. */
  4. export function extractConnectedSegments(
  5. segments: { a: number; b: number }[]
  6. ): number[][] {
  7. if (segments.length === 0) return [];
  8. // 1. 构建邻接表
  9. const adj: Map<number, Set<number>> = new Map();
  10. for (const { a, b } of segments) {
  11. if (a === b) continue; // 忽略自环
  12. if (!adj.has(a)) adj.set(a, new Set());
  13. if (!adj.has(b)) adj.set(b, new Set());
  14. adj.get(a)!.add(b);
  15. adj.get(b)!.add(a);
  16. }
  17. const result: number[][] = [];
  18. // 2. 首先提取所有“开放路径” (从度数为 1 的点开始)
  19. // 这样做可以消除干扰,剩下的一定是纯粹的环结构
  20. for (const [node, neighbors] of adj.entries()) {
  21. if (neighbors.size === 1) {
  22. const path: number[] = [node];
  23. let curr = node;
  24. while (adj.has(curr) && adj.get(curr)!.size > 0) {
  25. const next = adj.get(curr)!.values().next().value!;
  26. path.push(next);
  27. // 删除已经处理的边
  28. adj.get(curr)!.delete(next);
  29. adj.get(next)!.delete(curr);
  30. // 如果 next 点没边了,从图中移除
  31. if (adj.get(curr)!.size === 0) adj.delete(curr);
  32. curr = next;
  33. // 如果遇到了分叉点(度数大于1)或终点,停止
  34. if (!adj.has(curr) || adj.get(curr)!.size !== 1) break;
  35. }
  36. if (path.length > 1) result.push(path);
  37. }
  38. }
  39. // 3. 处理剩下的部分(此时图中只剩下闭合环)
  40. // 采用简单的深度优先遍历提取环
  41. const nodes = Array.from(adj.keys());
  42. for (const startNode of nodes) {
  43. if (!adj.has(startNode)) continue;
  44. const path: number[] = [startNode];
  45. let curr = startNode;
  46. while (adj.has(curr) && adj.get(curr)!.size > 0) {
  47. const next = adj.get(curr)!.values().next().value!;
  48. path.push(next);
  49. adj.get(curr)!.delete(next);
  50. adj.get(next)!.delete(curr);
  51. if (adj.get(curr)!.size === 0) adj.delete(curr);
  52. curr = next;
  53. if (curr === startNode) break; // 环闭合
  54. }
  55. if (path.length > 1) {
  56. result.push(path);
  57. }
  58. }
  59. return result;
  60. }
  61. /**
  62. * 从无向线段中提取所有闭合环(多边形)和开放路径
  63. * @param segments 线段数组,每条线段由两个点组成(无方向性)
  64. * @returns 返回所有闭合环和开放路径,每个路径用点数组表示
  65. */
  66. export function extractConnectedNoDirectionSegments(
  67. segments: { a: number; b: number }[]
  68. ): number[][] {
  69. // 1. 构建无向图邻接表
  70. const graph: Record<number, number[]> = {};
  71. for (const { a, b } of segments) {
  72. if (!graph[a]) graph[a] = [];
  73. if (!graph[b]) graph[b] = [];
  74. graph[a].push(b);
  75. graph[b].push(a);
  76. }
  77. // 2. 边访问记录(确保 (a,b) 和 (b,a) 被视为同一条边)
  78. const edgeKey = (a: number, b: number) => (a < b ? `${a},${b}` : `${b},${a}`);
  79. // 3. 结果收集
  80. const result: number[][] = [];
  81. const polygonSet = new Set<string>(); // 用于去重
  82. // 4. 标准化多边形(按点集排序去重)
  83. const normalizePolygon = (polygon: number[]) => {
  84. const points = [...new Set(polygon.slice(0, -1))].sort((a, b) => a - b);
  85. return JSON.stringify(points);
  86. };
  87. // 5. DFS 查找闭合环(无向图)
  88. function findPolygons(start: number) {
  89. const stack: {
  90. current: number;
  91. path: number[];
  92. visitedEdges: Set<string>;
  93. }[] = [{ current: start, path: [start], visitedEdges: new Set() }];
  94. while (stack.length > 0) {
  95. const { current, path, visitedEdges } = stack.pop()!;
  96. for (const neighbor of graph[current]) {
  97. const edge = edgeKey(current, neighbor);
  98. // 跳过已访问的边
  99. if (visitedEdges.has(edge)) continue;
  100. // 发现闭合环(路径长度 > 2 且回到起点)
  101. if (neighbor === start && path.length > 2) {
  102. const closedPath = [...path, start];
  103. const polyKey = normalizePolygon(closedPath);
  104. if (!polygonSet.has(polyKey)) {
  105. polygonSet.add(polyKey);
  106. result.push(closedPath);
  107. }
  108. continue;
  109. }
  110. // 避免重复访问点(起点除外)
  111. if (neighbor !== start && path.includes(neighbor)) continue;
  112. const newVisitedEdges = new Set(visitedEdges);
  113. newVisitedEdges.add(edge);
  114. stack.push({
  115. current: neighbor,
  116. path: [...path, neighbor],
  117. visitedEdges: newVisitedEdges,
  118. });
  119. }
  120. }
  121. }
  122. // 6. 查找所有闭合环
  123. for (const node in graph) {
  124. const nodeId = Number(node);
  125. if (graph[nodeId].length >= 2) {
  126. findPolygons(nodeId);
  127. }
  128. }
  129. // 7. 查找开放路径(度数为1的点)
  130. const visitedNodes = new Set<number>();
  131. for (const node in graph) {
  132. const nodeId = Number(node);
  133. if (visitedNodes.has(nodeId)) continue;
  134. // 开放路径必须从端点开始(度数为1的点)
  135. if (graph[nodeId].length === 1) {
  136. const path: number[] = [];
  137. let current: number | null = nodeId;
  138. let prev: number | null = null;
  139. while (current !== null) {
  140. visitedNodes.add(current);
  141. path.push(current);
  142. // 找到下一个未访问的相邻点
  143. let next: number | null = null;
  144. for (const neighbor of graph[current]) {
  145. if (neighbor !== prev && !visitedNodes.has(neighbor)) {
  146. next = neighbor;
  147. break;
  148. }
  149. }
  150. prev = current;
  151. current = next;
  152. }
  153. if (path.length >= 2) {
  154. result.push(path);
  155. }
  156. }
  157. }
  158. return result;
  159. }