@fleet-frontend/mower-maps 0.0.9-beta.9 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -69,7 +69,8 @@ const DEFAULT_LINE_WIDTHS = {
69
69
  const DEFAULT_OPACITIES = {
70
70
  FULL: 1.0,
71
71
  HIGH: 0.7,
72
- MEDIUM: 0.6};
72
+ MEDIUM: 0.6,
73
+ DOODLE: 0.8};
73
74
  /**
74
75
  * 默认半径设置
75
76
  */
@@ -228,7 +229,6 @@ class SvgMapView {
228
229
  */
229
230
  removeLayer(layer) {
230
231
  const index = this.layers.indexOf(layer);
231
- console.log('removeLayer----->', index);
232
232
  if (index !== -1) {
233
233
  this.layers.splice(index, 1);
234
234
  this.refresh();
@@ -276,7 +276,6 @@ class SvgMapView {
276
276
  width: boundWidth + padding * 2,
277
277
  height: boundHeight + padding * 2,
278
278
  };
279
- console.log('viewbox->', this.viewBox);
280
279
  // 根据宽高比选择合适的preserveAspectRatio设置
281
280
  if (Math.abs(contentAspectRatio - containerAspectRatio) < 0.01) {
282
281
  // 宽高比接近,使用slice填满容器
@@ -286,7 +285,6 @@ class SvgMapView {
286
285
  // 宽高比差异较大,使用meet确保内容完全可见
287
286
  this.svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
288
287
  }
289
- console.log('fitToView');
290
288
  this.updateViewBox();
291
289
  }
292
290
  /**
@@ -357,7 +355,6 @@ class SvgMapView {
357
355
  * 绘制图层,不传参数则默认绘制所有图层
358
356
  */
359
357
  onDrawLayers(type) {
360
- console.log('onDrawLayers----->', type);
361
358
  if (type) {
362
359
  const layer = this.layers.find((layer) => layer.getType() === type);
363
360
  if (layer) {
@@ -434,7 +431,6 @@ class SvgMapView {
434
431
  refresh() {
435
432
  if (this.destroyed)
436
433
  return;
437
- console.log('refresh----->');
438
434
  this.render();
439
435
  }
440
436
  // ==================== 拖拽功能 ====================
@@ -1051,15 +1047,37 @@ class PathLayer extends BaseLayer {
1051
1047
  d += ' Z ';
1052
1048
  }
1053
1049
  });
1054
- // 3. svgElements(直接拼接path字符串,建议逆时针)
1055
- if (Array.isArray(svgElements)) {
1056
- svgElements.forEach((svgPath) => {
1057
- const svgPathString = svgPath?.metadata?.svg;
1058
- if (svgPathString && typeof svgPathString === 'string' && svgPathString.trim()) {
1059
- d += svgPathString + ' ';
1050
+ // 3. svgElements(解析 SVG 字符串并提取 path 数据)
1051
+ Object.values(svgElements).forEach((svgPath) => {
1052
+ const svgPathString = svgPath?.metadata?.svg;
1053
+ if (svgPathString && typeof svgPathString === 'string' && svgPathString.trim()) {
1054
+ // 处理转义字符
1055
+ const processedSvgString = svgPathString.replace(/\\n/g, '\n').replace(/\\"/g, '"');
1056
+ // 解析 SVG 字符串
1057
+ const parser = new DOMParser();
1058
+ const svgDoc = parser.parseFromString(processedSvgString, 'image/svg+xml');
1059
+ const svgElement = svgDoc.documentElement;
1060
+ if (svgElement.tagName === 'svg') {
1061
+ // 查找 path 元素
1062
+ const pathElement = svgElement.querySelector('path');
1063
+ if (pathElement) {
1064
+ const pathData = pathElement.getAttribute('d');
1065
+ if (pathData) {
1066
+ // 获取 SVG 元素的变换参数
1067
+ const centerCoords = svgPath.coordinates?.[0] || [0, 0];
1068
+ const center = [centerCoords[0], centerCoords[1]];
1069
+ const userScale = svgPath.metadata.scale || 1;
1070
+ const direction = svgPath.metadata?.direction || 0;
1071
+ const originalWidth = parseFloat(svgElement.getAttribute('width') || '76');
1072
+ const originalHeight = parseFloat(svgElement.getAttribute('height') || '68');
1073
+ // 应用变换到路径数据
1074
+ const transformedPathData = this.transformSvgPath(pathData, center, userScale, direction, originalWidth, originalHeight);
1075
+ d += transformedPathData + ' ';
1076
+ }
1077
+ }
1060
1078
  }
1061
- });
1062
- }
1079
+ }
1080
+ });
1063
1081
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1064
1082
  path.setAttribute('d', d);
1065
1083
  const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
@@ -1084,47 +1102,132 @@ class PathLayer extends BaseLayer {
1084
1102
  // 2. 创建一个组,应用 clipPath
1085
1103
  const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
1086
1104
  group.setAttribute('clip-path', `url(#${clipPathId})`);
1087
- group.setAttribute('opacity', '0.6'); // 统一透明度,防止叠加脏乱
1088
- // 3. 渲染所有路径
1105
+ group.setAttribute('opacity', '0.5'); // 统一透明度,防止叠加脏乱
1106
+ // 3. 优化渲染:按样式分组并合并路径
1107
+ this.renderOptimizedPaths(group);
1108
+ svgGroup.appendChild(group);
1109
+ }
1110
+ /**
1111
+ * 优化渲染:按样式分组并合并路径,减少 DOM 节点数量
1112
+ */
1113
+ renderOptimizedPaths(group) {
1114
+ // 按样式分组存储路径数据
1115
+ const styleGroups = new Map();
1116
+ // 收集所有路径数据并按样式分组
1089
1117
  for (const element of this.elements) {
1090
- const { id, elements } = element;
1118
+ // 类型断言:PathLayer 中的 elements 实际上是 PathElements 结构
1119
+ const pathElement = element;
1120
+ const { id, elements } = pathElement;
1091
1121
  this.boundaryPaths[id] = [];
1122
+ elements.forEach((pathElement) => {
1123
+ const { coordinates, style } = pathElement;
1124
+ if (coordinates.length < 2)
1125
+ return;
1126
+ // 生成样式键(用于分组)
1127
+ const styleKey = this.generateStyleKey(style);
1128
+ // 构建路径数据
1129
+ let pathData = `M ${coordinates[0][0]} ${coordinates[0][1]}`;
1130
+ for (let i = 1; i < coordinates.length; i++) {
1131
+ pathData += ` L ${coordinates[i][0]} ${coordinates[i][1]}`;
1132
+ }
1133
+ // 按样式分组存储
1134
+ if (!styleGroups.has(styleKey)) {
1135
+ styleGroups.set(styleKey, { pathData: [], elements: [] });
1136
+ }
1137
+ styleGroups.get(styleKey).pathData.push(pathData);
1138
+ styleGroups.get(styleKey).elements.push(pathElement);
1139
+ });
1140
+ }
1141
+ // 为每种样式创建一个合并的 path 元素
1142
+ styleGroups.forEach((groupData) => {
1143
+ const { pathData, elements } = groupData;
1144
+ if (pathData.length === 0)
1145
+ return;
1146
+ // 使用第一个元素的样式作为该组的样式
1147
+ const firstElement = elements[0];
1148
+ const style = firstElement.style;
1149
+ // 创建合并的 path 元素
1150
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1151
+ // 合并所有路径数据
1152
+ const mergedPathData = pathData.join(' ');
1153
+ path.setAttribute('d', mergedPathData);
1154
+ // 设置样式属性
1155
+ path.setAttribute('fill', 'none');
1156
+ path.setAttribute('stroke', style.lineColor || '#000000');
1157
+ path.setAttribute('mix-blend-mode', 'normal');
1158
+ const lineWidth = Math.max(style.lineWidth || 1, 0.5);
1159
+ path.setAttribute('stroke-width', lineWidth.toString());
1160
+ path.setAttribute('stroke-linecap', 'round');
1161
+ path.setAttribute('stroke-linejoin', 'round');
1162
+ path.classList.add('vector-path');
1163
+ // 将合并的 path 添加到组中
1164
+ group.appendChild(path);
1165
+ // 保存引用到 boundaryPaths 中(保持兼容性)
1092
1166
  elements.forEach((element) => {
1093
- this.renderPathToGroup(group, id, element);
1167
+ const { id } = element;
1168
+ if (!this.boundaryPaths[id]) {
1169
+ this.boundaryPaths[id] = [];
1170
+ }
1171
+ this.boundaryPaths[id].push(path);
1094
1172
  });
1173
+ });
1174
+ }
1175
+ /**
1176
+ * 变换 SVG 路径数据
1177
+ */
1178
+ transformSvgPath(pathData, center, scale, direction, originalWidth, originalHeight) {
1179
+ // 解析路径数据并应用变换
1180
+ const commands = pathData.match(/[MmLlHhVvCcSsQqTtAaZz][^MmLlHhVvCcSsQqTtAaZz]*/g) || [];
1181
+ let transformedCommands = [];
1182
+ for (const command of commands) {
1183
+ const type = command[0];
1184
+ const params = command
1185
+ .slice(1)
1186
+ .trim()
1187
+ .split(/[\s,]+/)
1188
+ .filter(Boolean)
1189
+ .map(Number);
1190
+ if (type === 'Z' || type === 'z') {
1191
+ // 闭合路径,不需要变换
1192
+ transformedCommands.push(command);
1193
+ continue;
1194
+ }
1195
+ // 处理坐标参数
1196
+ let transformedParams = [];
1197
+ for (let i = 0; i < params.length; i += 2) {
1198
+ if (i + 1 < params.length) {
1199
+ let x = params[i];
1200
+ let y = params[i + 1];
1201
+ // 应用变换:先平移到中心,然后缩放、旋转,最后平移到目标位置
1202
+ // 1. 平移到原点(相对于原始尺寸的中心)
1203
+ x -= originalWidth / 2;
1204
+ y -= originalHeight / 2;
1205
+ // 2. 应用缩放
1206
+ x *= scale;
1207
+ y *= scale;
1208
+ // 3. 应用旋转
1209
+ const cos = Math.cos(-direction);
1210
+ const sin = Math.sin(-direction);
1211
+ const newX = x * cos - y * sin;
1212
+ const newY = x * sin + y * cos;
1213
+ // 4. 平移到目标位置
1214
+ x = newX + center[0];
1215
+ y = newY + center[1];
1216
+ transformedParams.push(x, y);
1217
+ }
1218
+ }
1219
+ // 重建命令
1220
+ if (transformedParams.length > 0) {
1221
+ transformedCommands.push(type + transformedParams.join(' '));
1222
+ }
1095
1223
  }
1096
- svgGroup.appendChild(group);
1224
+ return transformedCommands.join(' ');
1097
1225
  }
1098
1226
  /**
1099
- * 渲染单个路径到指定的组中
1227
+ * 生成样式键,用于路径分组
1100
1228
  */
1101
- renderPathToGroup(group, id, element) {
1102
- const { coordinates, style } = element;
1103
- if (coordinates.length < 2)
1104
- return;
1105
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1106
- // 构建路径数据
1107
- let pathData = `M ${coordinates[0][0]} ${coordinates[0][1]}`;
1108
- for (let i = 1; i < coordinates.length; i++) {
1109
- pathData += ` L ${coordinates[i][0]} ${coordinates[i][1]}`;
1110
- }
1111
- path.style.mixBlendMode = 'normal';
1112
- // 设置路径属性
1113
- path.setAttribute('d', pathData);
1114
- // 直接给fill的颜色设置透明度会导致path重叠的部分颜色叠加,所以使用fill填充实色,通过fill-opacity设置透明度
1115
- path.setAttribute('fill', 'none');
1116
- // path.setAttribute('fill-opacity', '0.4');
1117
- path.setAttribute('stroke', style.lineColor || '#000000');
1118
- path.setAttribute('mix-blend-mode', 'normal');
1119
- const lineWidth = Math.max(style.lineWidth || 1, 0.5);
1120
- path.setAttribute('stroke-width', lineWidth.toString());
1121
- path.setAttribute('stroke-linecap', 'round');
1122
- path.setAttribute('stroke-linejoin', 'round');
1123
- // 注意:这里不设置 opacity,因为透明度由父组控制
1124
- // path.setAttribute('vector-effect', 'non-scaling-stroke');
1125
- path.classList.add('vector-path');
1126
- this.boundaryPaths[id].push(path);
1127
- group.appendChild(path);
1229
+ generateStyleKey(style) {
1230
+ return `${style.lineColor || '#000000'}-${style.lineWidth || 1}-${style.opacity || 1}`;
1128
1231
  }
1129
1232
  }
1130
1233
 
@@ -1548,7 +1651,7 @@ const DOODLE_STYLES = {
1548
1651
  lineColor: '#ff5722',
1549
1652
  fillColor: '#ff9800', // 粉色半透明填充
1550
1653
  lineWidth: DEFAULT_LINE_WIDTHS.TIME_LIMIT_OBSTACLE,
1551
- opacity: DEFAULT_OPACITIES.HIGH,
1654
+ opacity: DEFAULT_OPACITIES.DOODLE,
1552
1655
  };
1553
1656
  const PATH_EDGE_STYLES = {
1554
1657
  lineWidth: DEFAULT_LINE_WIDTHS.PATH,
@@ -1590,13 +1693,13 @@ const DEFAULT_STYLES = {
1590
1693
  function convertPointsFormat(points) {
1591
1694
  if (!points || points.length === 0)
1592
1695
  return null;
1593
- return points.map(point => {
1696
+ return points.map((point) => {
1594
1697
  if (point.length >= 2) {
1595
1698
  // 对前两个元素应用缩放因子,保留其他元素
1596
1699
  return [
1597
1700
  point[0] * SCALE_FACTOR,
1598
1701
  -point[1] * SCALE_FACTOR, // Y轴翻转,与Python代码一致
1599
- ...point.slice(2) // 保留第三个及以后的元素
1702
+ ...point.slice(2), // 保留第三个及以后的元素
1600
1703
  ];
1601
1704
  }
1602
1705
  return point;
@@ -1611,7 +1714,7 @@ function convertPositionFormat(position) {
1611
1714
  return null;
1612
1715
  return {
1613
1716
  x: position[0] * SCALE_FACTOR,
1614
- y: -position[1] * SCALE_FACTOR // Y轴翻转
1717
+ y: -position[1] * SCALE_FACTOR, // Y轴翻转
1615
1718
  };
1616
1719
  }
1617
1720
  /**
@@ -1620,9 +1723,146 @@ function convertPositionFormat(position) {
1620
1723
  function convertCoordinate(x, y) {
1621
1724
  return {
1622
1725
  x: x * SCALE_FACTOR,
1623
- y: -y * SCALE_FACTOR // Y轴翻转
1726
+ y: -y * SCALE_FACTOR, // Y轴翻转
1624
1727
  };
1625
1728
  }
1729
+ /**
1730
+ * @param x x坐标
1731
+ * @param y y坐标
1732
+ * @param isAllowInBoundary 是否允许点在边界上的判断
1733
+ * @return ture-点在边界上即可视为在边界内,false-严格判断点在边界内
1734
+ */
1735
+ function isPointIn$1(x, y, pointList, isAllowInBoundary) {
1736
+ let count = 0;
1737
+ let size = pointList.length;
1738
+ let p1, p2, p3;
1739
+ for (let i = 0; i < size; i++) {
1740
+ p1 = pointList[i];
1741
+ p2 = pointList[(i + 1) % size];
1742
+ if (p1.y == null || p2.y == null || p1.x == null || p2.x == null) {
1743
+ continue;
1744
+ }
1745
+ if (p1.y === p2.y) {
1746
+ continue;
1747
+ }
1748
+ if (y > Math.min(p1.y, p2.y) && y < Math.max(p1.y, p2.y)) {
1749
+ const interX = ((y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y) + p1.x;
1750
+ if (interX >= x) {
1751
+ count++;
1752
+ }
1753
+ else if (interX == x) {
1754
+ return isAllowInBoundary;
1755
+ }
1756
+ }
1757
+ else {
1758
+ if (y == p2.y && x <= p2.x) {
1759
+ p3 = pointList[(i + 2) % size];
1760
+ if (y >= Math.min(p1.y, p3.y) && y <= Math.max(p1.y, p3.y)) {
1761
+ // 若当前点的y坐标位于 p1和p3组成的线段关于y轴的投影中,则记为该点的射线只穿过端点一次。
1762
+ ++count;
1763
+ }
1764
+ else {
1765
+ // 若当前点的y坐标不能包含在p1和p3组成的线段关于y轴的投影中,则点射线通过的两条线段组成了一个弯折的部分,
1766
+ // 此时我们记射线穿过该端点两次
1767
+ count += 2;
1768
+ }
1769
+ }
1770
+ }
1771
+ }
1772
+ return count % 2 == 1;
1773
+ }
1774
+ /**
1775
+ * 用于判断三个点的方向的辅助方法
1776
+ */
1777
+ function orientation(p, q, r) {
1778
+ const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
1779
+ if (val == 0)
1780
+ return 0; // colinear
1781
+ return val > 0 ? 1 : 2; // clock or counterclock wise
1782
+ }
1783
+ /**
1784
+ * 检查点q是否在线段pr上的辅助方法
1785
+ */
1786
+ function onSegment(p, q, r) {
1787
+ if (q.x <= Math.max(p.x, r.x) &&
1788
+ q.x >= Math.min(p.x, r.x) &&
1789
+ q.y <= Math.max(p.y, r.y) &&
1790
+ q.y >= Math.min(p.y, r.y)) {
1791
+ return true;
1792
+ }
1793
+ return false;
1794
+ }
1795
+ /**
1796
+ * 判断两条线段是否相交的方法
1797
+ */
1798
+ function doTwoLinesIntersect(p1, q1, p2, q2) {
1799
+ //处理p1和q1两个点相同的情况
1800
+ if (p1.x - q1.x == 0 && p1.y - q1.y == 0) {
1801
+ return false;
1802
+ }
1803
+ if (p2.x - q2.x == 0 && p2.y - q2.y == 0) {
1804
+ return false;
1805
+ }
1806
+ // 计算四个点的方向
1807
+ const o1 = orientation(p1, q1, p2);
1808
+ const o2 = orientation(p1, q1, q2);
1809
+ const o3 = orientation(p2, q2, p1);
1810
+ const o4 = orientation(p2, q2, q1);
1811
+ // 一般情况,如果四个方向两两不同,则线段相交
1812
+ if (o1 != o2 && o3 != o4) {
1813
+ return true;
1814
+ }
1815
+ // 特殊情况,当线段的端点在另一条线段上时
1816
+ if (o1 == 0 && onSegment(p1, q1, p2))
1817
+ return true;
1818
+ if (o2 == 0 && onSegment(p1, q1, q2))
1819
+ return true;
1820
+ if (o3 == 0 && onSegment(p2, q2, p1))
1821
+ return true;
1822
+ if (o4 == 0 && onSegment(p2, q2, q1))
1823
+ return true;
1824
+ // 如果以上情况都不满足,则线段不相交
1825
+ return false;
1826
+ }
1827
+ /**
1828
+ * 判断多点折线是否相交
1829
+ */
1830
+ function doIntersect(points1, points2) {
1831
+ if (points1 == null || points2 == null || points1.length < 3 || points2.length < 3) {
1832
+ return false;
1833
+ }
1834
+ for (let i = 0; i < points1.length - 1; i++) {
1835
+ for (let j = 0; j < points2.length - 1; j++) {
1836
+ if (doTwoLinesIntersect(points1[i], points1[i + 1], points2[j], points2[j + 1])) {
1837
+ return true;
1838
+ }
1839
+ }
1840
+ }
1841
+ return false;
1842
+ }
1843
+ /**
1844
+ * 两个图形是否完全分离,互相不包含
1845
+ */
1846
+ function isOutsideToEachOther(points1, points2) {
1847
+ // 相交关系
1848
+ if (doIntersect(points1, points2)) {
1849
+ return false;
1850
+ }
1851
+ // 点关系,判断每个图形的点都在另一个图形外部
1852
+ for (let point of points1) {
1853
+ if (isPointIn$1(point.x, point.y, points2, true)) {
1854
+ // Log.i("ycf", "isOutsideToEachOther: mapPoint1=" + mapPoint);
1855
+ return false;
1856
+ }
1857
+ }
1858
+ for (let point of points2) {
1859
+ if (isPointIn$1(point.x, point.y, points1, true)) {
1860
+ // Log.i("ycf", "isOutsideToEachOther: mapPoint2=" + mapPoint);
1861
+ return false;
1862
+ }
1863
+ }
1864
+ return true;
1865
+ }
1626
1866
 
1627
1867
  /**
1628
1868
  * 按Python逻辑创建路径段:根据连续的两点之间的关系确定线段类型
@@ -1885,6 +2125,136 @@ function calculateMapGpsCenter(mapData) {
1885
2125
  };
1886
2126
  }
1887
2127
 
2128
+ /**
2129
+ * 并查集(Union-Find)是一种非常高效的数据结构,用于处理动态连通性问题。
2130
+ * 它可以快速判断网络中任意两点是否连通,并能将不连通的集合合并。
2131
+ */
2132
+ class UnionFind {
2133
+ /**
2134
+ * 构造函数,n为图的节点总数
2135
+ * @param {number} n - 节点总数
2136
+ */
2137
+ constructor(n) {
2138
+ this.count = n; // 连通分量的数量
2139
+ this.parent = new Array(n); // parent[i]表示第i个元素所指向的父节点
2140
+ // 初始时,每个节点的父节点是自己
2141
+ for (let i = 0; i < n; i++) {
2142
+ this.parent[i] = i;
2143
+ }
2144
+ }
2145
+ /**
2146
+ * 查找元素p所对应的集合编号(根节点)
2147
+ * @param {number} p - 要查找的元素
2148
+ * @returns {number} 根节点的编号
2149
+ */
2150
+ find(p) {
2151
+ while (p !== this.parent[p]) {
2152
+ this.parent[p] = this.parent[this.parent[p]]; // 路径压缩
2153
+ p = this.parent[p];
2154
+ }
2155
+ return p;
2156
+ }
2157
+ /**
2158
+ * 判断元素p和元素q是否属于同一集合
2159
+ * @param {number} p - 第一个元素
2160
+ * @param {number} q - 第二个元素
2161
+ * @returns {boolean} 是否连通
2162
+ */
2163
+ isConnected(p, q) {
2164
+ return this.find(p) === this.find(q);
2165
+ }
2166
+ /**
2167
+ * 合并元素p和元素q所属的集合
2168
+ * @param {number} p - 第一个元素
2169
+ * @param {number} q - 第二个元素
2170
+ */
2171
+ union(p, q) {
2172
+ const rootP = this.find(p);
2173
+ const rootQ = this.find(q);
2174
+ if (rootP === rootQ) {
2175
+ return; // 已经在同一个集合中
2176
+ }
2177
+ // 将较小的根节点作为父节点(按秩合并的简化版本)
2178
+ if (rootP < rootQ) {
2179
+ this.parent[rootQ] = rootP;
2180
+ }
2181
+ else {
2182
+ this.parent[rootP] = rootQ;
2183
+ }
2184
+ // 两个集合合并成一个集合,连通分量减1
2185
+ this.count--;
2186
+ }
2187
+ /**
2188
+ * 获取当前的连通分量个数
2189
+ * @returns {number} 连通分量数量
2190
+ */
2191
+ getCount() {
2192
+ return this.count;
2193
+ }
2194
+ /**
2195
+ * 获取联通的组
2196
+ * @param {Array} list - 原始元素列表
2197
+ * @returns {Array<Set>} 联通组列表
2198
+ */
2199
+ getConnectedGroup(list) {
2200
+ if (!list || list.length === 0 || !this.parent || this.parent.length === 0) {
2201
+ return null;
2202
+ }
2203
+ if (list.length !== this.parent.length) {
2204
+ return null;
2205
+ }
2206
+ const map = new Map();
2207
+ // 遍历所有元素,按根节点分组
2208
+ for (let i = 0; i < this.parent.length; i++) {
2209
+ const root = this.parent[i];
2210
+ if (!map.has(root)) {
2211
+ map.set(root, new Set());
2212
+ }
2213
+ map.get(root).add(list[i]);
2214
+ }
2215
+ return Array.from(map.values());
2216
+ }
2217
+ /**
2218
+ * 重置并查集
2219
+ * @param {number} n - 新的节点总数
2220
+ */
2221
+ reset(n) {
2222
+ this.count = n;
2223
+ this.parent = new Array(n);
2224
+ for (let i = 0; i < n; i++) {
2225
+ this.parent[i] = i;
2226
+ }
2227
+ }
2228
+ }
2229
+
2230
+ function isTunnelConnected(a, b, connectIds) {
2231
+ if (!a || !b)
2232
+ return false;
2233
+ if (!connectIds || connectIds?.length === 0)
2234
+ return false;
2235
+ const temp = [a?.id, b?.id];
2236
+ temp.sort();
2237
+ return connectIds?.includes(temp?.join('-'));
2238
+ }
2239
+ function isOverlayConnected(a, b) {
2240
+ if (!a || !b) {
2241
+ return false;
2242
+ }
2243
+ if (!a?.points?.length || !b?.points?.length) {
2244
+ return false;
2245
+ }
2246
+ const aPoints = a?.points?.map(item => ({ x: item[0], y: item[1] }));
2247
+ const bPoints = b?.points?.map(item => ({ x: item[0], y: item[1] }));
2248
+ try {
2249
+ if (isOutsideToEachOther(aPoints, bPoints)) {
2250
+ return false;
2251
+ }
2252
+ }
2253
+ catch (error) {
2254
+ console.log('error->', error);
2255
+ }
2256
+ return true;
2257
+ }
1888
2258
  /**
1889
2259
  * 通过 mapData 和 pathData 生成所有 boundary 的数据
1890
2260
  * @param mapData 地图数据
@@ -1893,11 +2263,12 @@ function calculateMapGpsCenter(mapData) {
1893
2263
  */
1894
2264
  function generateBoundaryData(mapData, pathData) {
1895
2265
  const boundaryData = [];
2266
+ let chargingPileBoundary = undefined;
1896
2267
  if (!mapData || !mapData.sub_maps) {
1897
2268
  return boundaryData;
1898
2269
  }
1899
2270
  // 第一步:收集所有TUNNEL数据的connection信息
1900
- const connectedBoundaryIds = new Set();
2271
+ const connectIds = [];
1901
2272
  // 遍历mapData中的tunnels字段
1902
2273
  if (mapData.tunnels && Array.isArray(mapData.tunnels)) {
1903
2274
  for (const tunnel of mapData.tunnels) {
@@ -1905,10 +2276,8 @@ function generateBoundaryData(mapData, pathData) {
1905
2276
  if (connection) {
1906
2277
  // connection可能是单个数字或数组
1907
2278
  if (Array.isArray(connection)) {
1908
- connection.forEach(id => connectedBoundaryIds.add(id));
1909
- }
1910
- else if (typeof connection === 'number') {
1911
- connectedBoundaryIds.add(connection);
2279
+ connection.sort();
2280
+ connectIds.push(connection.join('-'));
1912
2281
  }
1913
2282
  }
1914
2283
  }
@@ -1919,9 +2288,9 @@ function generateBoundaryData(mapData, pathData) {
1919
2288
  if (!subMap.elements)
1920
2289
  continue;
1921
2290
  // 每个sub_map的elements是边界坐标,没有sub_map只有一个boundary数据
1922
- const boundaryElement = subMap.elements.find(element => element.type === 'BOUNDARY');
2291
+ const boundaryElement = subMap.elements.find((element) => element.type === 'BOUNDARY');
1923
2292
  // 如果当前subMap存在充电桩且充电桩存在tunnel,说明当前subMap中的boundary是初始boundary,这个boundary不为孤立区域
1924
- const hasTunnelToChargingPile = subMap.elements.some(element => element.type === 'CHARGING_PILE' && element.tunnel);
2293
+ const hasTunnelToChargingPile = subMap.elements.some((element) => element.type === 'CHARGING_PILE' && element.tunnel);
1925
2294
  // 创建基础的 boundary 数据(来自 mapData)
1926
2295
  const boundary = {
1927
2296
  // 从 BOUNDARY 元素复制属性
@@ -1930,8 +2299,6 @@ function generateBoundaryData(mapData, pathData) {
1930
2299
  area: subMap?.area,
1931
2300
  points: convertPointsFormat(boundaryElement?.points) || [],
1932
2301
  type: boundaryElement.type,
1933
- // 判断是否为孤立子区域
1934
- isIsolated: hasTunnelToChargingPile ? false : !connectedBoundaryIds.has(boundaryElement.id)
1935
2302
  };
1936
2303
  // 如果有 pathData,尝试匹配对应的分区数据
1937
2304
  if (pathData) {
@@ -1947,8 +2314,33 @@ function generateBoundaryData(mapData, pathData) {
1947
2314
  boundary.endTime = partitionData.endTime;
1948
2315
  }
1949
2316
  }
2317
+ if (hasTunnelToChargingPile) {
2318
+ chargingPileBoundary = boundary;
2319
+ }
1950
2320
  boundaryData.push(boundary);
1951
2321
  }
2322
+ const unionFind = new UnionFind(boundaryData?.length);
2323
+ for (let i = 0; i < boundaryData?.length - 1; i++) {
2324
+ for (let j = i + 1; j < boundaryData?.length; j++) {
2325
+ const boundary1 = boundaryData[i];
2326
+ const boundary2 = boundaryData[j];
2327
+ const isChannelConnect = isTunnelConnected(boundary1, boundary2, connectIds);
2328
+ const isOverlayConnect = isOverlayConnected(boundary1, boundary2);
2329
+ if (isChannelConnect || isOverlayConnect) {
2330
+ unionFind.union(i, j);
2331
+ }
2332
+ }
2333
+ }
2334
+ const tunnelAndOverlayList = unionFind.getConnectedGroup(boundaryData);
2335
+ const chargingPileConnectBoundarys = tunnelAndOverlayList?.find(item => item?.has(chargingPileBoundary));
2336
+ for (let boundary of boundaryData) {
2337
+ if (chargingPileConnectBoundarys?.has(boundary)) {
2338
+ boundary.isIsolated = false;
2339
+ }
2340
+ else {
2341
+ boundary.isIsolated = true;
2342
+ }
2343
+ }
1952
2344
  return boundaryData;
1953
2345
  }
1954
2346
 
@@ -2165,13 +2557,15 @@ var hNoPosition = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEoAAABKCAYAAAA
2165
2557
 
2166
2558
  var hDisabled = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAACQySURBVHgB7XxJrCXXed5/arx175t6nskeODWbg0KKk4aEUWTYMqyNkQ6Q7IwAMiAkChRkkZ1ay2ycwFlZyCIIslJvggSQnYBKZNkWFdmMZYmkKE7dbHY/9sDX/YY71XROvu8/Vffe9/r1xClZuJrFqltVt26dr/7h+4fzjPx/sLgzZwJZ/m+hfkjXAtl12Ihcs7LccXrs4Ndrc+aMlf/Hi5HPcJmAUsyHsreMpWtjyWs8w7pI0fPPUnSNJEMn8/MiG/iMjWxgJwmdpN1ahmUt3bjE0cqc+VEln+HyqYPlnDPye8dSmY8SiZJIgr2BJACoxMrtdkuZGYlHbrItsMqc/id93jT3UrY7qyW/UcqBTm5+/5VSPuXlUwPLfePpWB7clcjyRiqLaaASlGIdJIHIAIBY/9tJZ/MzxM1xybCO/G4ZeHWMsC3H2O+JgkhpW+EWx2MCd2AsF6UwZ8/W8iksnzhY7o8A0nKUSb9KJOl6gDAaKTaw7UJ5AEacGgVrAgwXAFOl/vMY+3MAoP2sIOVuAiDBiwFaNIfjAEylj1usAqlb6+Zybc/okwbtEwPLnT4dyqMXelK4GFY6EFNA3aBGwzIgRrIGYLpxoBdXA6NAxAm2QyOdjgeRoFQzAOrnAgDgfITj3CdQej32XWQlBogxjhWUvsJSaKWbWAXvUD6Q9Zdzc0Y+EefwiYDlvv1CJkHaUxs0JEgYmKG6VYFKg6k8SNx2okDG4gef5yLhvN/GnVASHB+WTkIMnOe5rQkStinO5ZXVLYHisagL8LBPUCOAViYw/ngzBaSuqGuZg5SNoZ6D+aH593+cy8dcPhZYKk3H352TSmIxlCZIUQwbpOBAiiglWeS3OY9hP60DqQ0BgVfETdIQKoqdqsKAI/88dYwtjoW8Dvu8np/jsJGQGtuk2WKphrWEAEpfAiUPElcCLKpoD9K2AdCuliP5T78YGiNOPuLykcFyf/i1VM6t9qQzDsXsBBCQqFmQuAYS+bEBmGgMSclCBYZSI2kkNaTIWHwHgJUYeIxtCO9Jv5YaP6gcn2OcI2BBc6zA5yTwgFW2lgwA5QAyBGgqbThGG0c1zSJeZ9WjRkEp7xxY/6i27COB5b59GGq3r6fSRNWrolBVzgAo0wAUASCqUFFTLQOx+ExQbA4wsO3gM4HJC4AKibIEJQDQoE5B6J+rtAAA+w7bOPKfA0igECiYRldU+G6tEtqRhjoANIJEEB3UtuMqtXOUOKrmqAKQ9br53r1TjXsGy33z1JxksLCt2iUECiCNCVbkpasHCRoOIHEABShBnULJAU6nDlVSQmwtjkW4RwXdUekiUPiBAOf5PVM7mGUvSZQoy88x1dcDVuJqSl+JzxGPhbXUwxI2EBIIoOoYYFFNxxV+r5Z4qVZbRieQA7Bi78a92rF7AkuB2runI30a8TCUCitdeWAitUdUtxSSxcEW65CiNIQ0UBljBU2lDuccwOLnKPTHDABy+G6p0gUpswAB4KQAMC8BBi8HMBX2OwBBJctghaSVkJzE1apqrgTcUaVg5QCTqhouOhlfL9T4z9GOgW50B5VsUDWzewLsrsFyZ54mAehKP/VAUYIIFqUJTyj1CMNOYpUYW4VeegBAVUUKSAw9qgCys5CoJJHaBSRMEsQh1MWoZAExMQbHcEeaboKG8cHww/4APAIEyPCbfh9f1M8hVU08SLR4LqDa4lxVqJrSEZT4R3uW4roRQHeQsKhTS39u/W4BuyuwPDUoerDJUC+AlCC2I6E0YwAArkQwIvArShaBghFD5BaJA3BpgMcDKCZIFKggjHULBKSuPHgBrnOwfSGAqiXY/OMATD0YpMgkBK+CfYP3A1gRgCBYzhQKnAkgObaARNWwWTk8LWxUWSpgCc6Pkko9pR3hO1mlgNU4vpiu302ceUew3JkXOwhk5zbZqKqRLLxi2JZAPVs58upm41ilCXBi4FTJRMIqVpDEJZA8gFhOQXOOhj9SsBQ0PlOt/0HKGjcPYFTaABR+EN8hWLBLlDJITJBQJQvYwFJsmeMllAqgQcAdAYw4rsDfSkmwraCO9JoWx7sLVvpQSXrJzlfX7pTZiG4LFHnU2sWuhA1QPRBNejKyc7wiMXnk2Truk6YJ4j9ABAmytGUAJg2obgnGCkmzOM59Agyf72r+NgEE8DVBg41TGoF9gGTU2hNHeFvoogVnCqBe1kLdCBgGGAK0qsrVVsV8iTXvHei9yONKeNq4g/tia2urPM6NsO3yHMKivvNOpsCL+2OG6evyUSXL/evHd4hZAhAYAMOWOCQYoXq+ggBh4GqfIvwY1DDPaYlTlSYDYBxUMMRnsCIMjGoIyasTqEUKMwLpAlg09oa2TikE7lczVUEpCzxocPlURbVXAMoAKKNgVaTy+A5RzfEickhrgWtwLBjj8lylq8R+WBfqLSNcS/VMMqvSVELahqV/AQyRxvv75t+eHd0zWGrQ825PpajlUQSLdkm9GtQPZhQgxGqfCuSnIoBDkAJsCZpzqYJjTAeDx2chSFRJHC9iVUkTxHm20BnG8908XkjzqNeh8jl6SD4eoCJaaTUokrI/2jW4sMq4SUgeAjXmAAhAGW7B4QlYVQwBIoEbQ/UAmG5LcLqx9KCKuaN0QvW6VoZ9ZclKJyKs8/Ort7Jf0fZAIUmX/yDzOSeqHrxVAi5VVz6DQP2gakGmYDUilapOAjDUNkHdahj1isB1MGpEvaCMNaNhHMe4x/Hi3Gv3/eZjV5YeOjbs7NpdBUkq97B0xysrO/sXLx6/9GevHVx7fRlPkyhQhvYPkspROUhmgp+0OaMEq941ZRiEbZ1DfKGKq2uINaGyDvFrj/FkypQPvf76XUuW+1dP9CRe7IIwhmrQRw07z0gRGLrAiBclpIIrJKkGIE68AZc6U4lyzDVYeFGTKWB1mQ47OxdfefAff+HS7ieejKBxe3YuyXyvK520g202fRqadWPaHSYQ1WxVFddKbqyty7XrqzJGAL4w/ODS82/8xz/ZufH+CszBCC9qBMke4blG+PoQL3yIH4B9gzqWzqtnFeS4ttb9Dhh/mZfKwyhZSljXwL/ezu8Ilibt5mVRVa8HkMZBpEDRVpF42nEsKTKeY6hRJ4U6Qf1q6wGzyGGFQU9BEgsAbRfq0cUAsnN7nz32ykP/5HeibD49evig7N25A5hHHpAGHIJCjJzb/IAT7MSfM821a+sb8u77l2QV26NXf/ZTgPZnOEmWDNCqAVQR+2agQAXxWO2aqUbgeQVCn0LVOE2pcvCWoBMFAcNbUTrx8urW1M7NargDEjEEh+ogF1WRAFTMIijPVl7UXYDdGgaepRdG3b8xNNSwWQCrphpCHcOA6pfhAbNXj379yVfv+9pv7N+zWx64/wgIeago2Hr2WTxCbipQDTAtSP6AU27hDy7Mz8nnTj4sFy9fkbdFnh+mSwtf+Zt/9wPlq560YgXdqGPEigAjgFNwMB8MkcjFchh9wVjSea85CdQ1gulxSSgLp2EaNhv7cJNUnXmREtNDko58B/bI+JSK8iKELg6jtAXJpmflBobawIDDnYE9kxt34MKhepQmB6ly2fW5w3tffuT3fvf+QwfkxH1HvFQ0EuJky+o277eS5FfngbTupuvn53qyiALHu33Z08/2hIev/fySxpP0pPpzxtsthkkudMrfSGwzhFZl6UFjwB4h8KZRY+A9GgTf/fML41l8NrPlq9c6mgpmhpMMPR74PFQNlt5mEGpHlhQp8cyHgRLPiHaL9CDw3q8qASDsVu06f/r4t353144lOXLggEYvXDnw2f32M/9Z2mLr2YJ1M9fwMySRJMLZ6ffbdRFSdvy+w3J+77PPndv//Al1MrCTaksp9Uh46T7JskVoluD5a2RA0nn/4jWtxHi34wsp5ShUk7QdWDQXmk2gBySnYo6cqV/mntImSI6qUBn7sPLZgU6XEoVMApl4GepDoXyg1AC+55fHfuexPO7NHz10SLMsngRYTSB4gDww+nkGJD3nfDg4AUnPyfRcA+DkelxzYPceWZibk18d+Y3nIU2pPodrnI8FYWXUwCgh7MQyAgEeg6QSMI4xin0OjhSJVSVGLAeiTLa1Wd96IJE5SJUpmJ/yOfHA+WSe5qSgsWTvBDgkOMw0MHOAUMfleJiAkoW3B5Wsar7F5O2DLz6zZ+dOSeNYVcZitEb1wqr+0LN9cG1F1jb6INNDGY7HU/u1ySPC0XcyjcUP7N4pu5aWpJulW82dvozD+/fJ6/3+ofP7nz8Go/+m+JQqJAqSFZTgVHBOlqSH+TTs18yJYXzUksXUyjq0yaK4At7MeoI7A3gbQz8FK8PNWl7FNIohUJWo+qUMA0lCmWvCCIwSU58lqGEwGSOGWGvyHHAuBM3n9z19P6XqwO7duMQ2Rtk1/wRebFl+ff49mIzN/K+x7dOlwWsw9EAuX7mm2yMH9snjD56AiYxm0DKy0EO8j6TG+7s/98DRyz89py81hIQFrlQNSJn1COnhCRRiR+baYLM43n5TeXIATA09B/4iwP7ReDNY4WKiF1TG579jbGumX2oPGEEiiCHUzQK4gICFtF1GySCiLOiKBw6A3Zi/f08Se3bhbAuRX359/oICNTPGGYGa+jsFzs0COf1w4YPL8uGNVfnC556A1KUzUMOhLyzI6sbhQ6p+DKl0nKG3rdaS2fvYkRJmMTZLcwPDHw6NqsAIAjOf8GZ2FiPd0Vrfm6zxNZ5SVRDgRFgLSFMUefWjZNVM8+I4IztSiSj09sApuYjVWDoXrczff5BvmLZG8VAv6ORNAPXGuffkJnHacsDd4lQCgdgHVeSLIKCXrl6VB+Bl3Qw5y/CCrkVwjwEojaWk26nqaagGcQxY9IWHR80MJibQ/H+F/BbtV1x6+8XxjqtkE1jyAQfJTAIQJa+qYac6lWqhGj6in8AjpkjnFtzHmyG1YPzGyD5lYk/tGN6UI1iQvzTlQytYKibwxrBJv3r3/IR8tuzcmO1D1Nlzc2D4zzz+aAPUzfSwgOkZjwp1GjHietC95Eb30NKO9Ytg9I4hUOTT18anuoux1xbE8GrkmWwNMO489nXIFvwABUmEf0zf+F8d9uFQkUVEdCAlPESHcTPAiYDwCGB0wS+h3pC0SN8Afyik7WIWAq+41LSmB4sgOvpNgBWE0zeOzdsXLnovpvpmpvLTXLPVXrnm3AlQgmeeODkBaVaK2iVOaJrgqIe5PlLz1VCzF7RHNB0hbuDg0RnrpkgxhQi2A3gujiUImiIZzVPi2wgKJichkyv/mzfM/a93UWDor3mUudBGtayCdbuCtgo3k9wLY4Lra/x4VXtVNQBJM5zK5knPQwTHiZu4eO/YPlxdVbfvuWVz0DWQNRLkZv7P5eQDx1SiVHrgDN48d0HOXVqeGPwdC3Py4NH75djhAyoovV5HY0a9Pl3IZHCRTofj9IDh2SBcACH0araOAHoeX6yc156qqZKVM5Xx+y5FU7A2QibvWB733gBhkzfqLHZCF0Oonyb2yH6BFjkWf8xYGnujCbtAU8JGRdxNopSJzaIw3FjfEI+LaU9uDvqabSthcwiyn3jkQQWP4Pzwp3+F7UimEaPIyuq6rPz8Vfnlr9+Wr7zweelBCxYWerBKKFFGWaJqxzwr+RXz+0zlayUJgGZIj6eUtJKP7Yk3bZVjNbwZAPnWtY5KTkAeob1Qg+bkaCYcIltnbY/ZSoq2afSAb8RWxrstJuk09+TfGrdmJoxqsCjBqdqwhXZlQiZ13zak1E4ZPdYnAVQSRfq9l37yl7IxGDas3k7Zu15r9dyP//KvFUPGnof27dOH0+cJQr/yeRMAxbWT+peu9CgyvhpOFtBEK7PSZcYeLFl+OtR2IIY4zKq06EqboUg8aLXGUwCoZAeM0x9igYFci57FsjLT/Gt12E0EABSOYLVgtExcGrI6jQHtzHlyKW7fuXAJXHEwYey2uQ+3HjSr+9chZbzWNeTUucaYG9s8F20XvQpedFEggTNw3qxYH8aFTUMdUwDEQNuh+gAra8BS5R46Fa1Ysw1+dGHSyEThwUpCf23Neh5Zu3jJYqGBhj1oGb8C2Dq7qdpNgHAKGgdHQkkmzjWMwhkwnXq9OPIO4vV3zk+krVbpkwlgrpXSRlLfX76iWxr7XpchaiP9fJkUOXpEG5JSs3RH3si2Aq89bC3QsbcV7LFvqNsodOyRHIRn2+j4i+AMpYvMIs2Q2qzWyJdOS+o1mze41dJLoPbKsPTeuDeuyvInWZTGDE3tkW0wfPj4UXnyoRMTz0Vb9FevvSHvI93C83T/bdTTh4rNEtvtvGF7/5W1tYn562XdwD+Tj0ulND5zE6lKysT2tjYrZnzY2GvGxQnVs5poRyAr4BjMNGgrokwbysK22w7n2J9YjmEma3+MVWNpvAa7XyjeKkJBY8NMINuMx86o2hMzQOnAupl8/tQjM1JjJ5JINatbm9ZsN69uYu8oPFMvXIt/bfrWzUQbSqRilBPW3rQEcGx0YmxUaRe2MHFhn5e0YO1K/QdNyxCgWQNvG9EsxDd1NJIzgr5TlPkwAUWbJfcg8FXkepqdmwVMUytTNdtuyfGGW7VahzS1IrIEB9R+r3atUW/TNx7c1vB3O51WyJBJ7TdOiC8xbNxOa2f5/IxwCZhxvg0qnuoDWYF2HE77XgNtn6bNIlDatjiTlaB0IWRRwOo2QRZ53h9RDfEQzDbrG3T+wRgmqa6ZTQxTlWjGxvzsl69Lfzh9MQWA+ov/8wvFlwNfYY4dRJjfOX7k4DT/NVnt1JvOeNBHThzV/cFoiFRcSbLZFFqCxmv7YetLJWBV4EOdhLm6RrLKti1zKEqXxl66YLPmnbZOc6HNYiQUBdNXTzUjYJaFUP2hptWxYjsQjpeib01tVm0aW2dck6OcXaybRs5vnX9f3n7v4kT6NHxucuv+s5FX33pHnnr0YTD4Q/LWe+/LZaRzNqck3CbpPfXgcdm3a4dK1fKVq/Kgaa9R+fPPybCGY2AIR9Jl4ciGI2ZRnU+lF97RaVIG7GAO16+ObAPxi9NEeNwgSrdZa9eKF08a9priU/k3ojEjyBoNZBAFSuJc5SYiP3mDNy+ulTJp6INsb7h57NU331V15OG//9zTGvbM2qgpz3Jy8sQx+fxjJ70dh5ReXbneGEgrPpzhTfGMSnCsU+0gk/dj8N6ekpXKtDt6COlhN3SderC0vm+bzrjJk0YeQM18FL6EHqbhpMmMYNAT0isSSIpEEHqehXNrvYPdcTKfJXG8BaqpkzQz/5+hY5N9nqFqkozmUEfSjC8+9YT81pdfUEnbt2uX0otHAdJvfel5hEQn9fsEkQF1CBwu73x0j75AaYhz0Lh3vvey+fEqt9qOWae+f1UjusBNAEPmVer+TPKP9X96PPKKBGqJIq7adOokc4EWKlcjsR9pPEk9Zgxl1e1qu1DlRcT6ob586p8+NZd1Ebctymapcltqb26at9qybZfrq2vyX176U/na331Ba4z7du1UoHxYOZP+cT49XQBYOuTdO3bK+fK5hx4791//Jis3vH3i8zFgDrR24QUialouOGC64LDxGuRoN9gADFKaLqjr90hHMDxx6yLZj84UBQBSNZxxp23Dq0+ie/rASokLTBvwjTtLyWrv0N5dSzs2wRI3+acWkNnFbLN1M2sf6vD9P/khwpmfaywoMvWEap+ufihXP7wh43Ghx/kku/n7cSf+1ZHfPOFTHHyhWh1zWtRqu0o5llrHAldKb9hpDDzWHuUF24uH66lkfbizll0b3iNyBoOtKD3eG3Ll/ePmya3xn00jRppCmDLGq4uP7OQ2a1x4uzDG62VIAszEntsBt93x9vNbF97XlTl9qjiPU1Vpo77y3DOaUm4BZJ6P14y6u+b0ZdbMjtR8flKI6e07PbZ4kjrBZtEDUuzICNigAhVNwBZOna3kbAvWUZhsOkQ23u+YQ5aOoWHl62taYwOvSsA78tJvyw3bJKUcQy/tdiFsmkTdTKJmPdwO8KU+wLql9b8FYFu3BIjr7DX7du6Q2V/m72rXUdBhBcr5jpzQ846QYRC9IUxLAQNGgRjRzLDffgFfxPgGhXcCFryjKVioGmrXyGpiVRVXIVnDdTuRKr0K6I7z2pM33BQZUKUNFGcSHmUT0Pe6ti0yE146MwKW7TcBKduDdDfHZr9/aO8e2cpzZ5OOk9xcS2bomKgNjDMYLLOtkgvNzwjGnBQqnme7uEMaZ9IGPi2yHil8No3StdBzk6kgOsvBaNuHdgdrGqyYNsDS7VKugjZDY9ytRrUXZbG5LLsJiFlPaGbWLV+/6fp2efjoUX+dkZtS1EqVNX4yTfez8fxQjzMuHFY6Nk5IiIwfN/toimYC1fJGfjNYy+x1go4u4YK1sedbChinh5Rel9kjQC+oE5VI6CI28rNqWuNt1QzGrNsGrOYI7dbfOfnI1sPbgiK3AWjWYx475JtM2oaSraFUI+a+a5DSzyqO4zMjJWqhJTHLXY3j4tSWbMeUZ8VZPdsvPwFLD9pmglDUpCgckutjTgFJ/awGzokkfahKNuvjPFSTvUCBThOhuDbNGCLuFspzGCrzGOp97aBnpcht2ppN4LTHZ+3X0sK8PHXyYX/uFroKagVgjI/MA9aFq1qbeIPEqCfUwiXBomRRUD7k5E+rnYCdaNPEgmDLvUeqp3Ezw0pB4+yrvFLiVuCHaRCrwr8ZJa8qWda3M9LnBJumerRqMasej504IY89cGLyefM4W6LqNp3bKnXMhP6DZ5/xqZztljZzrWbBMsistR2Svan6zHhOkznNAIep16B2KZopefKjodwSrD94eazSRXVsWWw650XUhZ7pRrBTSea0jdq4ZjVK5Tn7gTnVWaBULbYQSAUMYH39731Z1chsgand305Nafe+ApC+/NSTTTV65vyszZp8WetpvruZJoO9ivpCSbo4JwgvfziuEe74aSxcVbtQ2rpdf5aO7RurI1lY7CnfMpzPx6idDV/9UHsxY5SAKGlWmkZYHLOWwQPeXp0neX/gB2Zush+zrJvLXLcrzz3+mK5Xr9+QGwjoyZk8F/NwkSuxS5CO4fC+vVOAtlE7f2s3+S0ucZ2P9NnYIGlcpVJV4/mDEKABIJoXC6mKe5blYRyHLR5auZEPt97/5mrlH70ykn/2bCZ7wGZXxwxlyGwjGeDGGSN0ttCUnHfDt+N/1NbQbXYP23LncPk6bzPCM8bR/GRgs3zLD2bzZxpprpvOb8kq+BOyWTdvYat46z7SNEcGy8sKVICyPbeQGFVBShkrs50OjrKpDWTAQYscNCvaUZrvvXTTRKitNkulCyI5VNtFkexi3YCo9qB6OW4ec1IRqraVlmuYcwVgnOXAHgIpsvGN9fnRlQtr/b7vIZlUutwm63zL1LDMXH870nUboLjcWF/T7eGVX77rO5njCha10BfL6SuUNk6QGtEGM/GX1Jxuq1OIsy9tbHfPYNtfou0iEF1Oo8XKG5HRsgezYuMq3gRJG99QhSIjJ7EBKH0AY4qTF/7Hj/mwl1eubT/QZnursv12x7ceul0UUFalXLn+oRxYee2nu9bOXdNGW3Ah6JF/zhBakHAMGV4+xrnQtdre7RZpw8a3mmkRbP+wuOX1UV+KxcYzln6+CwkqJxPFyO/UA9TJKd4AKww5JQQPYsZcjy//5K37r73y4ysrH8qFyx+gCr791L7ZXoZZgLaq6xSemSzSDI9oL2GeniC9+d45SdcunX/6jf/8P5lSgUTl+AKcFycS4HlLVCHWkMdxffZm1QqU6OyzUj7/L4Zyi+W2Ydpk4oDOsBjhvcQJzFIoWdWBd0zBV9iRnEL/u9hnC3dXO5TJgU3d/cWD//Dpt/Z/6cUy6S1kaUdzTJ/WQqAK9qrV+fjQtV/87Auv/YcfAkUM3NLhDIRBTBSPxI6HeLFjVJpzsfkYTnyMcK6UGqp64tlV+ed/WJjtohC5E1gMjr/5+JL05mMJAFZ3MUGMGEnZjySey/CmUtgxzqjoaP+75SQBABUBsBr7QajXvHvwiyeuLD18lLGtYW+UTre0vuKi6V4+nvOtTGamfu/9cxPTGR+6+BO2eT5UAwOdf+j4dsYrNx669OPXsnwFeRwDiYJURcFQ9wlWEsFmVUMI0UgSSFsR5JKFOegEzEunL//mz2lo1aRuh8dtJzrpo55+ZF2C95Z8HzxUkZWxGv9zQ4hxppkGYURdayZSfBaC5Zba8xkY/uOXX37j+PJfvKO/xwlOQRD7wmzg5+pI2yxnggYGmaidaYN5/m5Ve+B4jJECbA/pgDqawPe1W8MJAdD7eoTj4CB2rH3xAYx7VXJCQa5zeDhZKiBZpa1yhZw7OGyk55Zu47Zg6ZfPnq3dN55el4W9S2KuIi/Nr1ShPiCzpEqo6sAH0MxxM6SIRPkKw3tOeuKEI+c4fYUzxniS3YGhJhDDppSmhQ6Wq5w01SHPLZi0Y1qboJFj1TqNTjyx1BoY7KWZAsaEsbOcy4MVNjSMcp3DA/2UinY1IJAF0lAF2Hsti5y2ef+GnD59JyjuKrWki8477BcLMmci/FAk+RiDjxOdY5gXMRt8kJlMEAql2sPJ6XKcliKWWTqN64VzC9mF5yLO+ol8uyVnbZCBmKbGNwlURLt4qJZaSYp8ocEH6j549zGpB8ophSn8bDFIFylCTLCSSo18VftZYsaRa42RQi/1uh271+TRPSWTe/L979tb2Ssud5Ss6fJiIXOv9GVweUFMVWm2Uee9xx7yKNF8CMpmTNNiMDm5DAaQNm2UNvZ9nGGk3cG26bjRhhICZn1+nH1gWhgMTGurfMLR54SbY1Yll6GW1TDGqzypDKfL1TXTKni2wM8c47S5KPH7tiw0RuztLGX3/nVZRxZUIFWnTrk7IXD3ktWWt/7lFzqS2QVZL0JZzGLwL6gTJIthECcSJEDNIbqt8khniRFNTtKpSj+9l7pkOXUYoJU6iTLQZJxrSlRtKU0ztOLT1wSOuSjacU0JGQ+Yn4NolTcRrJqEE/s6k5VhGIqA/FMbaddP/eVMaouKMT3fpSur8tXfrmTHV3EfiNXp20sVl7uWLPVXHMQf/GQs3/ptK+nVeRnwD00IE+KQMmZWYU8YOqR44y4FQHhgGlP9cwVseINk5aNmlmnTY185NsYyBSTqLLR7mNXixPi2piar4LO0HihKV83iO/NRSBWx7p/G/k8WlDkAiq1XOYRiKdSOLNWy3x37nV4hyck1+MZaXvqeldNfFXn9lLsTUPcEVoOY3545U8qB5TX54M1FWd3w1JbzVdcAQIYfzcdM2cCasTOQXmgcoNoba+cz5wDVJTsMIWGlLyJUoZ9IFTTSqxl+zpmmHygZnlotZTGTqSU4thBwQnloNelI28X5dZzpGoEwM03MCeQD1sXYGJYw1Kkky0AZ7sMDn7eoxCMO9rk3MXenYPfEEpsavX8DHxys5Tv/67oU8xsoJSJVEMJ1c0I3RDwHdwHlVz7DeX/hHEhgBLcdggQ6zv8DB0JBLohQbo4GOE63ja3BsQ72ccxgP8LWNls9x/0eInTuQzYsvq/3xj0lBFXAWpCx49wQWYMoLaS7BEMfjyWPr8tKDf51ysKgM1dl5bvf9WzuToHqdPz3vkzs19mzAVYax1Cu/WAe+ZBE50/zTxpw4rj+yaex0XnU2kjGTueBLzvlOe6Bbadpw7TNPcegCjw25t+eQVCrjWbs8OE5fHap1X4MBsD6p1bgKYMmGObcZ34eQsL497eiXSi+bAxkcb0vB77o5KUbFh7PASQ/kO98567U72OBtQkwLt/FfV77R0ZOjVPZ2OhKWsfaJ1DTqMPzxb1QOC8nyyKpmfZJfTsTe+xpz9pCLv+6kbboc9oyAMyhcmnm/wqS/5MpGgOAEPtUNwvBY9ixRfYZMGnXrzVhGYI/kZXbZCAXv13IqdfxOz8S7esAQP6ZIVWfFVjbgtY+xNp/T2EverIbErbaN6jjhwpe++ei+qACrMHW8KSdORQ5+00pCpmNOQDLDAfP9ws/kMW5QANdfo7YvknWjc9zqEINkVZhNWbQZDnpVK5CrU/BJLz2TSffPz3NIDRE914Aml0+NlhblxnwDLwmCGvZkYOgEPz7NTqRqu0AZhPFhv9jZJQGgpg1f6aOiVJWl2qASkDYAlQ2nXglP9MLx/5PpPCvMeRQz8MvwFYha3B9VwmJkYmqnTnTFHjkIwE0u3ziYG236B/T2P/XkWT7EghIzCke/u8BZpzwOX0G7bIb+NanwcD/VUn2G+ifqWt6MZBUlP074XlzerRcFo4W5ve/pzkgLzTmY4Nyq+UzAWvrouCdej2U/iEANwi0z5xzh8rE98+z026huThllXyEeuYSclAovS2ug2ucru7V3vzt8hkv/xcO4yl3QkZ/XQAAAABJRU5ErkJggg==";
2167
2559
 
2560
+ var x3Edger = "data:image/png;base64,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";
2561
+
2168
2562
  var x3Mower = "data:image/png;base64,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";
2169
2563
 
2170
2564
  var x3NoPosition = "data:image/png;base64,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";
2171
2565
 
2172
2566
  var x3Disabled = "data:image/png;base64,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";
2173
2567
 
2174
- function getMowerImageByModal(mowerModal) {
2568
+ function getMowerImageByModal(mowerModal, hasEdger) {
2175
2569
  if (mowerModal.includes('i')) {
2176
2570
  return iMower;
2177
2571
  }
@@ -2179,7 +2573,7 @@ function getMowerImageByModal(mowerModal) {
2179
2573
  return hMower;
2180
2574
  }
2181
2575
  else if (mowerModal.includes('x3')) {
2182
- return x3Mower;
2576
+ return hasEdger ? x3Edger : x3Mower;
2183
2577
  }
2184
2578
  return iMower;
2185
2579
  }
@@ -2207,12 +2601,12 @@ function getNoPositionMowerImageByModal(mowerModal) {
2207
2601
  }
2208
2602
  return iNoPosition;
2209
2603
  }
2210
- function getMowerImage(positonConfig, modelType) {
2604
+ function getMowerImage(positonConfig, modelType, hasEdger) {
2211
2605
  if (!positonConfig)
2212
2606
  return '';
2213
2607
  const model = modelType?.toLowerCase() || 'i';
2214
2608
  const state = positonConfig.vehicleState;
2215
- const mowerImage = getMowerImageByModal(model);
2609
+ const mowerImage = getMowerImageByModal(model, hasEdger);
2216
2610
  const disabledImage = getDisabledMowerImageByModal(model);
2217
2611
  const noPositionImage = getNoPositionMowerImageByModal(model);
2218
2612
  const positonOutOfRange = isOutOfRange(positonConfig);
@@ -4694,8 +5088,8 @@ var PathSegmentType;
4694
5088
  */
4695
5089
  var UnitsType;
4696
5090
  (function (UnitsType) {
4697
- UnitsType["Metric"] = "metric";
4698
- UnitsType["Imperial"] = "imperial";
5091
+ UnitsType["Metric"] = "Metric";
5092
+ UnitsType["Imperial"] = "Imperial";
4699
5093
  })(UnitsType || (UnitsType = {}));
4700
5094
  /**
4701
5095
  * 面积单位类型枚举
@@ -4982,6 +5376,12 @@ class BoundaryBorderLayer extends BaseLayer {
4982
5376
  this.mowingBoundarys = mowingBoundarys;
4983
5377
  }
4984
5378
  }
5379
+ /**
5380
+ * 获取当前割草任务的边界
5381
+ */
5382
+ getMowingBoundarys() {
5383
+ return this.mowingBoundarys;
5384
+ }
4985
5385
  /**
4986
5386
  * SVG渲染方法
4987
5387
  */
@@ -4990,13 +5390,31 @@ class BoundaryBorderLayer extends BaseLayer {
4990
5390
  return;
4991
5391
  }
4992
5392
  this.scale = scale;
4993
- console.log('draw boundary border->', this.elements, this.mowingBoundarys);
4994
- // 只渲染边界边框类型的元素
5393
+ // 将元素分为两组:非割草边界和割草边界
5394
+ const nonMowingElements = [];
5395
+ const mowingElements = [];
5396
+ // 只处理边界边框类型的元素
4995
5397
  for (const element of this.elements) {
4996
5398
  if (element.type === 'boundary_border') {
4997
- this.renderBoundaryBorder(svgGroup, element);
5399
+ const { originalData } = element;
5400
+ const { id } = originalData || {};
5401
+ // 检查是否为割草边界
5402
+ if (this.mowingBoundarys.includes(Number(id))) {
5403
+ mowingElements.push(element);
5404
+ }
5405
+ else {
5406
+ nonMowingElements.push(element);
5407
+ }
4998
5408
  }
4999
5409
  }
5410
+ // 先渲染非割草边界
5411
+ for (const element of nonMowingElements) {
5412
+ this.renderBoundaryBorder(svgGroup, element);
5413
+ }
5414
+ // 再渲染割草边界(放在最后)
5415
+ for (const element of mowingElements) {
5416
+ this.renderBoundaryBorder(svgGroup, element);
5417
+ }
5000
5418
  }
5001
5419
  /**
5002
5420
  * 渲染边界边框
@@ -6065,7 +6483,7 @@ class PathDataProcessor {
6065
6483
  * 专门处理边界标签的创建、定位和管理
6066
6484
  */
6067
6485
  class BoundaryLabelsManager {
6068
- constructor(svgView, boundaryData) {
6486
+ constructor(svgView, boundaryData, { unitType, language }) {
6069
6487
  this.container = null;
6070
6488
  this.overlayDiv = null;
6071
6489
  this.globalClickHandler = null;
@@ -6076,6 +6494,8 @@ class BoundaryLabelsManager {
6076
6494
  this.svgView = svgView;
6077
6495
  this.boundaryData = boundaryData;
6078
6496
  this.initializeContainer();
6497
+ this.unitType = unitType;
6498
+ this.language = language;
6079
6499
  }
6080
6500
  /**
6081
6501
  * 初始化容器
@@ -6141,7 +6561,7 @@ class BoundaryLabelsManager {
6141
6561
  labelDiv.setAttribute('data-boundary-id', boundary.id.toString());
6142
6562
  // 样式设置
6143
6563
  labelDiv.style.position = 'absolute';
6144
- labelDiv.style.backgroundColor = 'rgba(30, 30, 31, 0.3)';
6564
+ labelDiv.style.backgroundColor = 'rgba(30, 30, 31, 0.6)';
6145
6565
  labelDiv.style.color = 'rgba(255, 255, 255, 1)';
6146
6566
  labelDiv.style.padding = '6px';
6147
6567
  labelDiv.style.borderRadius = '12px';
@@ -6158,7 +6578,7 @@ class BoundaryLabelsManager {
6158
6578
  labelDiv.style.zIndex = BoundaryLabelsManager.Z_INDEX.DEFAULT.toString();
6159
6579
  // 计算进度
6160
6580
  const progress = boundary.finishedArea && boundary.area
6161
- ? `${Math.round((boundary.finishedArea / boundary.area) * 100)}%`
6581
+ ? `${Math.floor((boundary.finishedArea / boundary.area) * 100)}%`
6162
6582
  : '0%';
6163
6583
  // 基础内容(始终显示)
6164
6584
  const baseContent = document.createElement('div');
@@ -6175,12 +6595,15 @@ class BoundaryLabelsManager {
6175
6595
  this.currentExpandedBoundaryId === boundary.id ? 'block' : 'none';
6176
6596
  extendedContent.style.borderTop = '1px solid rgba(255,255,255,0.2)';
6177
6597
  extendedContent.style.paddingTop = '6px';
6598
+ const boundaryLayer = this.svgView.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
6599
+ const mowingBoundarys = boundaryLayer.getMowingBoundarys();
6178
6600
  // 面积信息
6179
- const totalArea = convertAreaByUnits(boundary.area || 0, 'metric');
6180
- const finishedArea = convertAreaByUnits(boundary.finishedArea || 0, 'metric');
6601
+ const totalArea = convertAreaByUnits(boundary.area || 0, this.unitType);
6602
+ const finishedArea = convertAreaByUnits(boundary.finishedArea || 0, this.unitType);
6181
6603
  const coverageText = `Coverage: ${finishedArea.value}/${totalArea.value}`;
6604
+ const isMowing = mowingBoundarys.includes(boundary.id);
6182
6605
  // 日期信息
6183
- const dateText = formatBoundaryDateText(boundary.endTime || 0);
6606
+ const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0);
6184
6607
  const covertHtml = `<div style="margin-bottom: 3px; font-weight: bold;">${coverageText}</div>`;
6185
6608
  const dateHtml = `<div>${dateText}</div>`;
6186
6609
  extendedContent.innerHTML = boundary.finishedArea > 0 ? `${covertHtml}${dateHtml}` : covertHtml;
@@ -6298,7 +6721,6 @@ class BoundaryLabelsManager {
6298
6721
  // 计算边界中心点的地图坐标
6299
6722
  const mapCenter = this.calculatePolygonCentroid(boundary.points);
6300
6723
  if (!mapCenter) {
6301
- console.warn(`BoundaryLabelsManager: 无法计算边界 ${boundary.name} (ID: ${boundary.id}) 的中心点`);
6302
6724
  return;
6303
6725
  }
6304
6726
  // 直接使用预计算的数据进行坐标转换
@@ -6383,7 +6805,6 @@ class BoundaryLabelsManager {
6383
6805
  area = area / 2;
6384
6806
  // 如果面积为0,回退到简单的平均值计算
6385
6807
  if (Math.abs(area) < 1e-10) {
6386
- console.warn('BoundaryLabelsManager: 多边形面积为0,使用平均值计算重心');
6387
6808
  return this.calculateAverageCenter(validPoints);
6388
6809
  }
6389
6810
  centroidX = centroidX / (6 * area);
@@ -7294,6 +7715,10 @@ class MowerPositionManager {
7294
7715
  getElement() {
7295
7716
  return this.container;
7296
7717
  }
7718
+ //
7719
+ setEdger(edger) {
7720
+ this.hasEdger = edger;
7721
+ }
7297
7722
  /**
7298
7723
  * 根据最后一次有效的位置更新数据
7299
7724
  */
@@ -7317,7 +7742,6 @@ class MowerPositionManager {
7317
7742
  postureY = chargingPilesPositionConfig.postureY || 0;
7318
7743
  postureTheta = chargingPilesPositionConfig.postureTheta || 0;
7319
7744
  }
7320
- console.log('updatePositionByLastPosition->', postureX, postureY, postureTheta, chargingPilesPositionConfig);
7321
7745
  // 检查是否需要更新图片
7322
7746
  this.updateMowerImage(chargingPilesPositionConfig);
7323
7747
  // 立即更新位置
@@ -7334,7 +7758,6 @@ class MowerPositionManager {
7334
7758
  const postureX = positionConfig?.postureX || this.lastPosition?.x || 0;
7335
7759
  const postureY = positionConfig?.postureY || this.lastPosition?.y || 0;
7336
7760
  const postureTheta = positionConfig?.postureTheta || this.lastPosition?.rotation || 0;
7337
- console.log('updatePosition manager', JSON.stringify(this.currentPosition), this.currentPosition, !this.currentPosition, positionConfig, this.lastPosition, animationTime);
7338
7761
  // 停止当前动画(如果有)
7339
7762
  this.stopAnimation();
7340
7763
  // 第一个点
@@ -7344,7 +7767,6 @@ class MowerPositionManager {
7344
7767
  y: postureY,
7345
7768
  rotation: postureTheta,
7346
7769
  };
7347
- console.log('updatePosition first->', this.currentPosition);
7348
7770
  this.setElementPosition(this.currentPosition.x, this.currentPosition.y, this.currentPosition.rotation);
7349
7771
  return;
7350
7772
  }
@@ -7366,7 +7788,7 @@ class MowerPositionManager {
7366
7788
  const imgElement = this.mowerElement.querySelector('img');
7367
7789
  if (!imgElement)
7368
7790
  return;
7369
- const imageSrc = getMowerImage(positonConfig, this.modelType);
7791
+ const imageSrc = getMowerImage(positonConfig, this.modelType, this.hasEdger);
7370
7792
  if (imageSrc) {
7371
7793
  imgElement.src = imageSrc;
7372
7794
  imgElement.style.display = 'block';
@@ -7453,12 +7875,10 @@ class MowerPositionManager {
7453
7875
  y: this.onlyUpdateTheta ? 0 : this.targetPosition.y - this.startPosition.y,
7454
7876
  rotation: radNormalize(targetTheta - startTheta),
7455
7877
  };
7456
- console.log('startAnimationToPosition-->', this.deltaPosition, this.onlyUpdateTheta, this.targetPosition, this.startPosition);
7457
7878
  // 开始动画循环
7458
7879
  this.animateStep();
7459
7880
  }
7460
7881
  forceUpdatePosition() {
7461
- console.log('forceUpdatePosition-->', this.currentPosition, this.targetPosition, this.startPosition);
7462
7882
  this.animateStep();
7463
7883
  }
7464
7884
  /**
@@ -7599,15 +8019,40 @@ function throttleAdvanced(func, delay, options = { leading: true, trailing: true
7599
8019
  }
7600
8020
  };
7601
8021
  }
8022
+ /**
8023
+ * 检测当前设备是否为移动设备
8024
+ * @returns {boolean} 如果是移动设备返回true,否则返回false
8025
+ */
8026
+ function isMobileDevice() {
8027
+ // 确保在浏览器环境中运行
8028
+ if (typeof window === 'undefined' || typeof navigator === 'undefined') {
8029
+ return false;
8030
+ }
8031
+ // 检查用户代理字符串
8032
+ const userAgent = navigator.userAgent.toLowerCase();
8033
+ const mobileKeywords = [
8034
+ 'android', 'webos', 'iphone', 'ipad', 'ipod',
8035
+ 'blackberry', 'windows phone', 'mobile'
8036
+ ];
8037
+ const isMobileUserAgent = mobileKeywords.some(keyword => userAgent.includes(keyword));
8038
+ // 检查触摸屏支持
8039
+ const hasTouchScreen = 'ontouchstart' in window ||
8040
+ (navigator.maxTouchPoints && navigator.maxTouchPoints > 0);
8041
+ // 检查屏幕尺寸(移动设备通常屏幕较小)
8042
+ const isSmallScreen = window.innerWidth <= 768;
8043
+ // 综合判断:用户代理包含移动设备关键词,或者有触摸屏且屏幕较小
8044
+ return isMobileUserAgent || (hasTouchScreen && isSmallScreen);
8045
+ }
7602
8046
 
7603
8047
  // Google Maps 叠加层类 - 带编辑功能
7604
8048
  class MowerMapOverlay {
7605
- constructor(bounds, mapData, partitionBoundary, mowerPositionConfig, modelType, pathData, isEditMode = false, mapConfig = {}, antennaConfig = {}, mowPartitionData = null, defaultTransform, onMapLoad, onPathLoad, dragCallbacks) {
8049
+ constructor(bounds, mapData, partitionBoundary, mowerPositionConfig, modelType, pathData, isEditMode = false, unitType = UnitsType.Imperial, language = 'en', mapConfig = {}, antennaConfig = {}, mowPartitionData = null, defaultTransform, onMapLoad, onPathLoad, dragCallbacks) {
7606
8050
  this.div = null;
7607
8051
  this.svgMapView = null;
7608
8052
  this.offscreenContainer = null;
7609
8053
  this.overlayView = null;
7610
8054
  this.defaultTransform = { x: 0, y: 0, rotation: 0 };
8055
+ this.hasEdger = false;
7611
8056
  // boundary数据
7612
8057
  this.boundaryData = [];
7613
8058
  // 边界标签管理器
@@ -7650,6 +8095,8 @@ class MowerMapOverlay {
7650
8095
  this.partitionBoundary = partitionBoundary;
7651
8096
  this.pathData = pathData;
7652
8097
  this.isEditMode = isEditMode;
8098
+ this.unitType = unitType;
8099
+ this.language = language;
7653
8100
  this.mapConfig = mapConfig;
7654
8101
  this.antennaConfig = antennaConfig;
7655
8102
  this.onMapLoad = onMapLoad;
@@ -7696,7 +8143,6 @@ class MowerMapOverlay {
7696
8143
  this.isUserAnimation = animationTime > 0;
7697
8144
  // 更新割草机位置配置
7698
8145
  this.mowerPositionConfig = positionConfig;
7699
- console.log('updatePosition overlay', positionConfig);
7700
8146
  // 更新割草机位置管理器
7701
8147
  if (this.mowerPositionManager) {
7702
8148
  this.mowerPositionManager.updatePosition(positionConfig, animationTime);
@@ -7714,6 +8160,12 @@ class MowerMapOverlay {
7714
8160
  this.overlayView.setMap(map);
7715
8161
  }
7716
8162
  }
8163
+ setEdger(edger) {
8164
+ this.hasEdger = edger;
8165
+ if (this.mowerPositionManager) {
8166
+ this.mowerPositionManager.setEdger(edger);
8167
+ }
8168
+ }
7717
8169
  getMap() {
7718
8170
  return this.overlayView ? this.overlayView.getMap() : null;
7719
8171
  }
@@ -7741,7 +8193,6 @@ class MowerMapOverlay {
7741
8193
  this.svgMapView?.renderLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
7742
8194
  }
7743
8195
  onAdd() {
7744
- console.log('onAdd');
7745
8196
  // 创建包含SVG的div
7746
8197
  this.div = document.createElement('div');
7747
8198
  this.div.style.borderStyle = 'none';
@@ -7809,7 +8260,10 @@ class MowerMapOverlay {
7809
8260
  if (!this.div || !this.svgMapView)
7810
8261
  return;
7811
8262
  // 创建边界标签管理器
7812
- this.boundaryLabelsManager = new BoundaryLabelsManager(this.svgMapView, this.boundaryData);
8263
+ this.boundaryLabelsManager = new BoundaryLabelsManager(this.svgMapView, this.boundaryData, {
8264
+ unitType: this.unitType,
8265
+ language: this.language,
8266
+ });
7813
8267
  // 设置叠加层div引用
7814
8268
  this.boundaryLabelsManager.setOverlayDiv(this.div);
7815
8269
  // 添加所有边界标签
@@ -7853,11 +8307,10 @@ class MowerMapOverlay {
7853
8307
  if (!this.div || !this.svgMapView)
7854
8308
  return;
7855
8309
  // 创建割草机位置管理器,传入动画完成回调
7856
- this.mowerPositionManager = new MowerPositionManager(this.svgMapView, this.mowerPositionConfig, this.modelType, this.div, () => {
7857
- console.log('动画完成');
7858
- }, this.updatePathDataByMowingPositionThrottled.bind(this));
8310
+ this.mowerPositionManager = new MowerPositionManager(this.svgMapView, this.mowerPositionConfig, this.modelType, this.div, () => { }, this.updatePathDataByMowingPositionThrottled.bind(this));
7859
8311
  // 设置叠加层div引用
7860
8312
  this.mowerPositionManager.setOverlayDiv(this.div);
8313
+ this.mowerPositionManager.setEdger(this.hasEdger);
7861
8314
  // 获取容器并添加到主div
7862
8315
  const container = this.mowerPositionManager.getElement();
7863
8316
  if (container) {
@@ -7937,7 +8390,7 @@ class MowerMapOverlay {
7937
8390
  this.rotateHandle.style.pointerEvents = 'auto';
7938
8391
  this.rotateHandle.innerHTML = DEFAULT_ROTATE_ICON;
7939
8392
  this.editContainer.appendChild(this.rotateHandle);
7940
- // 创建拖拽手柄(左上角)
8393
+ // 创建拖拽手柄(左下角)- 仅在移动设备上显示
7941
8394
  this.dragHandle = document.createElement('div');
7942
8395
  this.dragHandle.style.position = 'absolute';
7943
8396
  this.dragHandle.style.bottom = '-20px';
@@ -7948,6 +8401,10 @@ class MowerMapOverlay {
7948
8401
  this.dragHandle.style.zIndex = EDIT_STYLES.Z_INDEX.HANDLE;
7949
8402
  this.dragHandle.style.pointerEvents = 'auto';
7950
8403
  this.dragHandle.innerHTML = DEFAULT_DRAG_ICON;
8404
+ // 在PC设备上隐藏拖拽手柄
8405
+ if (!isMobileDevice()) {
8406
+ this.dragHandle.style.display = 'none';
8407
+ }
7951
8408
  this.editContainer.appendChild(this.dragHandle);
7952
8409
  // 将编辑容器添加到主div
7953
8410
  this.div.appendChild(this.editContainer);
@@ -7989,7 +8446,6 @@ class MowerMapOverlay {
7989
8446
  this.boundaryLabelsManager.collapseAllLabels();
7990
8447
  }
7991
8448
  this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
7992
- console.log('开始旋转操作');
7993
8449
  });
7994
8450
  // 旋转手柄的触摸事件
7995
8451
  this.rotateHandle.addEventListener('touchstart', (e) => {
@@ -8005,39 +8461,41 @@ class MowerMapOverlay {
8005
8461
  this.boundaryLabelsManager.collapseAllLabels();
8006
8462
  }
8007
8463
  this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8008
- console.log('开始旋转操作(触摸)');
8009
- }, { passive: false });
8010
- // 拖拽手柄的鼠标事件
8011
- this.dragHandle.addEventListener('mousedown', (e) => {
8012
- e.preventDefault();
8013
- e.stopPropagation();
8014
- e.stopImmediatePropagation();
8015
- this.isDragging = true;
8016
- this.startPos = { x: e.clientX, y: e.clientY };
8017
- this.dragHandle.style.cursor = 'grabbing';
8018
- // 开始编辑时关闭所有展开的边界标签
8019
- if (this.boundaryLabelsManager) {
8020
- this.boundaryLabelsManager.collapseAllLabels();
8021
- }
8022
- this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8023
- console.log('开始拖动操作(通过手柄)');
8024
- });
8025
- // 拖拽手柄的触摸事件
8026
- this.dragHandle.addEventListener('touchstart', (e) => {
8027
- e.preventDefault();
8028
- e.stopPropagation();
8029
- e.stopImmediatePropagation();
8030
- this.isDragging = true;
8031
- const touch = e.touches[0];
8032
- this.startPos = { x: touch.clientX, y: touch.clientY };
8033
- this.dragHandle.style.cursor = 'grabbing';
8034
- this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8035
- console.log('开始拖动操作(通过手柄,触摸)');
8036
8464
  }, { passive: false });
8465
+ // 拖拽手柄的鼠标事件 - 仅在移动设备上启用
8466
+ if (isMobileDevice()) {
8467
+ this.dragHandle.addEventListener('mousedown', (e) => {
8468
+ e.preventDefault();
8469
+ e.stopPropagation();
8470
+ e.stopImmediatePropagation();
8471
+ this.isDragging = true;
8472
+ this.startPos = { x: e.clientX, y: e.clientY };
8473
+ this.dragHandle.style.cursor = 'grabbing';
8474
+ // 开始编辑时关闭所有展开的边界标签
8475
+ if (this.boundaryLabelsManager) {
8476
+ this.boundaryLabelsManager.collapseAllLabels();
8477
+ }
8478
+ this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8479
+ });
8480
+ // 拖拽手柄的触摸事件
8481
+ this.dragHandle.addEventListener('touchstart', (e) => {
8482
+ e.preventDefault();
8483
+ e.stopPropagation();
8484
+ e.stopImmediatePropagation();
8485
+ this.isDragging = true;
8486
+ const touch = e.touches[0];
8487
+ this.startPos = { x: touch.clientX, y: touch.clientY };
8488
+ this.dragHandle.style.cursor = 'grabbing';
8489
+ this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8490
+ }, { passive: false });
8491
+ }
8037
8492
  // 编辑容器的鼠标事件(整个区域拖拽)
8038
8493
  this.editContainer.addEventListener('mousedown', (e) => {
8039
- console.log('开始拖动操作(整个叠加层)');
8040
- if (e.target === this.dragHandle || e.target === this.rotateHandle) {
8494
+ // 在移动设备上,检查是否点击了拖拽手柄或旋转手柄
8495
+ // 在PC设备上,只检查旋转手柄(拖拽手柄已隐藏)
8496
+ const isDragHandleClick = isMobileDevice() && e.target === this.dragHandle;
8497
+ const isRotateHandleClick = e.target === this.rotateHandle;
8498
+ if (isDragHandleClick || isRotateHandleClick) {
8041
8499
  return;
8042
8500
  }
8043
8501
  e.preventDefault();
@@ -8054,8 +8512,11 @@ class MowerMapOverlay {
8054
8512
  });
8055
8513
  // 编辑容器的触摸事件(整个区域拖拽)
8056
8514
  this.editContainer.addEventListener('touchstart', (e) => {
8057
- console.log('开始拖动操作(整个叠加层,触摸)');
8058
- if (e.target === this.dragHandle || e.target === this.rotateHandle) {
8515
+ // 在移动设备上,检查是否点击了拖拽手柄或旋转手柄
8516
+ // 在PC设备上,只检查旋转手柄(拖拽手柄已隐藏)
8517
+ const isDragHandleClick = isMobileDevice() && e.target === this.dragHandle;
8518
+ const isRotateHandleClick = e.target === this.rotateHandle;
8519
+ if (isDragHandleClick || isRotateHandleClick) {
8059
8520
  return;
8060
8521
  }
8061
8522
  e.preventDefault();
@@ -8115,7 +8576,6 @@ class MowerMapOverlay {
8115
8576
  e.preventDefault();
8116
8577
  e.stopPropagation();
8117
8578
  e.stopImmediatePropagation();
8118
- console.log('结束编辑操作');
8119
8579
  }
8120
8580
  // 如果是拖拽结束,将像素偏移量转换为地理坐标偏移量
8121
8581
  if (this.isDragging) {
@@ -8137,7 +8597,6 @@ class MowerMapOverlay {
8137
8597
  e.preventDefault();
8138
8598
  e.stopPropagation();
8139
8599
  e.stopImmediatePropagation();
8140
- console.log('结束编辑操作(触摸)');
8141
8600
  }
8142
8601
  // 如果是拖拽结束,将像素偏移量转换为地理坐标偏移量
8143
8602
  if (this.isDragging) {
@@ -8249,7 +8708,6 @@ class MowerMapOverlay {
8249
8708
  this.div.style.transform = transform;
8250
8709
  // 更新鼠标起始位置为当前位置,为下次计算做准备
8251
8710
  this.startPos = { x: mouseCurrentX, y: mouseCurrentY };
8252
- console.log('旋转角度:', this.currentRotation, '角度增量:', angleDifferenceDegrees);
8253
8711
  }
8254
8712
  // 将像素偏移量转换为地理坐标偏移量
8255
8713
  convertPixelOffsetToLatLng() {
@@ -8279,14 +8737,6 @@ class MowerMapOverlay {
8279
8737
  // 累积更新地理坐标偏移量(不是直接赋值!)
8280
8738
  this.latLngOffset.lat += latOffset;
8281
8739
  this.latLngOffset.lng += lngOffset;
8282
- console.log('精确转换偏移量:', {
8283
- pixelOffset: this.tempPixelOffset,
8284
- centerLatLng: { lat: centerLatLng.lat(), lng: centerLatLng.lng() },
8285
- offsetLatLng: { lat: offsetLatLng.lat(), lng: offsetLatLng.lng() },
8286
- latOffset,
8287
- lngOffset,
8288
- newLatLngOffset: this.latLngOffset,
8289
- });
8290
8740
  // 重置临时像素偏移量
8291
8741
  this.tempPixelOffset = { x: 0, y: 0 };
8292
8742
  this.draw();
@@ -8324,8 +8774,6 @@ class MowerMapOverlay {
8324
8774
  editData: editData,
8325
8775
  timestamp: new Date().toISOString(),
8326
8776
  };
8327
- // 在这里可以添加保存逻辑,比如发送到服务器
8328
- console.log('保存编辑数据:', saveData);
8329
8777
  // 显示保存成功提示
8330
8778
  this.showSaveSuccess();
8331
8779
  return saveData;
@@ -8418,6 +8866,9 @@ class MowerMapOverlay {
8418
8866
  y: transform.y,
8419
8867
  rotation: transform.rotation,
8420
8868
  };
8869
+ // defaultTransform的x对应经度偏移量,y对应纬度偏移量
8870
+ this.latLngOffset.lng = this.defaultTransform.x;
8871
+ this.latLngOffset.lat = this.defaultTransform.y;
8421
8872
  this.setManagerRotation(this.currentRotation);
8422
8873
  this.draw();
8423
8874
  }
@@ -8457,7 +8908,6 @@ class MowerMapOverlay {
8457
8908
  if (this.pathData && this.svgMapView) {
8458
8909
  this.loadPathData(this.pathData, this.mowPartitionData);
8459
8910
  }
8460
- console.log('initializeSvgMapView');
8461
8911
  // 刷新绘制图层
8462
8912
  this.svgMapView.refresh();
8463
8913
  // 获取生成的SVG并添加到叠加层div中
@@ -8613,7 +9063,6 @@ class MowerMapOverlay {
8613
9063
  this.boundaryLabelsManager?.updateBoundaryData(boundaryData);
8614
9064
  }
8615
9065
  draw() {
8616
- console.log('ondraw');
8617
9066
  // 防御性检查:如果this.div为null,说明onAdd还没被调用,直接返回
8618
9067
  if (!this.div) {
8619
9068
  return;
@@ -8881,7 +9330,7 @@ const getValidGpsBounds = (mapData, rotation = 0) => {
8881
9330
  // 默认配置
8882
9331
  const defaultMapConfig = DEFAULT_STYLES;
8883
9332
  // 地图渲染器组件
8884
- const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJson, pathJson, realTimeData, antennaConfig, onMapLoad, onPathLoad, onError, className, style, googleMapInstance, isEditMode = false, dragCallbacks, defaultTransform, debug = false, }, ref) => {
9333
+ const MowerMapRenderer = React.forwardRef(({ edger = false, unitType = UnitsType.Imperial, language = 'en', mapConfig, modelType, mapRef, mapJson, pathJson, realTimeData, antennaConfig, onMapLoad, onPathLoad, onError, className, style, googleMapInstance, isEditMode = false, dragCallbacks, defaultTransform, debug = false, }, ref) => {
8885
9334
  const [elementCount, setElementCount] = React.useState(0);
8886
9335
  const [pathCount, setPathCount] = React.useState(0);
8887
9336
  const [currentError, setCurrentError] = React.useState(null);
@@ -8889,7 +9338,7 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
8889
9338
  // const mapRef = useMap();
8890
9339
  const [isGoogleMapsReady, setIsGoogleMapsReady] = React.useState(false);
8891
9340
  const [hasInitializedBounds, setHasInitializedBounds] = React.useState(false);
8892
- const { clearSubBoundaryBorder, clearObstacles } = useSubBoundaryBorderStore();
9341
+ const { clearSubBoundaryBorder, clearObstacles, clearSvgElements } = useSubBoundaryBorderStore();
8893
9342
  const currentProcessMowingStatusRef = React.useRef(false);
8894
9343
  const { updateProcessStateIsMowing, processStateIsMowing } = useProcessMowingState();
8895
9344
  const [mowPartitionData, setMowPartitionData] = React.useState(null);
@@ -8923,7 +9372,7 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
8923
9372
  postureX: 0,
8924
9373
  postureY: 0,
8925
9374
  postureTheta: 0,
8926
- vehicleState: RobotStatus.PARKED,
9375
+ vehicleState: RobotStatus.DISCONNECTED,
8927
9376
  };
8928
9377
  let currentPositionData;
8929
9378
  if (realTimeData.length === 1 && realTimeData[0].type === RealTimeDataType.LOCATION) {
@@ -8949,10 +9398,9 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
8949
9398
  lastPostureY: currentPositionData?.lastPostureY
8950
9399
  ? Number(currentPositionData.lastPostureY)
8951
9400
  : 0,
8952
- vehicleState: currentPositionData?.vehicleState || RobotStatus.CHARGING,
9401
+ vehicleState: currentPositionData?.vehicleState || RobotStatus.DISCONNECTED,
8953
9402
  };
8954
9403
  }, [realTimeData, modelType]);
8955
- console.log('mowerPositionData', mowerPositionData);
8956
9404
  // 处理错误
8957
9405
  const handleError = (error) => {
8958
9406
  setCurrentError(error);
@@ -8977,7 +9425,6 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
8977
9425
  const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
8978
9426
  new window.google.maps.LatLng(neLat, neLng) // 东北角
8979
9427
  );
8980
- console.log('fitBounds----->', googleBounds);
8981
9428
  mapRef.fitBounds(googleBounds);
8982
9429
  }, [mapJson, mapRef, defaultTransform]);
8983
9430
  // 初始化Google Maps叠加层
@@ -9018,9 +9465,8 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9018
9465
  overlayRef.current.setMap(null);
9019
9466
  overlayRef.current = null;
9020
9467
  }
9021
- console.log('initializeGoogleMapsOverlay', mowPartitionData);
9022
9468
  // 创建叠加层
9023
- const overlay = new MowerMapOverlay(googleBounds, mapJson, partitionBoundary, mowerPositionData, modelType, pathJson || {}, isEditMode, mergedMapConfig, mergedAntennaConfig, null, defaultTransform, (count) => {
9469
+ const overlay = new MowerMapOverlay(googleBounds, mapJson, partitionBoundary, mowerPositionData, modelType, pathJson || {}, isEditMode, unitType, language, mergedMapConfig, mergedAntennaConfig, null, defaultTransform, (count) => {
9024
9470
  setElementCount(count);
9025
9471
  onMapLoad?.(count);
9026
9472
  }, (count) => {
@@ -9030,6 +9476,7 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9030
9476
  // 设置地图
9031
9477
  overlay.setMap(mapInstance);
9032
9478
  overlayRef.current = overlay;
9479
+ overlay.setEdger(edger);
9033
9480
  // 只在首次初始化时自适应视图
9034
9481
  if (!hasInitializedBounds) {
9035
9482
  mapInstance.fitBounds(googleBounds);
@@ -9053,7 +9500,7 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9053
9500
  postureY: chargingPiles?.originalData.position[1],
9054
9501
  postureTheta: chargingPiles?.originalData.direction - Math.PI || 0,
9055
9502
  }, 0);
9056
- }, [mapJson]);
9503
+ }, [mapJson, mowerPositionData]);
9057
9504
  // 初始化效果
9058
9505
  React.useEffect(() => {
9059
9506
  initializeGoogleMapsOverlay();
@@ -9061,6 +9508,7 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9061
9508
  return () => {
9062
9509
  clearSubBoundaryBorder();
9063
9510
  clearObstacles();
9511
+ clearSvgElements();
9064
9512
  updateProcessStateIsMowing(false);
9065
9513
  currentProcessMowingStatusRef.current = false;
9066
9514
  if (overlayRef.current) {
@@ -9092,7 +9540,6 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9092
9540
  const isOffLine = mowerPositionData.vehicleState === RobotStatus.DISCONNECTED;
9093
9541
  const isInChargingPile = inChargingPiles.includes(mowerPositionData.vehicleState);
9094
9542
  // 如果在充电桩上,则直接更新位置到充电桩的位置
9095
- console.log('usefeect mowerPositionData----->', mowerPositionData);
9096
9543
  if (isInChargingPile) {
9097
9544
  overlayRef.current.updatePosition({
9098
9545
  ...mowerPositionData,
@@ -9125,7 +9572,6 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9125
9572
  }
9126
9573
  }
9127
9574
  else {
9128
- console.log('hook updatePosition----->', mowerPositionData);
9129
9575
  overlayRef.current.updatePosition(mowerPositionData, isStandby ? 0 : 2000);
9130
9576
  }
9131
9577
  }
@@ -9244,7 +9690,6 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9244
9690
  if (!realTimeData || realTimeData.length === 0 || !Array.isArray(realTimeData)) {
9245
9691
  return;
9246
9692
  }
9247
- console.log('usefeect realTimeData----->', realTimeData, mapJson, pathJson, overlayRef.current);
9248
9693
  let curMowPartitionData = mowPartitionData;
9249
9694
  // realtime中包含当前割草任务的数据,根据数据进行path路径和边界的高亮操作,
9250
9695
  const mowingPartition = realTimeData.find((item) => item.type === RealTimeDataType.PARTITION);
@@ -9252,9 +9697,8 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9252
9697
  setMowPartitionData(mowingPartition);
9253
9698
  curMowPartitionData = mowingPartition;
9254
9699
  }
9255
- const positionData = realTimeData?.find(item => item?.type === RealTimeDataType.LOCATION);
9256
- const statusData = realTimeData?.find(item => item?.type === RealTimeDataType.STATUS);
9257
- console.log('current->1', positionData, statusData);
9700
+ const positionData = realTimeData?.find((item) => item?.type === RealTimeDataType.LOCATION);
9701
+ const statusData = realTimeData?.find((item) => item?.type === RealTimeDataType.STATUS);
9258
9702
  if (statusData || positionData) {
9259
9703
  const currentStatus = statusData?.vehicleState || positionData?.vehicleState;
9260
9704
  // 车辆回桩不会回传最后的park的位置,所以根据实时数据的状态数据判断车辆回到桩上
@@ -9264,32 +9708,28 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9264
9708
  else if (currentStatus === RobotStatus.WORKING) {
9265
9709
  // 兜底收不到割草地块的实时数据,使用状态来兜底
9266
9710
  overlayRef.current.resetBorderLayerHighlight();
9267
- setMowPartitionData(null);
9268
- curMowPartitionData = null;
9711
+ setMowPartitionData({});
9712
+ curMowPartitionData = {};
9269
9713
  }
9270
- else if (currentStatus === RobotStatus.MOWING && (curMowPartitionData && !curMowPartitionData?.partitionIds)) {
9714
+ else if (currentStatus === RobotStatus.MOWING &&
9715
+ curMowPartitionData &&
9716
+ !curMowPartitionData?.partitionIds) {
9271
9717
  // 如果当前是割草状态,但是地块数据初始化过且不存在则认为是全局割草,则把所有地块都高亮
9272
- const allPartitionIds = mapJson?.sub_maps?.map(item => item?.id);
9273
- console.log('allPartitionIds->', allPartitionIds, mapJson);
9718
+ const allPartitionIds = mapJson?.sub_maps?.map((item) => item?.id);
9274
9719
  setMowPartitionData({
9275
- partitionIds: allPartitionIds
9720
+ partitionIds: allPartitionIds,
9276
9721
  });
9277
9722
  curMowPartitionData = {
9278
- partitionIds: allPartitionIds
9723
+ partitionIds: allPartitionIds,
9279
9724
  };
9280
9725
  }
9281
9726
  }
9282
- if (!mapJson ||
9283
- !pathJson ||
9284
- !overlayRef.current)
9727
+ if (!mapJson || !pathJson || !overlayRef.current)
9285
9728
  return;
9286
9729
  // 根据后端推送的实时数据,进行不同处理
9287
- // TODO:需要根据返回的数据,处理车辆的移动位置
9288
- console.log('realTimeData----->', realTimeData, curMowPartitionData);
9289
9730
  if (curMowPartitionData) {
9290
9731
  const isMowing = curMowPartitionData?.partitionIds && curMowPartitionData.partitionIds.length > 0;
9291
9732
  overlayRef.current.updateMowPartitionData(curMowPartitionData);
9292
- console.log('isMowing', isMowing, curMowPartitionData);
9293
9733
  if (!isMowing) {
9294
9734
  overlayRef.current.resetBorderLayerHighlight();
9295
9735
  }
@@ -9327,7 +9767,6 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9327
9767
  }
9328
9768
  }, [realTimeData, mapJson, pathJson]);
9329
9769
  React.useEffect(() => {
9330
- console.log('defaultTransform----->', defaultTransform, overlayRef.current, mapJson);
9331
9770
  if (!overlayRef.current || !defaultTransform)
9332
9771
  return;
9333
9772
  overlayRef.current?.setTransform(defaultTransform);
@@ -9342,6 +9781,11 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
9342
9781
  );
9343
9782
  mapRef.fitBounds(googleBounds);
9344
9783
  }, [defaultTransform]);
9784
+ React.useEffect(() => {
9785
+ if (!overlayRef || !overlayRef.current)
9786
+ return;
9787
+ overlayRef.current.setEdger(edger);
9788
+ }, [edger]);
9345
9789
  // 提供ref方法
9346
9790
  React.useImperativeHandle(ref, () => ({
9347
9791
  fitToView: () => {