@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.esm.js CHANGED
@@ -67,7 +67,8 @@ const DEFAULT_LINE_WIDTHS = {
67
67
  const DEFAULT_OPACITIES = {
68
68
  FULL: 1.0,
69
69
  HIGH: 0.7,
70
- MEDIUM: 0.6};
70
+ MEDIUM: 0.6,
71
+ DOODLE: 0.8};
71
72
  /**
72
73
  * 默认半径设置
73
74
  */
@@ -226,7 +227,6 @@ class SvgMapView {
226
227
  */
227
228
  removeLayer(layer) {
228
229
  const index = this.layers.indexOf(layer);
229
- console.log('removeLayer----->', index);
230
230
  if (index !== -1) {
231
231
  this.layers.splice(index, 1);
232
232
  this.refresh();
@@ -274,7 +274,6 @@ class SvgMapView {
274
274
  width: boundWidth + padding * 2,
275
275
  height: boundHeight + padding * 2,
276
276
  };
277
- console.log('viewbox->', this.viewBox);
278
277
  // 根据宽高比选择合适的preserveAspectRatio设置
279
278
  if (Math.abs(contentAspectRatio - containerAspectRatio) < 0.01) {
280
279
  // 宽高比接近,使用slice填满容器
@@ -284,7 +283,6 @@ class SvgMapView {
284
283
  // 宽高比差异较大,使用meet确保内容完全可见
285
284
  this.svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
286
285
  }
287
- console.log('fitToView');
288
286
  this.updateViewBox();
289
287
  }
290
288
  /**
@@ -355,7 +353,6 @@ class SvgMapView {
355
353
  * 绘制图层,不传参数则默认绘制所有图层
356
354
  */
357
355
  onDrawLayers(type) {
358
- console.log('onDrawLayers----->', type);
359
356
  if (type) {
360
357
  const layer = this.layers.find((layer) => layer.getType() === type);
361
358
  if (layer) {
@@ -432,7 +429,6 @@ class SvgMapView {
432
429
  refresh() {
433
430
  if (this.destroyed)
434
431
  return;
435
- console.log('refresh----->');
436
432
  this.render();
437
433
  }
438
434
  // ==================== 拖拽功能 ====================
@@ -1049,15 +1045,37 @@ class PathLayer extends BaseLayer {
1049
1045
  d += ' Z ';
1050
1046
  }
1051
1047
  });
1052
- // 3. svgElements(直接拼接path字符串,建议逆时针)
1053
- if (Array.isArray(svgElements)) {
1054
- svgElements.forEach((svgPath) => {
1055
- const svgPathString = svgPath?.metadata?.svg;
1056
- if (svgPathString && typeof svgPathString === 'string' && svgPathString.trim()) {
1057
- d += svgPathString + ' ';
1048
+ // 3. svgElements(解析 SVG 字符串并提取 path 数据)
1049
+ Object.values(svgElements).forEach((svgPath) => {
1050
+ const svgPathString = svgPath?.metadata?.svg;
1051
+ if (svgPathString && typeof svgPathString === 'string' && svgPathString.trim()) {
1052
+ // 处理转义字符
1053
+ const processedSvgString = svgPathString.replace(/\\n/g, '\n').replace(/\\"/g, '"');
1054
+ // 解析 SVG 字符串
1055
+ const parser = new DOMParser();
1056
+ const svgDoc = parser.parseFromString(processedSvgString, 'image/svg+xml');
1057
+ const svgElement = svgDoc.documentElement;
1058
+ if (svgElement.tagName === 'svg') {
1059
+ // 查找 path 元素
1060
+ const pathElement = svgElement.querySelector('path');
1061
+ if (pathElement) {
1062
+ const pathData = pathElement.getAttribute('d');
1063
+ if (pathData) {
1064
+ // 获取 SVG 元素的变换参数
1065
+ const centerCoords = svgPath.coordinates?.[0] || [0, 0];
1066
+ const center = [centerCoords[0], centerCoords[1]];
1067
+ const userScale = svgPath.metadata.scale || 1;
1068
+ const direction = svgPath.metadata?.direction || 0;
1069
+ const originalWidth = parseFloat(svgElement.getAttribute('width') || '76');
1070
+ const originalHeight = parseFloat(svgElement.getAttribute('height') || '68');
1071
+ // 应用变换到路径数据
1072
+ const transformedPathData = this.transformSvgPath(pathData, center, userScale, direction, originalWidth, originalHeight);
1073
+ d += transformedPathData + ' ';
1074
+ }
1075
+ }
1058
1076
  }
1059
- });
1060
- }
1077
+ }
1078
+ });
1061
1079
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1062
1080
  path.setAttribute('d', d);
1063
1081
  const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
@@ -1082,47 +1100,132 @@ class PathLayer extends BaseLayer {
1082
1100
  // 2. 创建一个组,应用 clipPath
1083
1101
  const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
1084
1102
  group.setAttribute('clip-path', `url(#${clipPathId})`);
1085
- group.setAttribute('opacity', '0.6'); // 统一透明度,防止叠加脏乱
1086
- // 3. 渲染所有路径
1103
+ group.setAttribute('opacity', '0.5'); // 统一透明度,防止叠加脏乱
1104
+ // 3. 优化渲染:按样式分组并合并路径
1105
+ this.renderOptimizedPaths(group);
1106
+ svgGroup.appendChild(group);
1107
+ }
1108
+ /**
1109
+ * 优化渲染:按样式分组并合并路径,减少 DOM 节点数量
1110
+ */
1111
+ renderOptimizedPaths(group) {
1112
+ // 按样式分组存储路径数据
1113
+ const styleGroups = new Map();
1114
+ // 收集所有路径数据并按样式分组
1087
1115
  for (const element of this.elements) {
1088
- const { id, elements } = element;
1116
+ // 类型断言:PathLayer 中的 elements 实际上是 PathElements 结构
1117
+ const pathElement = element;
1118
+ const { id, elements } = pathElement;
1089
1119
  this.boundaryPaths[id] = [];
1120
+ elements.forEach((pathElement) => {
1121
+ const { coordinates, style } = pathElement;
1122
+ if (coordinates.length < 2)
1123
+ return;
1124
+ // 生成样式键(用于分组)
1125
+ const styleKey = this.generateStyleKey(style);
1126
+ // 构建路径数据
1127
+ let pathData = `M ${coordinates[0][0]} ${coordinates[0][1]}`;
1128
+ for (let i = 1; i < coordinates.length; i++) {
1129
+ pathData += ` L ${coordinates[i][0]} ${coordinates[i][1]}`;
1130
+ }
1131
+ // 按样式分组存储
1132
+ if (!styleGroups.has(styleKey)) {
1133
+ styleGroups.set(styleKey, { pathData: [], elements: [] });
1134
+ }
1135
+ styleGroups.get(styleKey).pathData.push(pathData);
1136
+ styleGroups.get(styleKey).elements.push(pathElement);
1137
+ });
1138
+ }
1139
+ // 为每种样式创建一个合并的 path 元素
1140
+ styleGroups.forEach((groupData) => {
1141
+ const { pathData, elements } = groupData;
1142
+ if (pathData.length === 0)
1143
+ return;
1144
+ // 使用第一个元素的样式作为该组的样式
1145
+ const firstElement = elements[0];
1146
+ const style = firstElement.style;
1147
+ // 创建合并的 path 元素
1148
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1149
+ // 合并所有路径数据
1150
+ const mergedPathData = pathData.join(' ');
1151
+ path.setAttribute('d', mergedPathData);
1152
+ // 设置样式属性
1153
+ path.setAttribute('fill', 'none');
1154
+ path.setAttribute('stroke', style.lineColor || '#000000');
1155
+ path.setAttribute('mix-blend-mode', 'normal');
1156
+ const lineWidth = Math.max(style.lineWidth || 1, 0.5);
1157
+ path.setAttribute('stroke-width', lineWidth.toString());
1158
+ path.setAttribute('stroke-linecap', 'round');
1159
+ path.setAttribute('stroke-linejoin', 'round');
1160
+ path.classList.add('vector-path');
1161
+ // 将合并的 path 添加到组中
1162
+ group.appendChild(path);
1163
+ // 保存引用到 boundaryPaths 中(保持兼容性)
1090
1164
  elements.forEach((element) => {
1091
- this.renderPathToGroup(group, id, element);
1165
+ const { id } = element;
1166
+ if (!this.boundaryPaths[id]) {
1167
+ this.boundaryPaths[id] = [];
1168
+ }
1169
+ this.boundaryPaths[id].push(path);
1092
1170
  });
1171
+ });
1172
+ }
1173
+ /**
1174
+ * 变换 SVG 路径数据
1175
+ */
1176
+ transformSvgPath(pathData, center, scale, direction, originalWidth, originalHeight) {
1177
+ // 解析路径数据并应用变换
1178
+ const commands = pathData.match(/[MmLlHhVvCcSsQqTtAaZz][^MmLlHhVvCcSsQqTtAaZz]*/g) || [];
1179
+ let transformedCommands = [];
1180
+ for (const command of commands) {
1181
+ const type = command[0];
1182
+ const params = command
1183
+ .slice(1)
1184
+ .trim()
1185
+ .split(/[\s,]+/)
1186
+ .filter(Boolean)
1187
+ .map(Number);
1188
+ if (type === 'Z' || type === 'z') {
1189
+ // 闭合路径,不需要变换
1190
+ transformedCommands.push(command);
1191
+ continue;
1192
+ }
1193
+ // 处理坐标参数
1194
+ let transformedParams = [];
1195
+ for (let i = 0; i < params.length; i += 2) {
1196
+ if (i + 1 < params.length) {
1197
+ let x = params[i];
1198
+ let y = params[i + 1];
1199
+ // 应用变换:先平移到中心,然后缩放、旋转,最后平移到目标位置
1200
+ // 1. 平移到原点(相对于原始尺寸的中心)
1201
+ x -= originalWidth / 2;
1202
+ y -= originalHeight / 2;
1203
+ // 2. 应用缩放
1204
+ x *= scale;
1205
+ y *= scale;
1206
+ // 3. 应用旋转
1207
+ const cos = Math.cos(-direction);
1208
+ const sin = Math.sin(-direction);
1209
+ const newX = x * cos - y * sin;
1210
+ const newY = x * sin + y * cos;
1211
+ // 4. 平移到目标位置
1212
+ x = newX + center[0];
1213
+ y = newY + center[1];
1214
+ transformedParams.push(x, y);
1215
+ }
1216
+ }
1217
+ // 重建命令
1218
+ if (transformedParams.length > 0) {
1219
+ transformedCommands.push(type + transformedParams.join(' '));
1220
+ }
1093
1221
  }
1094
- svgGroup.appendChild(group);
1222
+ return transformedCommands.join(' ');
1095
1223
  }
1096
1224
  /**
1097
- * 渲染单个路径到指定的组中
1225
+ * 生成样式键,用于路径分组
1098
1226
  */
1099
- renderPathToGroup(group, id, element) {
1100
- const { coordinates, style } = element;
1101
- if (coordinates.length < 2)
1102
- return;
1103
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
1104
- // 构建路径数据
1105
- let pathData = `M ${coordinates[0][0]} ${coordinates[0][1]}`;
1106
- for (let i = 1; i < coordinates.length; i++) {
1107
- pathData += ` L ${coordinates[i][0]} ${coordinates[i][1]}`;
1108
- }
1109
- path.style.mixBlendMode = 'normal';
1110
- // 设置路径属性
1111
- path.setAttribute('d', pathData);
1112
- // 直接给fill的颜色设置透明度会导致path重叠的部分颜色叠加,所以使用fill填充实色,通过fill-opacity设置透明度
1113
- path.setAttribute('fill', 'none');
1114
- // path.setAttribute('fill-opacity', '0.4');
1115
- path.setAttribute('stroke', style.lineColor || '#000000');
1116
- path.setAttribute('mix-blend-mode', 'normal');
1117
- const lineWidth = Math.max(style.lineWidth || 1, 0.5);
1118
- path.setAttribute('stroke-width', lineWidth.toString());
1119
- path.setAttribute('stroke-linecap', 'round');
1120
- path.setAttribute('stroke-linejoin', 'round');
1121
- // 注意:这里不设置 opacity,因为透明度由父组控制
1122
- // path.setAttribute('vector-effect', 'non-scaling-stroke');
1123
- path.classList.add('vector-path');
1124
- this.boundaryPaths[id].push(path);
1125
- group.appendChild(path);
1227
+ generateStyleKey(style) {
1228
+ return `${style.lineColor || '#000000'}-${style.lineWidth || 1}-${style.opacity || 1}`;
1126
1229
  }
1127
1230
  }
1128
1231
 
@@ -1546,7 +1649,7 @@ const DOODLE_STYLES = {
1546
1649
  lineColor: '#ff5722',
1547
1650
  fillColor: '#ff9800', // 粉色半透明填充
1548
1651
  lineWidth: DEFAULT_LINE_WIDTHS.TIME_LIMIT_OBSTACLE,
1549
- opacity: DEFAULT_OPACITIES.HIGH,
1652
+ opacity: DEFAULT_OPACITIES.DOODLE,
1550
1653
  };
1551
1654
  const PATH_EDGE_STYLES = {
1552
1655
  lineWidth: DEFAULT_LINE_WIDTHS.PATH,
@@ -1588,13 +1691,13 @@ const DEFAULT_STYLES = {
1588
1691
  function convertPointsFormat(points) {
1589
1692
  if (!points || points.length === 0)
1590
1693
  return null;
1591
- return points.map(point => {
1694
+ return points.map((point) => {
1592
1695
  if (point.length >= 2) {
1593
1696
  // 对前两个元素应用缩放因子,保留其他元素
1594
1697
  return [
1595
1698
  point[0] * SCALE_FACTOR,
1596
1699
  -point[1] * SCALE_FACTOR, // Y轴翻转,与Python代码一致
1597
- ...point.slice(2) // 保留第三个及以后的元素
1700
+ ...point.slice(2), // 保留第三个及以后的元素
1598
1701
  ];
1599
1702
  }
1600
1703
  return point;
@@ -1609,7 +1712,7 @@ function convertPositionFormat(position) {
1609
1712
  return null;
1610
1713
  return {
1611
1714
  x: position[0] * SCALE_FACTOR,
1612
- y: -position[1] * SCALE_FACTOR // Y轴翻转
1715
+ y: -position[1] * SCALE_FACTOR, // Y轴翻转
1613
1716
  };
1614
1717
  }
1615
1718
  /**
@@ -1618,9 +1721,146 @@ function convertPositionFormat(position) {
1618
1721
  function convertCoordinate(x, y) {
1619
1722
  return {
1620
1723
  x: x * SCALE_FACTOR,
1621
- y: -y * SCALE_FACTOR // Y轴翻转
1724
+ y: -y * SCALE_FACTOR, // Y轴翻转
1622
1725
  };
1623
1726
  }
1727
+ /**
1728
+ * @param x x坐标
1729
+ * @param y y坐标
1730
+ * @param isAllowInBoundary 是否允许点在边界上的判断
1731
+ * @return ture-点在边界上即可视为在边界内,false-严格判断点在边界内
1732
+ */
1733
+ function isPointIn$1(x, y, pointList, isAllowInBoundary) {
1734
+ let count = 0;
1735
+ let size = pointList.length;
1736
+ let p1, p2, p3;
1737
+ for (let i = 0; i < size; i++) {
1738
+ p1 = pointList[i];
1739
+ p2 = pointList[(i + 1) % size];
1740
+ if (p1.y == null || p2.y == null || p1.x == null || p2.x == null) {
1741
+ continue;
1742
+ }
1743
+ if (p1.y === p2.y) {
1744
+ continue;
1745
+ }
1746
+ if (y > Math.min(p1.y, p2.y) && y < Math.max(p1.y, p2.y)) {
1747
+ const interX = ((y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y) + p1.x;
1748
+ if (interX >= x) {
1749
+ count++;
1750
+ }
1751
+ else if (interX == x) {
1752
+ return isAllowInBoundary;
1753
+ }
1754
+ }
1755
+ else {
1756
+ if (y == p2.y && x <= p2.x) {
1757
+ p3 = pointList[(i + 2) % size];
1758
+ if (y >= Math.min(p1.y, p3.y) && y <= Math.max(p1.y, p3.y)) {
1759
+ // 若当前点的y坐标位于 p1和p3组成的线段关于y轴的投影中,则记为该点的射线只穿过端点一次。
1760
+ ++count;
1761
+ }
1762
+ else {
1763
+ // 若当前点的y坐标不能包含在p1和p3组成的线段关于y轴的投影中,则点射线通过的两条线段组成了一个弯折的部分,
1764
+ // 此时我们记射线穿过该端点两次
1765
+ count += 2;
1766
+ }
1767
+ }
1768
+ }
1769
+ }
1770
+ return count % 2 == 1;
1771
+ }
1772
+ /**
1773
+ * 用于判断三个点的方向的辅助方法
1774
+ */
1775
+ function orientation(p, q, r) {
1776
+ const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
1777
+ if (val == 0)
1778
+ return 0; // colinear
1779
+ return val > 0 ? 1 : 2; // clock or counterclock wise
1780
+ }
1781
+ /**
1782
+ * 检查点q是否在线段pr上的辅助方法
1783
+ */
1784
+ function onSegment(p, q, r) {
1785
+ if (q.x <= Math.max(p.x, r.x) &&
1786
+ q.x >= Math.min(p.x, r.x) &&
1787
+ q.y <= Math.max(p.y, r.y) &&
1788
+ q.y >= Math.min(p.y, r.y)) {
1789
+ return true;
1790
+ }
1791
+ return false;
1792
+ }
1793
+ /**
1794
+ * 判断两条线段是否相交的方法
1795
+ */
1796
+ function doTwoLinesIntersect(p1, q1, p2, q2) {
1797
+ //处理p1和q1两个点相同的情况
1798
+ if (p1.x - q1.x == 0 && p1.y - q1.y == 0) {
1799
+ return false;
1800
+ }
1801
+ if (p2.x - q2.x == 0 && p2.y - q2.y == 0) {
1802
+ return false;
1803
+ }
1804
+ // 计算四个点的方向
1805
+ const o1 = orientation(p1, q1, p2);
1806
+ const o2 = orientation(p1, q1, q2);
1807
+ const o3 = orientation(p2, q2, p1);
1808
+ const o4 = orientation(p2, q2, q1);
1809
+ // 一般情况,如果四个方向两两不同,则线段相交
1810
+ if (o1 != o2 && o3 != o4) {
1811
+ return true;
1812
+ }
1813
+ // 特殊情况,当线段的端点在另一条线段上时
1814
+ if (o1 == 0 && onSegment(p1, q1, p2))
1815
+ return true;
1816
+ if (o2 == 0 && onSegment(p1, q1, q2))
1817
+ return true;
1818
+ if (o3 == 0 && onSegment(p2, q2, p1))
1819
+ return true;
1820
+ if (o4 == 0 && onSegment(p2, q2, q1))
1821
+ return true;
1822
+ // 如果以上情况都不满足,则线段不相交
1823
+ return false;
1824
+ }
1825
+ /**
1826
+ * 判断多点折线是否相交
1827
+ */
1828
+ function doIntersect(points1, points2) {
1829
+ if (points1 == null || points2 == null || points1.length < 3 || points2.length < 3) {
1830
+ return false;
1831
+ }
1832
+ for (let i = 0; i < points1.length - 1; i++) {
1833
+ for (let j = 0; j < points2.length - 1; j++) {
1834
+ if (doTwoLinesIntersect(points1[i], points1[i + 1], points2[j], points2[j + 1])) {
1835
+ return true;
1836
+ }
1837
+ }
1838
+ }
1839
+ return false;
1840
+ }
1841
+ /**
1842
+ * 两个图形是否完全分离,互相不包含
1843
+ */
1844
+ function isOutsideToEachOther(points1, points2) {
1845
+ // 相交关系
1846
+ if (doIntersect(points1, points2)) {
1847
+ return false;
1848
+ }
1849
+ // 点关系,判断每个图形的点都在另一个图形外部
1850
+ for (let point of points1) {
1851
+ if (isPointIn$1(point.x, point.y, points2, true)) {
1852
+ // Log.i("ycf", "isOutsideToEachOther: mapPoint1=" + mapPoint);
1853
+ return false;
1854
+ }
1855
+ }
1856
+ for (let point of points2) {
1857
+ if (isPointIn$1(point.x, point.y, points1, true)) {
1858
+ // Log.i("ycf", "isOutsideToEachOther: mapPoint2=" + mapPoint);
1859
+ return false;
1860
+ }
1861
+ }
1862
+ return true;
1863
+ }
1624
1864
 
1625
1865
  /**
1626
1866
  * 按Python逻辑创建路径段:根据连续的两点之间的关系确定线段类型
@@ -1883,6 +2123,136 @@ function calculateMapGpsCenter(mapData) {
1883
2123
  };
1884
2124
  }
1885
2125
 
2126
+ /**
2127
+ * 并查集(Union-Find)是一种非常高效的数据结构,用于处理动态连通性问题。
2128
+ * 它可以快速判断网络中任意两点是否连通,并能将不连通的集合合并。
2129
+ */
2130
+ class UnionFind {
2131
+ /**
2132
+ * 构造函数,n为图的节点总数
2133
+ * @param {number} n - 节点总数
2134
+ */
2135
+ constructor(n) {
2136
+ this.count = n; // 连通分量的数量
2137
+ this.parent = new Array(n); // parent[i]表示第i个元素所指向的父节点
2138
+ // 初始时,每个节点的父节点是自己
2139
+ for (let i = 0; i < n; i++) {
2140
+ this.parent[i] = i;
2141
+ }
2142
+ }
2143
+ /**
2144
+ * 查找元素p所对应的集合编号(根节点)
2145
+ * @param {number} p - 要查找的元素
2146
+ * @returns {number} 根节点的编号
2147
+ */
2148
+ find(p) {
2149
+ while (p !== this.parent[p]) {
2150
+ this.parent[p] = this.parent[this.parent[p]]; // 路径压缩
2151
+ p = this.parent[p];
2152
+ }
2153
+ return p;
2154
+ }
2155
+ /**
2156
+ * 判断元素p和元素q是否属于同一集合
2157
+ * @param {number} p - 第一个元素
2158
+ * @param {number} q - 第二个元素
2159
+ * @returns {boolean} 是否连通
2160
+ */
2161
+ isConnected(p, q) {
2162
+ return this.find(p) === this.find(q);
2163
+ }
2164
+ /**
2165
+ * 合并元素p和元素q所属的集合
2166
+ * @param {number} p - 第一个元素
2167
+ * @param {number} q - 第二个元素
2168
+ */
2169
+ union(p, q) {
2170
+ const rootP = this.find(p);
2171
+ const rootQ = this.find(q);
2172
+ if (rootP === rootQ) {
2173
+ return; // 已经在同一个集合中
2174
+ }
2175
+ // 将较小的根节点作为父节点(按秩合并的简化版本)
2176
+ if (rootP < rootQ) {
2177
+ this.parent[rootQ] = rootP;
2178
+ }
2179
+ else {
2180
+ this.parent[rootP] = rootQ;
2181
+ }
2182
+ // 两个集合合并成一个集合,连通分量减1
2183
+ this.count--;
2184
+ }
2185
+ /**
2186
+ * 获取当前的连通分量个数
2187
+ * @returns {number} 连通分量数量
2188
+ */
2189
+ getCount() {
2190
+ return this.count;
2191
+ }
2192
+ /**
2193
+ * 获取联通的组
2194
+ * @param {Array} list - 原始元素列表
2195
+ * @returns {Array<Set>} 联通组列表
2196
+ */
2197
+ getConnectedGroup(list) {
2198
+ if (!list || list.length === 0 || !this.parent || this.parent.length === 0) {
2199
+ return null;
2200
+ }
2201
+ if (list.length !== this.parent.length) {
2202
+ return null;
2203
+ }
2204
+ const map = new Map();
2205
+ // 遍历所有元素,按根节点分组
2206
+ for (let i = 0; i < this.parent.length; i++) {
2207
+ const root = this.parent[i];
2208
+ if (!map.has(root)) {
2209
+ map.set(root, new Set());
2210
+ }
2211
+ map.get(root).add(list[i]);
2212
+ }
2213
+ return Array.from(map.values());
2214
+ }
2215
+ /**
2216
+ * 重置并查集
2217
+ * @param {number} n - 新的节点总数
2218
+ */
2219
+ reset(n) {
2220
+ this.count = n;
2221
+ this.parent = new Array(n);
2222
+ for (let i = 0; i < n; i++) {
2223
+ this.parent[i] = i;
2224
+ }
2225
+ }
2226
+ }
2227
+
2228
+ function isTunnelConnected(a, b, connectIds) {
2229
+ if (!a || !b)
2230
+ return false;
2231
+ if (!connectIds || connectIds?.length === 0)
2232
+ return false;
2233
+ const temp = [a?.id, b?.id];
2234
+ temp.sort();
2235
+ return connectIds?.includes(temp?.join('-'));
2236
+ }
2237
+ function isOverlayConnected(a, b) {
2238
+ if (!a || !b) {
2239
+ return false;
2240
+ }
2241
+ if (!a?.points?.length || !b?.points?.length) {
2242
+ return false;
2243
+ }
2244
+ const aPoints = a?.points?.map(item => ({ x: item[0], y: item[1] }));
2245
+ const bPoints = b?.points?.map(item => ({ x: item[0], y: item[1] }));
2246
+ try {
2247
+ if (isOutsideToEachOther(aPoints, bPoints)) {
2248
+ return false;
2249
+ }
2250
+ }
2251
+ catch (error) {
2252
+ console.log('error->', error);
2253
+ }
2254
+ return true;
2255
+ }
1886
2256
  /**
1887
2257
  * 通过 mapData 和 pathData 生成所有 boundary 的数据
1888
2258
  * @param mapData 地图数据
@@ -1891,11 +2261,12 @@ function calculateMapGpsCenter(mapData) {
1891
2261
  */
1892
2262
  function generateBoundaryData(mapData, pathData) {
1893
2263
  const boundaryData = [];
2264
+ let chargingPileBoundary = undefined;
1894
2265
  if (!mapData || !mapData.sub_maps) {
1895
2266
  return boundaryData;
1896
2267
  }
1897
2268
  // 第一步:收集所有TUNNEL数据的connection信息
1898
- const connectedBoundaryIds = new Set();
2269
+ const connectIds = [];
1899
2270
  // 遍历mapData中的tunnels字段
1900
2271
  if (mapData.tunnels && Array.isArray(mapData.tunnels)) {
1901
2272
  for (const tunnel of mapData.tunnels) {
@@ -1903,10 +2274,8 @@ function generateBoundaryData(mapData, pathData) {
1903
2274
  if (connection) {
1904
2275
  // connection可能是单个数字或数组
1905
2276
  if (Array.isArray(connection)) {
1906
- connection.forEach(id => connectedBoundaryIds.add(id));
1907
- }
1908
- else if (typeof connection === 'number') {
1909
- connectedBoundaryIds.add(connection);
2277
+ connection.sort();
2278
+ connectIds.push(connection.join('-'));
1910
2279
  }
1911
2280
  }
1912
2281
  }
@@ -1917,9 +2286,9 @@ function generateBoundaryData(mapData, pathData) {
1917
2286
  if (!subMap.elements)
1918
2287
  continue;
1919
2288
  // 每个sub_map的elements是边界坐标,没有sub_map只有一个boundary数据
1920
- const boundaryElement = subMap.elements.find(element => element.type === 'BOUNDARY');
2289
+ const boundaryElement = subMap.elements.find((element) => element.type === 'BOUNDARY');
1921
2290
  // 如果当前subMap存在充电桩且充电桩存在tunnel,说明当前subMap中的boundary是初始boundary,这个boundary不为孤立区域
1922
- const hasTunnelToChargingPile = subMap.elements.some(element => element.type === 'CHARGING_PILE' && element.tunnel);
2291
+ const hasTunnelToChargingPile = subMap.elements.some((element) => element.type === 'CHARGING_PILE' && element.tunnel);
1923
2292
  // 创建基础的 boundary 数据(来自 mapData)
1924
2293
  const boundary = {
1925
2294
  // 从 BOUNDARY 元素复制属性
@@ -1928,8 +2297,6 @@ function generateBoundaryData(mapData, pathData) {
1928
2297
  area: subMap?.area,
1929
2298
  points: convertPointsFormat(boundaryElement?.points) || [],
1930
2299
  type: boundaryElement.type,
1931
- // 判断是否为孤立子区域
1932
- isIsolated: hasTunnelToChargingPile ? false : !connectedBoundaryIds.has(boundaryElement.id)
1933
2300
  };
1934
2301
  // 如果有 pathData,尝试匹配对应的分区数据
1935
2302
  if (pathData) {
@@ -1945,8 +2312,33 @@ function generateBoundaryData(mapData, pathData) {
1945
2312
  boundary.endTime = partitionData.endTime;
1946
2313
  }
1947
2314
  }
2315
+ if (hasTunnelToChargingPile) {
2316
+ chargingPileBoundary = boundary;
2317
+ }
1948
2318
  boundaryData.push(boundary);
1949
2319
  }
2320
+ const unionFind = new UnionFind(boundaryData?.length);
2321
+ for (let i = 0; i < boundaryData?.length - 1; i++) {
2322
+ for (let j = i + 1; j < boundaryData?.length; j++) {
2323
+ const boundary1 = boundaryData[i];
2324
+ const boundary2 = boundaryData[j];
2325
+ const isChannelConnect = isTunnelConnected(boundary1, boundary2, connectIds);
2326
+ const isOverlayConnect = isOverlayConnected(boundary1, boundary2);
2327
+ if (isChannelConnect || isOverlayConnect) {
2328
+ unionFind.union(i, j);
2329
+ }
2330
+ }
2331
+ }
2332
+ const tunnelAndOverlayList = unionFind.getConnectedGroup(boundaryData);
2333
+ const chargingPileConnectBoundarys = tunnelAndOverlayList?.find(item => item?.has(chargingPileBoundary));
2334
+ for (let boundary of boundaryData) {
2335
+ if (chargingPileConnectBoundarys?.has(boundary)) {
2336
+ boundary.isIsolated = false;
2337
+ }
2338
+ else {
2339
+ boundary.isIsolated = true;
2340
+ }
2341
+ }
1950
2342
  return boundaryData;
1951
2343
  }
1952
2344
 
@@ -2163,13 +2555,15 @@ var hNoPosition = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEoAAABKCAYAAAA
2163
2555
 
2164
2556
  var hDisabled = "data:image/png;base64,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";
2165
2557
 
2558
+ var x3Edger = "data:image/png;base64,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";
2559
+
2166
2560
  var x3Mower = "data:image/png;base64,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";
2167
2561
 
2168
2562
  var x3NoPosition = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAYAAAA4TnrqAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAACP6SURBVHgB7XzJjxxXmt/33ouIzKyFrCqxWCSLIlncRKnV0+rFaw/a1vhiA776asCX8bkPY4w9A5iGx9PdtmEfjbn4H/DFMAwYBmZBj2Z6gFFrtIxaCyWSKorFrUSy9syMiPfe/H7fi8jKWiix1WL3ZQJMZlZEZkS83/uW37e8MPIr3uLNq12ZnM+k7ne3s5DJ9o70TW56MY8iOzIpMlidlHp+XgbGXK3lV7gZ+SVuMV7NtlcnjvnhcDrL4oytuxNihs57i/uwxrkQvQ96T87ZmH5lYy21ZHiXYGpv4yaOrQ0Gw7XZpatr8kvcnjlYBGh92T2f53Em1nHOZdZ4L5bgZPo5HHIPwUTJcKAmYkGivuGVAUW8xxr/YV9p6qKXPawG9uHU0r+5J894eyZgYVRmffkHM3kMSzG3cxy8ACBjABJfClADEgDTv50T8dHgo3FFxAdjIjacKoqLCSyCBvnCuSQ6gqhA+ggAnbi+s/K4GJafmKWrA3kG21cKFsZm1tf/x0y+s34eyjML9XKZAmScD94SIAXMuwQaASNY2DzEzeGLQWpjbebT3RFIT00ESMZLsPxVcBDIGgi34MUY9EsOwNWW4MV70ydPfmLMv/pKQfvKwFpb/sFsLtX5mJlZzHeGYWPurZPM2UDUAIRwn4kGQ7OulTKKiTjrQzQAgUPXvSpReqxVQaE4ARicXaXK4QXAeJCfbfQ4Vidpwz6cMlpzd/orlLRfGKyf/vQP8pcX1s/VtjqXGet8WTqxwIZGSQGDhmTG6cWiz7xxGaXFmAz7KnENNNBHBdL7GuOk2uEIZFVFjNKEU0GA4A0zmitPQKyavzF1VLCMHo/BVgQMOrvjquKTyXO/dR9iHeUX2L40WKpyt344YyW+hHuehP5kdFkqVQEjy10Wqtq5BJSFn8ssJAj3i79rE6KFtfaA11qVJvwwQD54Av2fb7gKZDACCQiJDdyspaDiS9ZRKQGOpVRVdR2p3ASujrEILpeyroFVsNgfqsrK3enSL/8iUvalwCJQcue/nd6ut65QiqBgGXyX8wTChjxgTl1haFgKZ7wNJmQOx2mhgALUD98zBKumclq6xsDxAwr9FLG3Ne4ZVRNDBihRJSoLCiuEDpeGIIZabA7gbO19VcPHQsltRTGMVmoXMuAYAJj1pcTtI1X91pcF7EuBNXjwHy/VA1mCCDnKhokhp0QRDGez3IeKKphjQM45GBRKHYGKAM0QjkgRA1gUHm9g5wky3aiqZASEsO0xcLyGkqM2CqDVXiUJFBbDB6ShpCRByrA/Vricxy88Zq9SlTSUMgAHYYS0QfcxjQDOFPb9qYXfuS8/5/ZzgRV/+pv51vzpy8bkiybUVLqMUkVpom0iKC7zkAWTw+piX8igRQ52uACktPEA1JNuAiiD0QK0ALnDKC3hxTisKiKwglTVEE0Fy1C26EotXCbYhUparAL/hnhGk0GiDKRJAF4GAhLx2WI/gMJJ9B3SRumqsYcSlzl/s3fqd5d/nvE/NVgkl/3b9tsY2Ay0ioQa9hyApMkvSA9AFqEPUMAA8Kh6AKyOdQZxyylVGASlTSXMUkTwG0gADuMIRoSrmPSiIcYe7hK18RFD5t8ABUbbUB2dDhp6W+IegCrBMRWALPmOU1ZwJrXjLZh6SIMPCYQRxalKAlfXXZffMKd++9bTYvDUYG3d+OE3JPMnjFGVyeH/MWgEcQAMwpGLraBirghUSeNzKFFBVQt8jx6BHgDDhEK7ABq+7wNtGGxyhNYALIiJwiVJFSlZNEzYE4xiCZoKjxCSegFAR5oAUmZKmEF4yTjECSqIVQWhhWSZCoZvKCYDSFkF7SvVY1ocR9gUQ03vU2/HeO3Y87+78jQYPBVYg9u/fxlGAtQASmapZlLgrhHehcLDbmGQRQLKF9AAgCKQNMmhWAUu0YUzAsCmoFRBbvDuVQ2FKpnAsTRUyQ+SZ8GyhAQYjvFjhHrWNOu44SrSMwAsTBypQoXdJeawDFFVEaDlFe5vaF1e+tqrpBE0qHyZgIMq5qYUVUlf13V4+2nizC8Ea2356vlu1rvgQwkVAh0AIA7GO1B9MkgVpctUBCLHTHZwsx2EJ5S8jqUPl9CBi4e0CYIYShscAVQTFt71y6K7NZye2BwemSjrIrfKrKR1hc0HK5P5ej/Pq/LY1IM1nB8M15UMenAPAMEADEiZ2CGChApTMIB+DyG1Q1x2CLMBb2PLGuqpgJkWsAhVzCBhMPreDHqhfOOLvGT2eQc//fT7vSzkAIrG3DhVPUgEVRFkCapmKEm0VwX+7qjKWdPBDRSWf9vYgfXuQDgIZIHYJB/Wne77d76xdHfjxML28MjU+PW6nc6e64MLCKZ9z77ZicePFmeX71w58f4ypBlqB/sXISUkt7jHxNkAcwo6yfpVrR2dBKMjb5THgroEeAqKMGlzZyfLX4Jdfufz0kBPBIsGffNW9m1l41Q9df8W7z6HdQVQtEuUGHyGM8Y7VC5AemLHUsJMAFChq+9CCYvFeytfX/p49cp5sd3s5PxzcuHoEZmenJBetysM8hAP6Tu39jPfCVi/LGVza1tWHz2ee3dldu7j+1cufO+FP3r9SGdrE2YthwQjDIVMC/1qZLRgiA5IPBkJwnJbeB5UGkIbRw+SGB1lFAHn0eHH7iz2X38SJk9Uw8Eq7FQ/LsEekUjCkFMFqw7ECuCorcrhxShFXUoWTEkHF+4CsC7fMZEdECOgEIqyyif//Po/fuXxztzM6ZMLcv75RSQZ3OgG4r6bifturFFJNffcPwBw7310Q9Y2NuXCsWs3vnnmjQ9FbRYShJINAVwf0cIA3nCAQGkIaRtAF4b4dR8WfqSWqoY+DKN1FdSGTqHuTtq3zNxvr8vTStanP/l+r96JZ+HfGeCCS5EOERyGLbWyb9x0Qf4kpA+QKmgBgKoJEsWki+vCVsXORv/I0R9/9Oq3jZ3pvvLiOZk5Mq0jVj8Xg0rOCJQxtHY/J+jav/lXkeXyyktX5JNPV+T6ipzfqSZ637342jsMRyNDKNwUlIuuFopYQ54MORqYKSkcjR0IHbJpmFjPaAKcEVQN0Rm+uzM0l3GJ1w/DxR62c25x9iXAAnZO+0TAYLMQtniqYrQw6EbVD9E/pI1A0S7REwYEORH2yoMuSHdjZ3rmNQCV5XPdX7tySY5MT4mmDQLTAkFBSJ/j7v7m79j8ndxh3PviMdzMmcWT8u2XX5TVrbMn/9/P/vl34SSmOEGAtAsGh/e6SJGEpeMplOpEzYhkdEgAzdFBkVbzM9NJYBST26s/OnUYLgfUcPuT/3QSdPHrhjQBtiCzlgST3q8wdZ1DyKB2dQf8qoN56gEgSJTvIr7txvR5AjPQ3RhOzvz42qvfUqBeuKxq16ocVaqqavn41m3YoDVZ29ySioZc9awRu7FbzHMn83OzcunMaXludobBUJJK/S7UcjiUN9/7QOanlu/+g/OvvQPD1Ie92oEZG8B67cCUDcB54ekM/+7DUEE1uU9KMJoS2gNaQYpBsgrviERi76PydfPqXmN/QA0hS2eUGEKyMkgSEafdcnwvEAwHsnBIVOVJSNtZomEvKF3UEnDF4rVrv/EtMUe6L1+6pFY1qpQkMLZ3BvLaT9+S7X4/qZga8gbIMZza/WVZy8q9VX1dOLMo33jhouwSDFywKOTly5fk3Wty8u2Vnf43Tr/xoVoQzCQmDjloz+i9ohnH0DLQOyQI6dk5vBJa44g8JQu0DMTE+s7a2WwRp15+ohoygWdtPAL91rSKZ05SaUJgZsqqyAYybsZ8yKaD9eEqEHV4RXg7ciyqwTu3v3OxCpPdr126SN+kQFHtqD6PYZT/8C9ely0AFTStF5P9Es1mqMTEuKuKu6+g4H20/Kn+fliCiGv+Ib163Y6cPrEg1x9cPn9v4/Q8rkd1xMQxFEOpCFEFbG2OwWQ2j5pnYwzLgSIMJ1aqitIkJTuT9oAq7gGrV8STtf4AQQGAck7z5PS7TjT4FauwxUxjPyGvU+kEiOBSgDjf6E9PffTg0rnFhQUYYoaByeYQsO2dvvz49Tebge4CIa19anKiu0DJrj2LMvrO440t+cmbfz06b3qJnDh2TI5MTsr7d1++oPcjXmNY2Cp4dNIbaIRHkO+ZmHAIzwLjn0JTR+C6mjOhjcYLzK2Ij3549FCw6AFx0UXThB+eVA1oM6WCczpN1jEFozkrzxQB/s5g1CMvRAboaDTvb5x4jvZp4bnnGoPczn6U19/9QIbDcgRSCE+Son0S1Ujb+At8Sz6GlO0a/nSNU5ikx9uzs5UHeQO3IkhIfWkgT27IzEdUDaHDYro7MnebUtzqxCAYXiMtu73tzx4K1ulLx2b0B4FAiUoVOJvjj/GZegw+GlJMx4sRPAJFrwjpwo3ipiS/s3by2DRmN+wD4QEM+QMMkFt7TBrVOwys3d8naTsMyJ9d/2TXOzag9YoUBSw/PLeAe8tU8vEOe5DuXTQdRClQ6QomeUKO03imukMq0SGh4Zw5Gv/kanYArJ0df0rreThPppKFzKdWY7wWE6jUEBhYamRJIitPyFMhk9sAlvEGYE/doJro9jrdpBpjlGDlwQNhdDwCJsQ9gO7Sh/C54I2/qqqCJ90cXYsvOpMiz2WnRGiA+wsq9dAu3CMNvGHwDpscmW+TNC5NUjtPaSLts4oDfoZwy+283Du+ByyGNnCXcypZtFOG1WGbTgQt1KIDs77kJRRaBYnFGEpZYMaAF2e+yjEoVlulXKh54TPpQUq4NHZK9oIhY3ZqP0hxDODRqzlG+7WXhwWZgAb2MWlRbZGoDU/3m+wucmyN2tG7By2Hp7eQ8ml6DAlqvJCcnWnBSiK23kE5HRpb10azSiC8wMWSQDCdRMcXQu34czhyo4UsGkQW7pTNe+4AD01VnDzL9cbTREjD2BvqkHDaZejNl9qIcBTsjFGI48/NyuTERPPd0X/K/qcmeyz1j6IC+H5mV6VigjKyFJk0gDUfag0dFT9o7clTRXnP1ErDMJsFINaGMPIaEsZaXn50D1jrG/VcF4oqWU5KbWjcMwLmiXFCX+vJsWaVGMEqbYEW7hwjCZUwsLztwXR3j01qB4z3re2dEYC7g262BqO2UkXJ4b4c4/1H33kFYM3J+EYyakz7Q2k+p8TXdlmrKtZVkXL8FBFmYa1T7Wi+DdvsYZuQkatZO8OYPaedwmFVsxhgwa0xw5bH+F8mjfmt7SxJQpzRngN+h9UDyAkj9PQbngo/pN3KcCQwOV4B/5CSdnpKrbXrjY2GsL9C18SCe6RnPGRu1G/871+7fGEEFGlHHANGY0qzC/b0xKTwvrnRuA4HmYKlVcSYqt/sEYDc2ET+M4eyD/iDpX3WnC1CRktkqU38qfcsIZRm++aAqaQEFkRqggPmmMkiIQGqaIEAaTEBwQxOxGKf0dKda4CsTbp/1tl5/0m58jzfIwUEYYhKQYzjKDYphHEJlL0oa9CN7ToC5j//q3daXA5sU7Dl/+Kf/sYISG6Vz7JkGmgqCJpmYDkEjAcUlRl81i+Z1TGauLFpGDBHNHBq7JF/xll91iNY9xUs6HnPKNVOkmWlxYDimwaUKscsK+DEvqYtY/HKku1BYmivOIO7t2vGpYUhS7UnVyWyGwLul7X2r8leT//a6Q+EtVgZOyajs0lSiHGk4tgHxiCs93AInFUm/gLnn6fJ1evjC5RGpgeRcAwKqvh0mzTy+E/5SMZmsiEr6lQR0suKNbuY0GIVKqq5RzaLYhqdaVBUxWf6jCA1hkHGJ348KdWw7xGA+6ro8QAM0uaXG/ukAj82C7LnUjxmx8FqNZnqF9MJ1JKkVifLhD5L4TqUWvm6oeLoZNMLSpNghWYhP8/yrdribN12ehmVVmcg2at2LOpO1Toa09TWydylKShYvSaFtsGhFRnvE/CyLyjeg8q+rQUp7pMxPdYIuTF7UW2/mUpmMgYo7XDlG3GjU0qTa8cmJPjdO1EVDMqF6pC0S1oJI5Ut1f4lb5ix4y4qy1dvCESM8lG2Jdim94C2K2WXCDs+p64gED2jKhzYgJZslueNjI1X7VY4ROdkHMzD/jIj6VI1NLtZnPHzU0isbeU97S8gDmPn0XszyQGZ1KmTxJHEJzZ9K55S1nrZLA0VnBy+wCc1rMwQkpXr5LBNkSZIw0L+guwkhdLRNncBg2gUKGOT1+C7zpLZM/7xfLoSyCbdMi5ddATTEz1l3NzuffZwL44NUDouXk4SUGbENRqwGqlrsfJJatIXg3InJXkh2V5GiUlBAs2Ili5SoiW3EWoHzdBGOhgzm/IvIdHRjFTPqa3SSghzhZrD1Q6GWukCwcC+iqOFutNA2kbCmmEzXwVj10i73Lh9S2aPHJXnF06OgTfCSD9PAaR/9r1/qO/ttgV68L//6E+lRBgz/iMFrBEnVZIREg1YjVTx9c5HH+i+uUlJQWUiM/pVO/qPxDFTHwmHyMSyspqWKLM8EG3jt5iWbgaKSldROcqNtkOxATad2tMdkMeFdApNXrC2yZOy6U4DoYYYNd85UmyUr5z5y/u3Hy4dZYwo+7Zxqbl49vk9QEkD4KWzp+W9j2/qd++tPpSj5yZlCenj9rstGTVjQFIyzZiBf2HhZ4+OTjzeGu1IqX6j1lhVMOVWEJWYlG7MFVnSejaHsZMiecNaTVNrXjJfmpquk8VybxumRzGOGjERRoovm17ZgQYDiOQQZSg2dsAkxu7UKfp44diHGw83j08OBnIgQzxurh6tb8hhW1ntZnLfQEpn8fgxYRZjElyqVcEWl2TQd/+u66R+89N3+yeO3t+MLQVqGR69d3LvUEMy/VwJO+0z7Y9amWR11WUjqKEnQ845qqhnVd/WpKTOIZkYtRISg/Zq8vzQWeW/bP9B+oLB1G4LY2jVUNuwQ1C9WNuez++tL06wXjoO1AQ4U0ojp235zj158/1rcgL1w3a7hX3MhLbAMkT6P3/8Z2DyF6XTyZv9aT5b0CYgcQuzs3ps2KjvG7e+e/yfvPB/N7udOoWk0kToY0YVxDrSb7GXEOkYxnNkD8a2RpUtK7XRvhfHTjOCdazTr7ZxROeE7SxtAGetVioDAx/GPtoJq1ZVeYMyeO3N8zZNiI/bw+n8J9e/tzg1dcyeOr4g49u4A2sxfAtgxfdln2NIWxwD7CdvvjM6vj9QevniBZnIuqOdV5Yu0Gbmf3Hj1dOvvvD/36PqqbfTi3JGnU6+clNKmDTelEw91JqtdxqkRm0j52Dz2NGyvmV9P6ZOOfanNFtKt6WGH7ZW0xuw+zGdPMGfkimG7T+60wgSf1Oln85PzS+ozxzfEhs3+yilGQEwTsEOYRZjZEIOOW9zjPYLGY9T88fl4faxqaHvuqR/av3jyF/WJIlNXoipTg0zaEaYplOSpH8nrc5QjU22IfUBSOzrl502dGqsydwG3SupL8IbfKzY8BpHyqVMLMaRice2U07kRZ6NhSa72+LC/FiAsgvBOHiHgSRjx0XMAdCOo0Rm9oVW3SL5FqSWXQoFYmjDAmWKamqY+fKNa9UZj8qmWdNqBQfvGV+Sb4/Awnc2JX1IHlH4asoFTJXi5JbJBaMLR4LarPZwaw/0phgluEMHu7S4qLN+GCDmkPcoh0lcHIMZE3B8XiUr7puD1uBv9qeKFkarzEqzeypJQaWLTby0QEx6MaZ22o/PVxIcbi72u7I+Aiuri03ljZrAYZjJVJWmroImVqiKFEn9eQiNVHnN2Ct42sIYDgR9Yxuzp6chXeNAPenLozBGnixx/Hvp9Kk9AJndmLDdqa2ENpVkW0MftRoBqxLZtKR1aLaQU09Y/MP4nUtAab99iLNvD3Yl67Pi/uqoLOBsbCMZzQT5qF6jkeVUNWKzTppoZoZSI79y2OKA/o3zn6XFU08E6DDAxoGRMfC4UaJON05kP0jONfGGJhvUVmmJKZn1RjMCZxriFbl2ShcgsMcrObpomuobeybMVluZ1rM+//x/7wPgHfYDM0fB32iGh3nVLFMLmE5KmOo20ZBmi6pvjTbIDqvc7QcpjunI8bk5+Tq8134QngTSk/YxTPr1b70ynl3Yo4qtKTB0T5psoZMy7MSKWlHQjDtNCuhCRfGpSUh1jQFTXSmQpb0CpF076ggclXmQhXmAE05JVccEhouaPGzKm8p+mUNOZVPmxwgeMkVsOmaawYQ2X2EO05lmMHT1ZNwffrKsvCvu+8phAI7vI+DffPEFmUNi8HBbtVvWD8lJBbXfmk0yWrdBkjekflXaYi4D6rLHLdrG7BC4yAAYI+sX5uEBsAberHZNfY7egKPHP5bCmGvR7F5aAsI+dFIRsFvDuo2Sd4adtQpdHKWi9gK2b1CXz53R14OHqY7IUv548LznNw2KVDuGPKzctNfYm9mQfZnYpHyiDsqSKTJlR6PCQfG+GbvVLmP3JnQwNPaX1FyXe5Bqhv7c3L9bPwDWzNl/+3jj1u/3XVr9EFODIWqPdRlIVglSamZixABdZPO+8RUvyHQ17EOtRr4ZYIxPtk6mGewCqjZ8Py6zB79jdmXtsHPtD6UOvZy2frMdi/ZbV2do9AsTw65mfgOVfLaCsLbHpiH1lJ6UMkYu3cjX9p5ubAuxXEnZFM+ccU3dVc9A243ZsdpWDbFFNQ0nrVUVkUc0bITVVuvY5iH2DXqvoW8HdtgAx21dW0M8TDfjU3gKrkfAEBkDNSsu2IzrdJEU4jaMKQ8213pLclasfsKE0Xazt/hhVd4aP9+elqOjZ+TWYEXOBvo/VmhZIVL7ZPmJxISSk0iEYUMVW6uZyGG3sEcOJ0nWqKVxtAQH5batTfn03h1htZoGmEaa5FVfTcuklrxtqgDQ37IBt8WJOSruYxWaPKhfDjWVw/CG/K297rgkIsdL7apYV8VVyKzIqdRuYXpZNkZYKFyHBnXUFUJey+ZKjeTu0r7u5WzvrF6t+8s/WAaol0zQ5aFeo8XACNWwzFwBx8z5ugq6lg1ibm1ptO9Jsl6xs/lgc7gXqGbbAFhFtl3OT364tTOcLFDXsxvVpI6yX/Zy+ZwtsyiZuhR8TOTb1XRnq1yY2KluPLo0t7G55Y41gXRrs9jcxq1jS0YmFTJ4JdQG1QVmD2Bfa+HioipyOQtpvJY5lYerhDFC/KwOB1ZeHGhm654Z3tpayc/S2WobGHScS0utRQ7RZ4Xxw1p7y9kFTzhB7yKb9iEXS89dv3lj9cKL9x9+JgvHjo3OyWD40ca6vHL63XsXjn+4lqTF2YZcm2SZNOeYvOkoBSrJi6dCX2wcB/m3erIqdsPK+sQC2y9b6SKT+fT+Xenmw+35I6v3cdIh5AeARbbvqJTRzlh6Ri5yQbTscnCHlLvU7Hosw72l8wd74g92/kG61pavLhuTX2LZC/pb+1g7jyI0jH9pUkURIPkyZV5jabk+C9pzdGLj0eUT1968dk+++XhzXQdA1ekPB3Lq6O0HF+Y/vtPUPTVJHLWlvrlwMlImLaA0qbgTEn5RUrO2jOrcoCugMV87+ebN1a35yQ9uVlNTvVTe57Vy19/69Ys//kOcjgsKKlyt9Fyyoq2QMUkVgSNgJFnNSljcDZcthmkTluWQ7dC4lY0ig7vdvw9XMZ3W6ZgcWY6u1MMcN9nBGHoYRA/aztVeBdiDtnRjGB22dG8Mjh59//6VFymJzlb+zNyt1ZNHb2+kyKMpobOYy+DApPVeKQOQ6iVJqGI80B6c+FzqCknVTdIW/8H9KyfWduamq7pwU521x5dPXL9WZP1tHO9zSQoEcQBZHOKEA8uXywZVDfU0AukJ/RgK2Kw4hI2ppqy7/qTFT08K8rVlEh7078JGwvRZgGU6ECx2zxVQuW6IQ7yL9rnDXndwQ10EB83yE+1mziIXPWlSg3SEHTcMy7R8kIpW1jZVF1oKqzFaWwUyjU9t1x5q6jZoK3YjeVqJCEmd2P0fk8fTMrkrwZC54GlodJlKgM0yA9S6tC8enIgA9lHtYk/8EN/jajPsj5u9E/VfPWmVxRPB4rZx++rlwuZLXNloKl9gpgtjS7YddjEIrskBo6t1MRNXgll2NLMJ13C9oS50Si2KEtJSEa1nM0yzScJso2ShrdxELXkbSRVZLceoqwixcXUpvdEAxchXtToxdPIp0ANLSsP1hyUOwkQIFwsMceKhECRIVlo4wLU82G/iMFaOpZly6vTZ1435l9tPwuNzwaKw91d+7+/hnmaZsMbNUdVQSII6UroAFlJk9IrYH3MoVo7fcOlczjXRBE3Sii+2msBusw3IN6UYtX16OIRUm7QNDO3+pvqlCSM7uiMXRdo1Y9RLQK8LNmPqSBYSZF0AVeE3Q64aAzglPpdiC9CbMMRFKE0lAUJqoOJ6alDwG1+0WPNzFzqp5/l04u3NOPgOROgI+6ATEc5VP9JguFAMZhSunaxeV8R7tgc6tiblymYMval2BbDua9JKOaYEcpesFAsDtOdN2w7bPnVlTRK33cy4Jjt4TaXaTdcsA2QSZz4QgVSnMtp3B2POJSiRoEFFuYxOAsBR0GpcD6B6rkWs60pWp8/8zucC9YVg6Xb6+/3pz/7nW4Phnb/TrJhk+Im7rKHpvG/8Rd9IB6wdXSpNfHIDnCdvRlsztHAWtNmt6Y8SzfHoIUpT6jRg1UVz1FGrxZpSqSW1WGiPB8M7r48eQX4g2IYfSUoVWUnLgAP7DS3qx5QmLv91UiLuqG1GoGwVyRsNuZf1hfMbH62Ha5+fp22ER55i4yxvrvxgDiSLq8RyrrxgIw7qYh3RtskqYyGcfeb6SAKj0VWz2l77CFiYTPaKNF/7JJKFSjZJ7Y4Sh5RtSp1ctm24Yphr2Rqm0hRofdk1y8cYIJJhEVOZt9GF5rqmGnSHhJkGPEP4V5MXApwAh4dog0uDQVKLTrGZL3RRDfn+4Gme+fDUYIn8B9iveAqj/BqXRyLyLHT9YWjtE/vKfaYdwOyZ5+NUXOMFkyFqjXrLOClF2n3DR0E0yUcoHPNLGnCp6dJHGKC+Z2NqzVWqxQxK4rLBJ0bmdb+hvHE1ftTVqqJdK5Yxc0kHkOJDSlaoQCm2eovPvS3yr/uNz/1CsL5YDUUa9hyltygr8vhHm4PN4Xe4isCnUmvQZ8dEbapkexPLwzVjS6YvaH5pWlKpiClFk55Bw2KjcgKKEzMoTLWFJtfO3gEloWrakPpgu31Mjecp38mlqKRqWvuTZn/Ux43QG+hjVjANlWYVTFbzSRiFTUDh/Bu9xe5fi9wdPi1QioM85Zakq9nu/9eJQRh+M6DuJVQ5sLwUeEMHasTxmfaT6+NSYBf4eAIju72coqGO03yT3Z8UptHTJ39IEsbUGpAKvZp1YhTaZglYxkLpHRjUTL94VqZMWWrYGjXIZ/7Ki/b5IVjmF0tzf6qcui4XH8HgX22TlV8tWHuAu3rVym9Kd6e0L+Du5kGNcnbgww1aXTvN5gl1SfBHXBPD0i7KAApQxSqmCpf+ndi48lL2RKWQkTZMPSwBqPQxY6HNZcS06sbpM7UIIgFpcus09kwkqeE3bFGu9GlINqmg65Uf9+Z+b0V2G3yeWqq+PFjJFGuP/uDuj05DWi74iitZcy6HcuylV6BoTAge7b2pkYEp4MsqXa2srSpB7CikCWwmswqclqJsejKbSzEh24BwQi2GsktRn9HCNgOtKyBN5LgEi6ab4AT4IFfx2Q9siMQtxO1ecfldmX9vR+Tfq6gezH0/I7D2Aye3/1e3dDfPge4sIu3laOUl99bzSQT09OyF1aUtDMC158sqVtpMqAWDJGaqYqm01LbK8X/bFPGIDE2d54PZ4BxdKsIzwvQpbmTRgU87yqCCJOuukuHgVvfcnRWRP6h/ZU85OmzbenD1hK2y87DHk6IRh3A1unZfqv3iAwp8s4pB2ibn9IQ2bS1XvBQxkUZH05ltWlbRPEsrtg/1UbtFbsWH3oBSVHV6nhb3hbg6FcL1r/IpbV8pWO0WH/znE31fnoEqHkkrrDSfYLgeRp9ew2CQDp19wsqWoiqrmnfSiEa6FBR2H3pdc6ZS5NKjjpLtUhVUaxjq1FwYclvc7Zf9+8/i4YnPBKx2izevzmxZOQXvOIORTChwEpoVDI1UcZ0Qywhqx5ofJofZ5IYpUUnKWKKqm5qerpeIWVOoif1hVd+bOSe3n+XjOZ8pWOObAmfcSbjHacRt046dOSY0T5YUxYOkj0Qtc4U+4Wj3x1bBScoJ6h2Y0Cs3uSgMu35pj+D8pYE1vjG5KJ/IVL+OU1U362U2m0K9A6Vvm6f26dTFQgqO4kaVMUFnqgFKJFs7O7I1/9Kv/gGvf7t9wfY3LX5XLVhDK/MAAAAASUVORK5CYII=";
2169
2563
 
2170
2564
  var x3Disabled = "data:image/png;base64,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";
2171
2565
 
2172
- function getMowerImageByModal(mowerModal) {
2566
+ function getMowerImageByModal(mowerModal, hasEdger) {
2173
2567
  if (mowerModal.includes('i')) {
2174
2568
  return iMower;
2175
2569
  }
@@ -2177,7 +2571,7 @@ function getMowerImageByModal(mowerModal) {
2177
2571
  return hMower;
2178
2572
  }
2179
2573
  else if (mowerModal.includes('x3')) {
2180
- return x3Mower;
2574
+ return hasEdger ? x3Edger : x3Mower;
2181
2575
  }
2182
2576
  return iMower;
2183
2577
  }
@@ -2205,12 +2599,12 @@ function getNoPositionMowerImageByModal(mowerModal) {
2205
2599
  }
2206
2600
  return iNoPosition;
2207
2601
  }
2208
- function getMowerImage(positonConfig, modelType) {
2602
+ function getMowerImage(positonConfig, modelType, hasEdger) {
2209
2603
  if (!positonConfig)
2210
2604
  return '';
2211
2605
  const model = modelType?.toLowerCase() || 'i';
2212
2606
  const state = positonConfig.vehicleState;
2213
- const mowerImage = getMowerImageByModal(model);
2607
+ const mowerImage = getMowerImageByModal(model, hasEdger);
2214
2608
  const disabledImage = getDisabledMowerImageByModal(model);
2215
2609
  const noPositionImage = getNoPositionMowerImageByModal(model);
2216
2610
  const positonOutOfRange = isOutOfRange(positonConfig);
@@ -4692,8 +5086,8 @@ var PathSegmentType;
4692
5086
  */
4693
5087
  var UnitsType;
4694
5088
  (function (UnitsType) {
4695
- UnitsType["Metric"] = "metric";
4696
- UnitsType["Imperial"] = "imperial";
5089
+ UnitsType["Metric"] = "Metric";
5090
+ UnitsType["Imperial"] = "Imperial";
4697
5091
  })(UnitsType || (UnitsType = {}));
4698
5092
  /**
4699
5093
  * 面积单位类型枚举
@@ -4980,6 +5374,12 @@ class BoundaryBorderLayer extends BaseLayer {
4980
5374
  this.mowingBoundarys = mowingBoundarys;
4981
5375
  }
4982
5376
  }
5377
+ /**
5378
+ * 获取当前割草任务的边界
5379
+ */
5380
+ getMowingBoundarys() {
5381
+ return this.mowingBoundarys;
5382
+ }
4983
5383
  /**
4984
5384
  * SVG渲染方法
4985
5385
  */
@@ -4988,13 +5388,31 @@ class BoundaryBorderLayer extends BaseLayer {
4988
5388
  return;
4989
5389
  }
4990
5390
  this.scale = scale;
4991
- console.log('draw boundary border->', this.elements, this.mowingBoundarys);
4992
- // 只渲染边界边框类型的元素
5391
+ // 将元素分为两组:非割草边界和割草边界
5392
+ const nonMowingElements = [];
5393
+ const mowingElements = [];
5394
+ // 只处理边界边框类型的元素
4993
5395
  for (const element of this.elements) {
4994
5396
  if (element.type === 'boundary_border') {
4995
- this.renderBoundaryBorder(svgGroup, element);
5397
+ const { originalData } = element;
5398
+ const { id } = originalData || {};
5399
+ // 检查是否为割草边界
5400
+ if (this.mowingBoundarys.includes(Number(id))) {
5401
+ mowingElements.push(element);
5402
+ }
5403
+ else {
5404
+ nonMowingElements.push(element);
5405
+ }
4996
5406
  }
4997
5407
  }
5408
+ // 先渲染非割草边界
5409
+ for (const element of nonMowingElements) {
5410
+ this.renderBoundaryBorder(svgGroup, element);
5411
+ }
5412
+ // 再渲染割草边界(放在最后)
5413
+ for (const element of mowingElements) {
5414
+ this.renderBoundaryBorder(svgGroup, element);
5415
+ }
4998
5416
  }
4999
5417
  /**
5000
5418
  * 渲染边界边框
@@ -6063,7 +6481,7 @@ class PathDataProcessor {
6063
6481
  * 专门处理边界标签的创建、定位和管理
6064
6482
  */
6065
6483
  class BoundaryLabelsManager {
6066
- constructor(svgView, boundaryData) {
6484
+ constructor(svgView, boundaryData, { unitType, language }) {
6067
6485
  this.container = null;
6068
6486
  this.overlayDiv = null;
6069
6487
  this.globalClickHandler = null;
@@ -6074,6 +6492,8 @@ class BoundaryLabelsManager {
6074
6492
  this.svgView = svgView;
6075
6493
  this.boundaryData = boundaryData;
6076
6494
  this.initializeContainer();
6495
+ this.unitType = unitType;
6496
+ this.language = language;
6077
6497
  }
6078
6498
  /**
6079
6499
  * 初始化容器
@@ -6139,7 +6559,7 @@ class BoundaryLabelsManager {
6139
6559
  labelDiv.setAttribute('data-boundary-id', boundary.id.toString());
6140
6560
  // 样式设置
6141
6561
  labelDiv.style.position = 'absolute';
6142
- labelDiv.style.backgroundColor = 'rgba(30, 30, 31, 0.3)';
6562
+ labelDiv.style.backgroundColor = 'rgba(30, 30, 31, 0.6)';
6143
6563
  labelDiv.style.color = 'rgba(255, 255, 255, 1)';
6144
6564
  labelDiv.style.padding = '6px';
6145
6565
  labelDiv.style.borderRadius = '12px';
@@ -6156,7 +6576,7 @@ class BoundaryLabelsManager {
6156
6576
  labelDiv.style.zIndex = BoundaryLabelsManager.Z_INDEX.DEFAULT.toString();
6157
6577
  // 计算进度
6158
6578
  const progress = boundary.finishedArea && boundary.area
6159
- ? `${Math.round((boundary.finishedArea / boundary.area) * 100)}%`
6579
+ ? `${Math.floor((boundary.finishedArea / boundary.area) * 100)}%`
6160
6580
  : '0%';
6161
6581
  // 基础内容(始终显示)
6162
6582
  const baseContent = document.createElement('div');
@@ -6173,12 +6593,15 @@ class BoundaryLabelsManager {
6173
6593
  this.currentExpandedBoundaryId === boundary.id ? 'block' : 'none';
6174
6594
  extendedContent.style.borderTop = '1px solid rgba(255,255,255,0.2)';
6175
6595
  extendedContent.style.paddingTop = '6px';
6596
+ const boundaryLayer = this.svgView.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
6597
+ const mowingBoundarys = boundaryLayer.getMowingBoundarys();
6176
6598
  // 面积信息
6177
- const totalArea = convertAreaByUnits(boundary.area || 0, 'metric');
6178
- const finishedArea = convertAreaByUnits(boundary.finishedArea || 0, 'metric');
6599
+ const totalArea = convertAreaByUnits(boundary.area || 0, this.unitType);
6600
+ const finishedArea = convertAreaByUnits(boundary.finishedArea || 0, this.unitType);
6179
6601
  const coverageText = `Coverage: ${finishedArea.value}/${totalArea.value}`;
6602
+ const isMowing = mowingBoundarys.includes(boundary.id);
6180
6603
  // 日期信息
6181
- const dateText = formatBoundaryDateText(boundary.endTime || 0);
6604
+ const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0);
6182
6605
  const covertHtml = `<div style="margin-bottom: 3px; font-weight: bold;">${coverageText}</div>`;
6183
6606
  const dateHtml = `<div>${dateText}</div>`;
6184
6607
  extendedContent.innerHTML = boundary.finishedArea > 0 ? `${covertHtml}${dateHtml}` : covertHtml;
@@ -6296,7 +6719,6 @@ class BoundaryLabelsManager {
6296
6719
  // 计算边界中心点的地图坐标
6297
6720
  const mapCenter = this.calculatePolygonCentroid(boundary.points);
6298
6721
  if (!mapCenter) {
6299
- console.warn(`BoundaryLabelsManager: 无法计算边界 ${boundary.name} (ID: ${boundary.id}) 的中心点`);
6300
6722
  return;
6301
6723
  }
6302
6724
  // 直接使用预计算的数据进行坐标转换
@@ -6381,7 +6803,6 @@ class BoundaryLabelsManager {
6381
6803
  area = area / 2;
6382
6804
  // 如果面积为0,回退到简单的平均值计算
6383
6805
  if (Math.abs(area) < 1e-10) {
6384
- console.warn('BoundaryLabelsManager: 多边形面积为0,使用平均值计算重心');
6385
6806
  return this.calculateAverageCenter(validPoints);
6386
6807
  }
6387
6808
  centroidX = centroidX / (6 * area);
@@ -7292,6 +7713,10 @@ class MowerPositionManager {
7292
7713
  getElement() {
7293
7714
  return this.container;
7294
7715
  }
7716
+ //
7717
+ setEdger(edger) {
7718
+ this.hasEdger = edger;
7719
+ }
7295
7720
  /**
7296
7721
  * 根据最后一次有效的位置更新数据
7297
7722
  */
@@ -7315,7 +7740,6 @@ class MowerPositionManager {
7315
7740
  postureY = chargingPilesPositionConfig.postureY || 0;
7316
7741
  postureTheta = chargingPilesPositionConfig.postureTheta || 0;
7317
7742
  }
7318
- console.log('updatePositionByLastPosition->', postureX, postureY, postureTheta, chargingPilesPositionConfig);
7319
7743
  // 检查是否需要更新图片
7320
7744
  this.updateMowerImage(chargingPilesPositionConfig);
7321
7745
  // 立即更新位置
@@ -7332,7 +7756,6 @@ class MowerPositionManager {
7332
7756
  const postureX = positionConfig?.postureX || this.lastPosition?.x || 0;
7333
7757
  const postureY = positionConfig?.postureY || this.lastPosition?.y || 0;
7334
7758
  const postureTheta = positionConfig?.postureTheta || this.lastPosition?.rotation || 0;
7335
- console.log('updatePosition manager', JSON.stringify(this.currentPosition), this.currentPosition, !this.currentPosition, positionConfig, this.lastPosition, animationTime);
7336
7759
  // 停止当前动画(如果有)
7337
7760
  this.stopAnimation();
7338
7761
  // 第一个点
@@ -7342,7 +7765,6 @@ class MowerPositionManager {
7342
7765
  y: postureY,
7343
7766
  rotation: postureTheta,
7344
7767
  };
7345
- console.log('updatePosition first->', this.currentPosition);
7346
7768
  this.setElementPosition(this.currentPosition.x, this.currentPosition.y, this.currentPosition.rotation);
7347
7769
  return;
7348
7770
  }
@@ -7364,7 +7786,7 @@ class MowerPositionManager {
7364
7786
  const imgElement = this.mowerElement.querySelector('img');
7365
7787
  if (!imgElement)
7366
7788
  return;
7367
- const imageSrc = getMowerImage(positonConfig, this.modelType);
7789
+ const imageSrc = getMowerImage(positonConfig, this.modelType, this.hasEdger);
7368
7790
  if (imageSrc) {
7369
7791
  imgElement.src = imageSrc;
7370
7792
  imgElement.style.display = 'block';
@@ -7451,12 +7873,10 @@ class MowerPositionManager {
7451
7873
  y: this.onlyUpdateTheta ? 0 : this.targetPosition.y - this.startPosition.y,
7452
7874
  rotation: radNormalize(targetTheta - startTheta),
7453
7875
  };
7454
- console.log('startAnimationToPosition-->', this.deltaPosition, this.onlyUpdateTheta, this.targetPosition, this.startPosition);
7455
7876
  // 开始动画循环
7456
7877
  this.animateStep();
7457
7878
  }
7458
7879
  forceUpdatePosition() {
7459
- console.log('forceUpdatePosition-->', this.currentPosition, this.targetPosition, this.startPosition);
7460
7880
  this.animateStep();
7461
7881
  }
7462
7882
  /**
@@ -7597,15 +8017,40 @@ function throttleAdvanced(func, delay, options = { leading: true, trailing: true
7597
8017
  }
7598
8018
  };
7599
8019
  }
8020
+ /**
8021
+ * 检测当前设备是否为移动设备
8022
+ * @returns {boolean} 如果是移动设备返回true,否则返回false
8023
+ */
8024
+ function isMobileDevice() {
8025
+ // 确保在浏览器环境中运行
8026
+ if (typeof window === 'undefined' || typeof navigator === 'undefined') {
8027
+ return false;
8028
+ }
8029
+ // 检查用户代理字符串
8030
+ const userAgent = navigator.userAgent.toLowerCase();
8031
+ const mobileKeywords = [
8032
+ 'android', 'webos', 'iphone', 'ipad', 'ipod',
8033
+ 'blackberry', 'windows phone', 'mobile'
8034
+ ];
8035
+ const isMobileUserAgent = mobileKeywords.some(keyword => userAgent.includes(keyword));
8036
+ // 检查触摸屏支持
8037
+ const hasTouchScreen = 'ontouchstart' in window ||
8038
+ (navigator.maxTouchPoints && navigator.maxTouchPoints > 0);
8039
+ // 检查屏幕尺寸(移动设备通常屏幕较小)
8040
+ const isSmallScreen = window.innerWidth <= 768;
8041
+ // 综合判断:用户代理包含移动设备关键词,或者有触摸屏且屏幕较小
8042
+ return isMobileUserAgent || (hasTouchScreen && isSmallScreen);
8043
+ }
7600
8044
 
7601
8045
  // Google Maps 叠加层类 - 带编辑功能
7602
8046
  class MowerMapOverlay {
7603
- constructor(bounds, mapData, partitionBoundary, mowerPositionConfig, modelType, pathData, isEditMode = false, mapConfig = {}, antennaConfig = {}, mowPartitionData = null, defaultTransform, onMapLoad, onPathLoad, dragCallbacks) {
8047
+ constructor(bounds, mapData, partitionBoundary, mowerPositionConfig, modelType, pathData, isEditMode = false, unitType = UnitsType.Imperial, language = 'en', mapConfig = {}, antennaConfig = {}, mowPartitionData = null, defaultTransform, onMapLoad, onPathLoad, dragCallbacks) {
7604
8048
  this.div = null;
7605
8049
  this.svgMapView = null;
7606
8050
  this.offscreenContainer = null;
7607
8051
  this.overlayView = null;
7608
8052
  this.defaultTransform = { x: 0, y: 0, rotation: 0 };
8053
+ this.hasEdger = false;
7609
8054
  // boundary数据
7610
8055
  this.boundaryData = [];
7611
8056
  // 边界标签管理器
@@ -7648,6 +8093,8 @@ class MowerMapOverlay {
7648
8093
  this.partitionBoundary = partitionBoundary;
7649
8094
  this.pathData = pathData;
7650
8095
  this.isEditMode = isEditMode;
8096
+ this.unitType = unitType;
8097
+ this.language = language;
7651
8098
  this.mapConfig = mapConfig;
7652
8099
  this.antennaConfig = antennaConfig;
7653
8100
  this.onMapLoad = onMapLoad;
@@ -7694,7 +8141,6 @@ class MowerMapOverlay {
7694
8141
  this.isUserAnimation = animationTime > 0;
7695
8142
  // 更新割草机位置配置
7696
8143
  this.mowerPositionConfig = positionConfig;
7697
- console.log('updatePosition overlay', positionConfig);
7698
8144
  // 更新割草机位置管理器
7699
8145
  if (this.mowerPositionManager) {
7700
8146
  this.mowerPositionManager.updatePosition(positionConfig, animationTime);
@@ -7712,6 +8158,12 @@ class MowerMapOverlay {
7712
8158
  this.overlayView.setMap(map);
7713
8159
  }
7714
8160
  }
8161
+ setEdger(edger) {
8162
+ this.hasEdger = edger;
8163
+ if (this.mowerPositionManager) {
8164
+ this.mowerPositionManager.setEdger(edger);
8165
+ }
8166
+ }
7715
8167
  getMap() {
7716
8168
  return this.overlayView ? this.overlayView.getMap() : null;
7717
8169
  }
@@ -7739,7 +8191,6 @@ class MowerMapOverlay {
7739
8191
  this.svgMapView?.renderLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
7740
8192
  }
7741
8193
  onAdd() {
7742
- console.log('onAdd');
7743
8194
  // 创建包含SVG的div
7744
8195
  this.div = document.createElement('div');
7745
8196
  this.div.style.borderStyle = 'none';
@@ -7807,7 +8258,10 @@ class MowerMapOverlay {
7807
8258
  if (!this.div || !this.svgMapView)
7808
8259
  return;
7809
8260
  // 创建边界标签管理器
7810
- this.boundaryLabelsManager = new BoundaryLabelsManager(this.svgMapView, this.boundaryData);
8261
+ this.boundaryLabelsManager = new BoundaryLabelsManager(this.svgMapView, this.boundaryData, {
8262
+ unitType: this.unitType,
8263
+ language: this.language,
8264
+ });
7811
8265
  // 设置叠加层div引用
7812
8266
  this.boundaryLabelsManager.setOverlayDiv(this.div);
7813
8267
  // 添加所有边界标签
@@ -7851,11 +8305,10 @@ class MowerMapOverlay {
7851
8305
  if (!this.div || !this.svgMapView)
7852
8306
  return;
7853
8307
  // 创建割草机位置管理器,传入动画完成回调
7854
- this.mowerPositionManager = new MowerPositionManager(this.svgMapView, this.mowerPositionConfig, this.modelType, this.div, () => {
7855
- console.log('动画完成');
7856
- }, this.updatePathDataByMowingPositionThrottled.bind(this));
8308
+ this.mowerPositionManager = new MowerPositionManager(this.svgMapView, this.mowerPositionConfig, this.modelType, this.div, () => { }, this.updatePathDataByMowingPositionThrottled.bind(this));
7857
8309
  // 设置叠加层div引用
7858
8310
  this.mowerPositionManager.setOverlayDiv(this.div);
8311
+ this.mowerPositionManager.setEdger(this.hasEdger);
7859
8312
  // 获取容器并添加到主div
7860
8313
  const container = this.mowerPositionManager.getElement();
7861
8314
  if (container) {
@@ -7935,7 +8388,7 @@ class MowerMapOverlay {
7935
8388
  this.rotateHandle.style.pointerEvents = 'auto';
7936
8389
  this.rotateHandle.innerHTML = DEFAULT_ROTATE_ICON;
7937
8390
  this.editContainer.appendChild(this.rotateHandle);
7938
- // 创建拖拽手柄(左上角)
8391
+ // 创建拖拽手柄(左下角)- 仅在移动设备上显示
7939
8392
  this.dragHandle = document.createElement('div');
7940
8393
  this.dragHandle.style.position = 'absolute';
7941
8394
  this.dragHandle.style.bottom = '-20px';
@@ -7946,6 +8399,10 @@ class MowerMapOverlay {
7946
8399
  this.dragHandle.style.zIndex = EDIT_STYLES.Z_INDEX.HANDLE;
7947
8400
  this.dragHandle.style.pointerEvents = 'auto';
7948
8401
  this.dragHandle.innerHTML = DEFAULT_DRAG_ICON;
8402
+ // 在PC设备上隐藏拖拽手柄
8403
+ if (!isMobileDevice()) {
8404
+ this.dragHandle.style.display = 'none';
8405
+ }
7949
8406
  this.editContainer.appendChild(this.dragHandle);
7950
8407
  // 将编辑容器添加到主div
7951
8408
  this.div.appendChild(this.editContainer);
@@ -7987,7 +8444,6 @@ class MowerMapOverlay {
7987
8444
  this.boundaryLabelsManager.collapseAllLabels();
7988
8445
  }
7989
8446
  this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
7990
- console.log('开始旋转操作');
7991
8447
  });
7992
8448
  // 旋转手柄的触摸事件
7993
8449
  this.rotateHandle.addEventListener('touchstart', (e) => {
@@ -8003,39 +8459,41 @@ class MowerMapOverlay {
8003
8459
  this.boundaryLabelsManager.collapseAllLabels();
8004
8460
  }
8005
8461
  this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8006
- console.log('开始旋转操作(触摸)');
8007
- }, { passive: false });
8008
- // 拖拽手柄的鼠标事件
8009
- this.dragHandle.addEventListener('mousedown', (e) => {
8010
- e.preventDefault();
8011
- e.stopPropagation();
8012
- e.stopImmediatePropagation();
8013
- this.isDragging = true;
8014
- this.startPos = { x: e.clientX, y: e.clientY };
8015
- this.dragHandle.style.cursor = 'grabbing';
8016
- // 开始编辑时关闭所有展开的边界标签
8017
- if (this.boundaryLabelsManager) {
8018
- this.boundaryLabelsManager.collapseAllLabels();
8019
- }
8020
- this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8021
- console.log('开始拖动操作(通过手柄)');
8022
- });
8023
- // 拖拽手柄的触摸事件
8024
- this.dragHandle.addEventListener('touchstart', (e) => {
8025
- e.preventDefault();
8026
- e.stopPropagation();
8027
- e.stopImmediatePropagation();
8028
- this.isDragging = true;
8029
- const touch = e.touches[0];
8030
- this.startPos = { x: touch.clientX, y: touch.clientY };
8031
- this.dragHandle.style.cursor = 'grabbing';
8032
- this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8033
- console.log('开始拖动操作(通过手柄,触摸)');
8034
8462
  }, { passive: false });
8463
+ // 拖拽手柄的鼠标事件 - 仅在移动设备上启用
8464
+ if (isMobileDevice()) {
8465
+ this.dragHandle.addEventListener('mousedown', (e) => {
8466
+ e.preventDefault();
8467
+ e.stopPropagation();
8468
+ e.stopImmediatePropagation();
8469
+ this.isDragging = true;
8470
+ this.startPos = { x: e.clientX, y: e.clientY };
8471
+ this.dragHandle.style.cursor = 'grabbing';
8472
+ // 开始编辑时关闭所有展开的边界标签
8473
+ if (this.boundaryLabelsManager) {
8474
+ this.boundaryLabelsManager.collapseAllLabels();
8475
+ }
8476
+ this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8477
+ });
8478
+ // 拖拽手柄的触摸事件
8479
+ this.dragHandle.addEventListener('touchstart', (e) => {
8480
+ e.preventDefault();
8481
+ e.stopPropagation();
8482
+ e.stopImmediatePropagation();
8483
+ this.isDragging = true;
8484
+ const touch = e.touches[0];
8485
+ this.startPos = { x: touch.clientX, y: touch.clientY };
8486
+ this.dragHandle.style.cursor = 'grabbing';
8487
+ this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
8488
+ }, { passive: false });
8489
+ }
8035
8490
  // 编辑容器的鼠标事件(整个区域拖拽)
8036
8491
  this.editContainer.addEventListener('mousedown', (e) => {
8037
- console.log('开始拖动操作(整个叠加层)');
8038
- if (e.target === this.dragHandle || e.target === this.rotateHandle) {
8492
+ // 在移动设备上,检查是否点击了拖拽手柄或旋转手柄
8493
+ // 在PC设备上,只检查旋转手柄(拖拽手柄已隐藏)
8494
+ const isDragHandleClick = isMobileDevice() && e.target === this.dragHandle;
8495
+ const isRotateHandleClick = e.target === this.rotateHandle;
8496
+ if (isDragHandleClick || isRotateHandleClick) {
8039
8497
  return;
8040
8498
  }
8041
8499
  e.preventDefault();
@@ -8052,8 +8510,11 @@ class MowerMapOverlay {
8052
8510
  });
8053
8511
  // 编辑容器的触摸事件(整个区域拖拽)
8054
8512
  this.editContainer.addEventListener('touchstart', (e) => {
8055
- console.log('开始拖动操作(整个叠加层,触摸)');
8056
- if (e.target === this.dragHandle || e.target === this.rotateHandle) {
8513
+ // 在移动设备上,检查是否点击了拖拽手柄或旋转手柄
8514
+ // 在PC设备上,只检查旋转手柄(拖拽手柄已隐藏)
8515
+ const isDragHandleClick = isMobileDevice() && e.target === this.dragHandle;
8516
+ const isRotateHandleClick = e.target === this.rotateHandle;
8517
+ if (isDragHandleClick || isRotateHandleClick) {
8057
8518
  return;
8058
8519
  }
8059
8520
  e.preventDefault();
@@ -8113,7 +8574,6 @@ class MowerMapOverlay {
8113
8574
  e.preventDefault();
8114
8575
  e.stopPropagation();
8115
8576
  e.stopImmediatePropagation();
8116
- console.log('结束编辑操作');
8117
8577
  }
8118
8578
  // 如果是拖拽结束,将像素偏移量转换为地理坐标偏移量
8119
8579
  if (this.isDragging) {
@@ -8135,7 +8595,6 @@ class MowerMapOverlay {
8135
8595
  e.preventDefault();
8136
8596
  e.stopPropagation();
8137
8597
  e.stopImmediatePropagation();
8138
- console.log('结束编辑操作(触摸)');
8139
8598
  }
8140
8599
  // 如果是拖拽结束,将像素偏移量转换为地理坐标偏移量
8141
8600
  if (this.isDragging) {
@@ -8247,7 +8706,6 @@ class MowerMapOverlay {
8247
8706
  this.div.style.transform = transform;
8248
8707
  // 更新鼠标起始位置为当前位置,为下次计算做准备
8249
8708
  this.startPos = { x: mouseCurrentX, y: mouseCurrentY };
8250
- console.log('旋转角度:', this.currentRotation, '角度增量:', angleDifferenceDegrees);
8251
8709
  }
8252
8710
  // 将像素偏移量转换为地理坐标偏移量
8253
8711
  convertPixelOffsetToLatLng() {
@@ -8277,14 +8735,6 @@ class MowerMapOverlay {
8277
8735
  // 累积更新地理坐标偏移量(不是直接赋值!)
8278
8736
  this.latLngOffset.lat += latOffset;
8279
8737
  this.latLngOffset.lng += lngOffset;
8280
- console.log('精确转换偏移量:', {
8281
- pixelOffset: this.tempPixelOffset,
8282
- centerLatLng: { lat: centerLatLng.lat(), lng: centerLatLng.lng() },
8283
- offsetLatLng: { lat: offsetLatLng.lat(), lng: offsetLatLng.lng() },
8284
- latOffset,
8285
- lngOffset,
8286
- newLatLngOffset: this.latLngOffset,
8287
- });
8288
8738
  // 重置临时像素偏移量
8289
8739
  this.tempPixelOffset = { x: 0, y: 0 };
8290
8740
  this.draw();
@@ -8322,8 +8772,6 @@ class MowerMapOverlay {
8322
8772
  editData: editData,
8323
8773
  timestamp: new Date().toISOString(),
8324
8774
  };
8325
- // 在这里可以添加保存逻辑,比如发送到服务器
8326
- console.log('保存编辑数据:', saveData);
8327
8775
  // 显示保存成功提示
8328
8776
  this.showSaveSuccess();
8329
8777
  return saveData;
@@ -8416,6 +8864,9 @@ class MowerMapOverlay {
8416
8864
  y: transform.y,
8417
8865
  rotation: transform.rotation,
8418
8866
  };
8867
+ // defaultTransform的x对应经度偏移量,y对应纬度偏移量
8868
+ this.latLngOffset.lng = this.defaultTransform.x;
8869
+ this.latLngOffset.lat = this.defaultTransform.y;
8419
8870
  this.setManagerRotation(this.currentRotation);
8420
8871
  this.draw();
8421
8872
  }
@@ -8455,7 +8906,6 @@ class MowerMapOverlay {
8455
8906
  if (this.pathData && this.svgMapView) {
8456
8907
  this.loadPathData(this.pathData, this.mowPartitionData);
8457
8908
  }
8458
- console.log('initializeSvgMapView');
8459
8909
  // 刷新绘制图层
8460
8910
  this.svgMapView.refresh();
8461
8911
  // 获取生成的SVG并添加到叠加层div中
@@ -8611,7 +9061,6 @@ class MowerMapOverlay {
8611
9061
  this.boundaryLabelsManager?.updateBoundaryData(boundaryData);
8612
9062
  }
8613
9063
  draw() {
8614
- console.log('ondraw');
8615
9064
  // 防御性检查:如果this.div为null,说明onAdd还没被调用,直接返回
8616
9065
  if (!this.div) {
8617
9066
  return;
@@ -8879,7 +9328,7 @@ const getValidGpsBounds = (mapData, rotation = 0) => {
8879
9328
  // 默认配置
8880
9329
  const defaultMapConfig = DEFAULT_STYLES;
8881
9330
  // 地图渲染器组件
8882
- const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pathJson, realTimeData, antennaConfig, onMapLoad, onPathLoad, onError, className, style, googleMapInstance, isEditMode = false, dragCallbacks, defaultTransform, debug = false, }, ref) => {
9331
+ const MowerMapRenderer = 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) => {
8883
9332
  const [elementCount, setElementCount] = useState(0);
8884
9333
  const [pathCount, setPathCount] = useState(0);
8885
9334
  const [currentError, setCurrentError] = useState(null);
@@ -8887,7 +9336,7 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8887
9336
  // const mapRef = useMap();
8888
9337
  const [isGoogleMapsReady, setIsGoogleMapsReady] = useState(false);
8889
9338
  const [hasInitializedBounds, setHasInitializedBounds] = useState(false);
8890
- const { clearSubBoundaryBorder, clearObstacles } = useSubBoundaryBorderStore();
9339
+ const { clearSubBoundaryBorder, clearObstacles, clearSvgElements } = useSubBoundaryBorderStore();
8891
9340
  const currentProcessMowingStatusRef = useRef(false);
8892
9341
  const { updateProcessStateIsMowing, processStateIsMowing } = useProcessMowingState();
8893
9342
  const [mowPartitionData, setMowPartitionData] = useState(null);
@@ -8921,7 +9370,7 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8921
9370
  postureX: 0,
8922
9371
  postureY: 0,
8923
9372
  postureTheta: 0,
8924
- vehicleState: RobotStatus.PARKED,
9373
+ vehicleState: RobotStatus.DISCONNECTED,
8925
9374
  };
8926
9375
  let currentPositionData;
8927
9376
  if (realTimeData.length === 1 && realTimeData[0].type === RealTimeDataType.LOCATION) {
@@ -8947,10 +9396,9 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8947
9396
  lastPostureY: currentPositionData?.lastPostureY
8948
9397
  ? Number(currentPositionData.lastPostureY)
8949
9398
  : 0,
8950
- vehicleState: currentPositionData?.vehicleState || RobotStatus.CHARGING,
9399
+ vehicleState: currentPositionData?.vehicleState || RobotStatus.DISCONNECTED,
8951
9400
  };
8952
9401
  }, [realTimeData, modelType]);
8953
- console.log('mowerPositionData', mowerPositionData);
8954
9402
  // 处理错误
8955
9403
  const handleError = (error) => {
8956
9404
  setCurrentError(error);
@@ -8975,7 +9423,6 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8975
9423
  const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
8976
9424
  new window.google.maps.LatLng(neLat, neLng) // 东北角
8977
9425
  );
8978
- console.log('fitBounds----->', googleBounds);
8979
9426
  mapRef.fitBounds(googleBounds);
8980
9427
  }, [mapJson, mapRef, defaultTransform]);
8981
9428
  // 初始化Google Maps叠加层
@@ -9016,9 +9463,8 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9016
9463
  overlayRef.current.setMap(null);
9017
9464
  overlayRef.current = null;
9018
9465
  }
9019
- console.log('initializeGoogleMapsOverlay', mowPartitionData);
9020
9466
  // 创建叠加层
9021
- const overlay = new MowerMapOverlay(googleBounds, mapJson, partitionBoundary, mowerPositionData, modelType, pathJson || {}, isEditMode, mergedMapConfig, mergedAntennaConfig, null, defaultTransform, (count) => {
9467
+ const overlay = new MowerMapOverlay(googleBounds, mapJson, partitionBoundary, mowerPositionData, modelType, pathJson || {}, isEditMode, unitType, language, mergedMapConfig, mergedAntennaConfig, null, defaultTransform, (count) => {
9022
9468
  setElementCount(count);
9023
9469
  onMapLoad?.(count);
9024
9470
  }, (count) => {
@@ -9028,6 +9474,7 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9028
9474
  // 设置地图
9029
9475
  overlay.setMap(mapInstance);
9030
9476
  overlayRef.current = overlay;
9477
+ overlay.setEdger(edger);
9031
9478
  // 只在首次初始化时自适应视图
9032
9479
  if (!hasInitializedBounds) {
9033
9480
  mapInstance.fitBounds(googleBounds);
@@ -9051,7 +9498,7 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9051
9498
  postureY: chargingPiles?.originalData.position[1],
9052
9499
  postureTheta: chargingPiles?.originalData.direction - Math.PI || 0,
9053
9500
  }, 0);
9054
- }, [mapJson]);
9501
+ }, [mapJson, mowerPositionData]);
9055
9502
  // 初始化效果
9056
9503
  useEffect(() => {
9057
9504
  initializeGoogleMapsOverlay();
@@ -9059,6 +9506,7 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9059
9506
  return () => {
9060
9507
  clearSubBoundaryBorder();
9061
9508
  clearObstacles();
9509
+ clearSvgElements();
9062
9510
  updateProcessStateIsMowing(false);
9063
9511
  currentProcessMowingStatusRef.current = false;
9064
9512
  if (overlayRef.current) {
@@ -9090,7 +9538,6 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9090
9538
  const isOffLine = mowerPositionData.vehicleState === RobotStatus.DISCONNECTED;
9091
9539
  const isInChargingPile = inChargingPiles.includes(mowerPositionData.vehicleState);
9092
9540
  // 如果在充电桩上,则直接更新位置到充电桩的位置
9093
- console.log('usefeect mowerPositionData----->', mowerPositionData);
9094
9541
  if (isInChargingPile) {
9095
9542
  overlayRef.current.updatePosition({
9096
9543
  ...mowerPositionData,
@@ -9123,7 +9570,6 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9123
9570
  }
9124
9571
  }
9125
9572
  else {
9126
- console.log('hook updatePosition----->', mowerPositionData);
9127
9573
  overlayRef.current.updatePosition(mowerPositionData, isStandby ? 0 : 2000);
9128
9574
  }
9129
9575
  }
@@ -9242,7 +9688,6 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9242
9688
  if (!realTimeData || realTimeData.length === 0 || !Array.isArray(realTimeData)) {
9243
9689
  return;
9244
9690
  }
9245
- console.log('usefeect realTimeData----->', realTimeData, mapJson, pathJson, overlayRef.current);
9246
9691
  let curMowPartitionData = mowPartitionData;
9247
9692
  // realtime中包含当前割草任务的数据,根据数据进行path路径和边界的高亮操作,
9248
9693
  const mowingPartition = realTimeData.find((item) => item.type === RealTimeDataType.PARTITION);
@@ -9250,9 +9695,8 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9250
9695
  setMowPartitionData(mowingPartition);
9251
9696
  curMowPartitionData = mowingPartition;
9252
9697
  }
9253
- const positionData = realTimeData?.find(item => item?.type === RealTimeDataType.LOCATION);
9254
- const statusData = realTimeData?.find(item => item?.type === RealTimeDataType.STATUS);
9255
- console.log('current->1', positionData, statusData);
9698
+ const positionData = realTimeData?.find((item) => item?.type === RealTimeDataType.LOCATION);
9699
+ const statusData = realTimeData?.find((item) => item?.type === RealTimeDataType.STATUS);
9256
9700
  if (statusData || positionData) {
9257
9701
  const currentStatus = statusData?.vehicleState || positionData?.vehicleState;
9258
9702
  // 车辆回桩不会回传最后的park的位置,所以根据实时数据的状态数据判断车辆回到桩上
@@ -9262,32 +9706,28 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9262
9706
  else if (currentStatus === RobotStatus.WORKING) {
9263
9707
  // 兜底收不到割草地块的实时数据,使用状态来兜底
9264
9708
  overlayRef.current.resetBorderLayerHighlight();
9265
- setMowPartitionData(null);
9266
- curMowPartitionData = null;
9709
+ setMowPartitionData({});
9710
+ curMowPartitionData = {};
9267
9711
  }
9268
- else if (currentStatus === RobotStatus.MOWING && (curMowPartitionData && !curMowPartitionData?.partitionIds)) {
9712
+ else if (currentStatus === RobotStatus.MOWING &&
9713
+ curMowPartitionData &&
9714
+ !curMowPartitionData?.partitionIds) {
9269
9715
  // 如果当前是割草状态,但是地块数据初始化过且不存在则认为是全局割草,则把所有地块都高亮
9270
- const allPartitionIds = mapJson?.sub_maps?.map(item => item?.id);
9271
- console.log('allPartitionIds->', allPartitionIds, mapJson);
9716
+ const allPartitionIds = mapJson?.sub_maps?.map((item) => item?.id);
9272
9717
  setMowPartitionData({
9273
- partitionIds: allPartitionIds
9718
+ partitionIds: allPartitionIds,
9274
9719
  });
9275
9720
  curMowPartitionData = {
9276
- partitionIds: allPartitionIds
9721
+ partitionIds: allPartitionIds,
9277
9722
  };
9278
9723
  }
9279
9724
  }
9280
- if (!mapJson ||
9281
- !pathJson ||
9282
- !overlayRef.current)
9725
+ if (!mapJson || !pathJson || !overlayRef.current)
9283
9726
  return;
9284
9727
  // 根据后端推送的实时数据,进行不同处理
9285
- // TODO:需要根据返回的数据,处理车辆的移动位置
9286
- console.log('realTimeData----->', realTimeData, curMowPartitionData);
9287
9728
  if (curMowPartitionData) {
9288
9729
  const isMowing = curMowPartitionData?.partitionIds && curMowPartitionData.partitionIds.length > 0;
9289
9730
  overlayRef.current.updateMowPartitionData(curMowPartitionData);
9290
- console.log('isMowing', isMowing, curMowPartitionData);
9291
9731
  if (!isMowing) {
9292
9732
  overlayRef.current.resetBorderLayerHighlight();
9293
9733
  }
@@ -9325,7 +9765,6 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9325
9765
  }
9326
9766
  }, [realTimeData, mapJson, pathJson]);
9327
9767
  useEffect(() => {
9328
- console.log('defaultTransform----->', defaultTransform, overlayRef.current, mapJson);
9329
9768
  if (!overlayRef.current || !defaultTransform)
9330
9769
  return;
9331
9770
  overlayRef.current?.setTransform(defaultTransform);
@@ -9340,6 +9779,11 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
9340
9779
  );
9341
9780
  mapRef.fitBounds(googleBounds);
9342
9781
  }, [defaultTransform]);
9782
+ useEffect(() => {
9783
+ if (!overlayRef || !overlayRef.current)
9784
+ return;
9785
+ overlayRef.current.setEdger(edger);
9786
+ }, [edger]);
9343
9787
  // 提供ref方法
9344
9788
  useImperativeHandle(ref, () => ({
9345
9789
  fitToView: () => {