@fleet-frontend/mower-maps 0.0.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.
Files changed (97) hide show
  1. package/dist/config/constants.d.ts +3 -25
  2. package/dist/config/constants.d.ts.map +1 -1
  3. package/dist/config/styles.d.ts +3 -2
  4. package/dist/config/styles.d.ts.map +1 -1
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.esm.js +1995 -1084
  8. package/dist/index.js +1993 -1082
  9. package/dist/processor/MapDataProcessor.d.ts +1 -1
  10. package/dist/processor/MapDataProcessor.d.ts.map +1 -1
  11. package/dist/processor/PathDataProcessor.d.ts +3 -18
  12. package/dist/processor/PathDataProcessor.d.ts.map +1 -1
  13. package/dist/processor/builder/AntennaDataBuilder.d.ts +2 -1
  14. package/dist/processor/builder/AntennaDataBuilder.d.ts.map +1 -1
  15. package/dist/processor/builder/BoundaryDataBuilder.d.ts +1 -1
  16. package/dist/processor/builder/BoundaryDataBuilder.d.ts.map +1 -1
  17. package/dist/processor/builder/ChannelDataBuilder.d.ts +1 -1
  18. package/dist/processor/builder/ChannelDataBuilder.d.ts.map +1 -1
  19. package/dist/processor/builder/ChargingPileDataBuilder.d.ts +1 -1
  20. package/dist/processor/builder/ChargingPileDataBuilder.d.ts.map +1 -1
  21. package/dist/processor/builder/ObstacleDataBuilder.d.ts +2 -1
  22. package/dist/processor/builder/ObstacleDataBuilder.d.ts.map +1 -1
  23. package/dist/processor/builder/PathDataBuilder.d.ts +6 -5
  24. package/dist/processor/builder/PathDataBuilder.d.ts.map +1 -1
  25. package/dist/processor/builder/PointDataBuilder.d.ts +2 -1
  26. package/dist/processor/builder/PointDataBuilder.d.ts.map +1 -1
  27. package/dist/processor/builder/SvgElementDataBuilder.d.ts +1 -1
  28. package/dist/processor/builder/SvgElementDataBuilder.d.ts.map +1 -1
  29. package/dist/processor/builder/VisionOffDataBuilder.d.ts +2 -1
  30. package/dist/processor/builder/VisionOffDataBuilder.d.ts.map +1 -1
  31. package/dist/processor/index.d.ts +2 -1
  32. package/dist/processor/index.d.ts.map +1 -1
  33. package/dist/render/AntennaManager.d.ts +7 -24
  34. package/dist/render/AntennaManager.d.ts.map +1 -1
  35. package/dist/render/BoundaryLabelsManager.d.ts +12 -20
  36. package/dist/render/BoundaryLabelsManager.d.ts.map +1 -1
  37. package/dist/render/ChargingPileManager.d.ts +10 -20
  38. package/dist/render/ChargingPileManager.d.ts.map +1 -1
  39. package/dist/render/MowerMapOverlay.d.ts +53 -8
  40. package/dist/render/MowerMapOverlay.d.ts.map +1 -1
  41. package/dist/render/MowerMapRenderer.d.ts.map +1 -1
  42. package/dist/render/{MowerPostionManager.d.ts → MowerPositionManager.d.ts} +16 -11
  43. package/dist/render/MowerPositionManager.d.ts.map +1 -0
  44. package/dist/render/SvgMapView.d.ts +15 -26
  45. package/dist/render/SvgMapView.d.ts.map +1 -1
  46. package/dist/render/layers/BoundaryBorderLayer.d.ts +11 -0
  47. package/dist/render/layers/BoundaryBorderLayer.d.ts.map +1 -1
  48. package/dist/render/layers/ChannelLayer.d.ts.map +1 -1
  49. package/dist/render/layers/DrawLayer.d.ts +1 -1
  50. package/dist/render/layers/DrawLayer.d.ts.map +1 -1
  51. package/dist/render/layers/PathLayer.d.ts +11 -2
  52. package/dist/render/layers/PathLayer.d.ts.map +1 -1
  53. package/dist/render/layers/SvgElementLayer.d.ts.map +1 -1
  54. package/dist/render/layers/index.d.ts +0 -1
  55. package/dist/render/layers/index.d.ts.map +1 -1
  56. package/dist/render/layers/types.d.ts +1 -36
  57. package/dist/render/layers/types.d.ts.map +1 -1
  58. package/dist/store/processMowingState.d.ts +4 -0
  59. package/dist/store/processMowingState.d.ts.map +1 -0
  60. package/dist/store/useSubBoundaryBorderStore.d.ts +9 -5
  61. package/dist/store/useSubBoundaryBorderStore.d.ts.map +1 -1
  62. package/dist/types/constants.d.ts +38 -0
  63. package/dist/types/constants.d.ts.map +1 -0
  64. package/dist/types/index.d.ts +6 -0
  65. package/dist/types/index.d.ts.map +1 -1
  66. package/dist/types/layers.d.ts +50 -0
  67. package/dist/types/layers.d.ts.map +1 -0
  68. package/dist/types/processor.d.ts +26 -0
  69. package/dist/types/processor.d.ts.map +1 -0
  70. package/dist/types/realTime.d.ts +27 -2
  71. package/dist/types/realTime.d.ts.map +1 -1
  72. package/dist/types/renderer.d.ts +28 -10
  73. package/dist/types/renderer.d.ts.map +1 -1
  74. package/dist/types/store.d.ts +22 -0
  75. package/dist/types/store.d.ts.map +1 -0
  76. package/dist/types/utils.d.ts +102 -0
  77. package/dist/types/utils.d.ts.map +1 -0
  78. package/dist/utils/boundaryUtils.d.ts +1 -29
  79. package/dist/utils/boundaryUtils.d.ts.map +1 -1
  80. package/dist/utils/common.d.ts +20 -0
  81. package/dist/utils/common.d.ts.map +1 -0
  82. package/dist/utils/coordinates.d.ts +28 -7
  83. package/dist/utils/coordinates.d.ts.map +1 -1
  84. package/dist/utils/handleRealTime.d.ts +20 -10
  85. package/dist/utils/handleRealTime.d.ts.map +1 -1
  86. package/dist/utils/mapBounds.d.ts +1 -10
  87. package/dist/utils/mapBounds.d.ts.map +1 -1
  88. package/dist/utils/math.d.ts +3 -10
  89. package/dist/utils/math.d.ts.map +1 -1
  90. package/dist/utils/mower.d.ts +5 -5
  91. package/dist/utils/mower.d.ts.map +1 -1
  92. package/dist/utils/pathSegments.d.ts +1 -15
  93. package/dist/utils/pathSegments.d.ts.map +1 -1
  94. package/dist/utils/unionFind.d.ts +49 -0
  95. package/dist/utils/unionFind.d.ts.map +1 -0
  96. package/package.json +1 -1
  97. package/dist/render/MowerPostionManager.d.ts.map +0 -1
package/dist/index.esm.js CHANGED
@@ -1,5 +1,135 @@
1
- import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React, { forwardRef, useRef, useState, useMemo, useEffect, useImperativeHandle } from 'react';
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import React, { forwardRef, useState, useRef, useMemo, useCallback, useEffect, useImperativeHandle } from 'react';
3
+
4
+ /**
5
+ * 常量和枚举类型定义
6
+ */
7
+ /**
8
+ * 机器人状态枚举
9
+ */
10
+ var RobotStatus;
11
+ (function (RobotStatus) {
12
+ RobotStatus[RobotStatus["PARKED"] = 1] = "PARKED";
13
+ RobotStatus[RobotStatus["CHARGING"] = 2] = "CHARGING";
14
+ RobotStatus[RobotStatus["STANDBY"] = 3] = "STANDBY";
15
+ RobotStatus[RobotStatus["MOWING"] = 4] = "MOWING";
16
+ RobotStatus[RobotStatus["WORKING"] = 5] = "WORKING";
17
+ RobotStatus[RobotStatus["MAPPING"] = 6] = "MAPPING";
18
+ RobotStatus[RobotStatus["ERROR"] = 7] = "ERROR";
19
+ RobotStatus[RobotStatus["UPGRADING"] = 8] = "UPGRADING";
20
+ RobotStatus[RobotStatus["DISCONNECTED"] = 9] = "DISCONNECTED";
21
+ RobotStatus[RobotStatus["UNKNOWN"] = -1] = "UNKNOWN";
22
+ RobotStatus[RobotStatus["TASK_DELAY"] = 10] = "TASK_DELAY";
23
+ })(RobotStatus || (RobotStatus = {}));
24
+ /**
25
+ * RTK状态枚举
26
+ */
27
+ var RTK_STATE;
28
+ (function (RTK_STATE) {
29
+ RTK_STATE[RTK_STATE["LOW_RTK"] = 1] = "LOW_RTK";
30
+ RTK_STATE[RTK_STATE["MIDDLE_RTK"] = 2] = "MIDDLE_RTK";
31
+ RTK_STATE[RTK_STATE["HIGH_RTK"] = 3] = "HIGH_RTK";
32
+ RTK_STATE[RTK_STATE["NO_POSTURE"] = 10] = "NO_POSTURE";
33
+ RTK_STATE[RTK_STATE["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
34
+ RTK_STATE[RTK_STATE["OFF_LINE"] = 19] = "OFF_LINE";
35
+ })(RTK_STATE || (RTK_STATE = {}));
36
+ /**
37
+ * 实时数据类型枚举
38
+ */
39
+ var REAL_TIME_DATA_TYPE;
40
+ (function (REAL_TIME_DATA_TYPE) {
41
+ REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["LOCATION"] = 1] = "LOCATION";
42
+ REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["PROCESS"] = 2] = "PROCESS";
43
+ })(REAL_TIME_DATA_TYPE || (REAL_TIME_DATA_TYPE = {}));
44
+
45
+ /**
46
+ * 地图渲染相关常量配置
47
+ */
48
+ /**
49
+ * 缩放因子 - 将米转换为像素
50
+ * 与Python代码中的SVG比例一致
51
+ */
52
+ const SCALE_FACTOR = 50; // 50像素/米
53
+ /**
54
+ * 默认线宽设置
55
+ */
56
+ const DEFAULT_LINE_WIDTHS = {
57
+ OBSTACLE: 2,
58
+ CHARGING_PILE: 2,
59
+ CHANNEL: 2,
60
+ PATH: 20,
61
+ VISION_OFF_AREA: 2,
62
+ TIME_LIMIT_OBSTACLE: 1,
63
+ };
64
+ /**
65
+ * 默认透明度设置
66
+ */
67
+ const DEFAULT_OPACITIES = {
68
+ FULL: 1.0,
69
+ HIGH: 0.7,
70
+ MEDIUM: 0.6,
71
+ DOODLE: 0.8};
72
+ /**
73
+ * 默认半径设置
74
+ */
75
+ const DEFAULT_RADII = {
76
+ CHARGING_PILE: 12};
77
+ /**
78
+ * 图层等级
79
+ */
80
+ const LAYER_LEVELS = {
81
+ BOUNDARY: 2,
82
+ BOUNDARY_BORDER: 4};
83
+ /**
84
+ * 图层默认id
85
+ */
86
+ const LAYER_DEFAULT_TYPE = {
87
+ CHANNEL: 'channel',
88
+ BOUNDARY: 'boundary',
89
+ PATH: 'path',
90
+ BOUNDARY_BORDER: 'boundary_border',
91
+ OBSTACLE: 'obstacle',
92
+ CHARGING_PILE: 'charging_pile',
93
+ POINT: 'point',
94
+ SVG: 'svg',
95
+ VISION_OFF_AREA: 'vision_off_area',
96
+ ANTENNA: 'antenna',
97
+ };
98
+ const ISOLATED_BOUNDARY_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
99
+ <g opacity="0.6">
100
+ <rect width="24" height="24" rx="12" fill="#1E1E1F" fill-opacity="0.5"/>
101
+ <path d="M8.20573 12.4961C6.739 11.1707 4.85775 11.2284 3.60938 11.1707C6.17744 13.0156 6.05584 15.7887 5.67404 16.9446H14.327C13.837 13.1673 15.5321 10.1289 16.4408 9.08187C15.0342 9.46118 13.5794 10.8303 13.0278 11.4674C12.8102 8.94572 13.992 5.97489 14.61 4.80469C12.5235 5.92214 11.0501 8.26056 10.4938 9.56261C9.22803 7.5947 6.83894 7.17806 5.883 6.9432C8.20573 10.2373 8.00039 11.1707 8.20573 12.4961Z" fill="white"/>
102
+ <mask id="path-3-outside-1_9822_43516" maskUnits="userSpaceOnUse" x="13.2344" y="7.20545" width="12.0208" height="12.0208" fill="black">
103
+ <rect fill="white" x="13.2344" y="7.20545" width="12.0208" height="12.0208"/>
104
+ <path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z"/>
105
+ </mask>
106
+ <path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z" fill="white"/>
107
+ <path d="M20.6852 11.1208L21.7458 10.0601L21.7459 10.0601L20.6852 11.1208ZM17.8568 11.1208L16.7961 10.0601L16.7962 10.0601L17.8568 11.1208ZM17.2733 11.7043L16.2126 10.6437L16.2126 10.6436L17.2733 11.7043ZM16.8631 13.9319L17.9238 14.9926L16.381 16.5354L15.493 14.5425L16.8631 13.9319ZM17.5916 13.2034L19.0855 13.0678L19.149 13.7674L18.6523 14.2641L17.5916 13.2034ZM20.4056 12.1421L21.4662 11.0814L21.4665 11.0817L20.4056 12.1421ZM18.9699 14.5348L17.9093 13.4741L18.3741 13.0093L19.0309 13.036L18.9699 14.5348ZM18.2235 15.2812L17.6298 16.6587L15.5997 15.7837L17.1628 14.2206L18.2235 15.2812ZM20.429 14.86L21.4897 15.9207L21.4897 15.9207L20.429 14.86ZM21.0125 14.2765L22.0732 15.3371L22.0732 15.3372L21.0125 14.2765ZM20.6852 11.1208L19.6246 12.1815C19.4294 11.9862 19.1127 11.9863 18.9174 12.1815L17.8568 11.1208L16.7962 10.0601C18.163 8.69337 20.379 8.6934 21.7458 10.0601L20.6852 11.1208ZM17.8568 11.1208L18.9175 12.1814L18.334 12.7649L17.2733 11.7043L16.2126 10.6436L16.7961 10.0601L17.8568 11.1208ZM17.2733 11.7043L18.334 12.7649C18.1877 12.9112 18.1488 13.1318 18.2333 13.3214L16.8631 13.9319L15.493 14.5425C14.9228 13.2629 15.1537 11.7026 16.2126 10.6437L17.2733 11.7043ZM16.8631 13.9319L15.8025 12.8713L16.531 12.1428L17.5916 13.2034L18.6523 14.2641L17.9238 14.9926L16.8631 13.9319ZM17.5916 13.2034L16.0978 13.339C16.0334 12.6302 16.2727 11.8914 16.8196 11.3445L17.8803 12.4052L18.9409 13.4658C19.051 13.3558 19.098 13.206 19.0855 13.0678L17.5916 13.2034ZM17.8803 12.4052L16.8196 11.3445L17.5067 10.6574L18.5674 11.7181L19.628 12.7788L18.9409 13.4658L17.8803 12.4052ZM18.5674 11.7181L17.5067 10.6574C18.483 9.68112 20.0659 9.68112 21.0422 10.6574L19.9816 11.7181L18.9209 12.7788C19.1162 12.974 19.4328 12.974 19.628 12.7788L18.5674 11.7181ZM19.9816 11.7181L21.0422 10.6574L21.4662 11.0814L20.4056 12.1421L19.3449 13.2027L18.9209 12.7788L19.9816 11.7181ZM20.4056 12.1421L21.4665 11.0817C22.4419 12.0577 22.4428 13.6404 21.4662 14.617L20.4056 13.5563L19.3449 12.4956C19.1493 12.6913 19.1498 13.0076 19.3446 13.2024L20.4056 12.1421ZM20.4056 13.5563L21.4662 14.617L20.7791 15.304L19.7185 14.2434L18.6578 13.1827L19.3449 12.4956L20.4056 13.5563ZM19.7185 14.2434L20.7791 15.304C20.2628 15.8204 19.5762 16.0607 18.909 16.0335L18.9699 14.5348L19.0309 13.036C18.9025 13.0308 18.7629 13.0777 18.6578 13.1827L19.7185 14.2434ZM18.9699 14.5348L20.0306 15.5954L19.2841 16.3419L18.2235 15.2812L17.1628 14.2206L17.9093 13.4741L18.9699 14.5348ZM18.2235 15.2812L18.8172 13.9037C19.0042 13.9843 19.2223 13.9455 19.3684 13.7993L20.429 14.86L21.4897 15.9207C20.4427 16.9676 18.9034 17.2077 17.6298 16.6587L18.2235 15.2812ZM20.429 14.86L19.3684 13.7994L19.9519 13.2159L21.0125 14.2765L22.0732 15.3372L21.4897 15.9207L20.429 14.86ZM21.0125 14.2765L19.9518 13.2159C20.1471 13.0206 20.1471 12.704 19.9519 12.5088L21.0125 11.4481L22.0732 10.3874C23.4401 11.7543 23.4399 13.9703 22.0732 15.3371L21.0125 14.2765ZM21.0125 11.4481L19.9519 12.5088L19.6246 12.1814L20.6852 11.1208L21.7459 10.0601L22.0732 10.3874L21.0125 11.4481Z" fill="#8E8E8F" mask="url(#path-3-outside-1_9822_43516)"/>
108
+ <mask id="path-5-outside-2_9822_43516" maskUnits="userSpaceOnUse" x="9.22265" y="10.388" width="12.7279" height="12.7279" fill="black">
109
+ <rect fill="white" x="9.22265" y="10.388" width="12.7279" height="12.7279"/>
110
+ <path d="M16.1708 14.5077C15.4353 14.1778 14.5422 14.3133 13.9383 14.9172L13.3548 15.5007C12.574 16.2818 12.5738 17.5481 13.3548 18.3291L13.6821 18.6564C14.4631 19.4374 15.7295 19.4373 16.5105 18.6564L17.094 18.0729C17.6895 17.4775 17.8299 16.6003 17.5173 15.8708L16.7743 16.6138C16.7828 16.8803 16.6863 17.1494 16.4829 17.3527L15.7952 18.0405C15.4046 18.4309 14.7714 18.431 14.3809 18.0405L13.957 17.6165C13.5667 17.226 13.5666 16.5927 13.957 16.2023L14.6447 15.5145C14.8654 15.2939 15.1639 15.1979 15.452 15.2266L16.1708 14.5077Z"/>
111
+ </mask>
112
+ <path d="M16.1708 14.5077C15.4353 14.1778 14.5422 14.3133 13.9383 14.9172L13.3548 15.5007C12.574 16.2818 12.5738 17.5481 13.3548 18.3291L13.6821 18.6564C14.4631 19.4374 15.7295 19.4373 16.5105 18.6564L17.094 18.0729C17.6895 17.4775 17.8299 16.6003 17.5173 15.8708L16.7743 16.6138C16.7828 16.8803 16.6863 17.1494 16.4829 17.3527L15.7952 18.0405C15.4046 18.4309 14.7714 18.431 14.3809 18.0405L13.957 17.6165C13.5667 17.226 13.5666 16.5927 13.957 16.2023L14.6447 15.5145C14.8654 15.2939 15.1639 15.1979 15.452 15.2266L16.1708 14.5077Z" fill="white"/>
113
+ <path d="M16.1708 14.5077L16.7847 13.1391L18.7701 14.0297L17.2315 15.5684L16.1708 14.5077ZM13.3548 15.5007L12.294 14.4402L12.2941 14.44L13.3548 15.5007ZM16.5105 18.6564L17.5712 19.7171L17.5711 19.7172L16.5105 18.6564ZM17.5173 15.8708L16.4567 14.8102L18.0236 13.2433L18.8962 15.2802L17.5173 15.8708ZM16.7743 16.6138L15.2751 16.6613L15.2545 16.0123L15.7137 15.5532L16.7743 16.6138ZM15.7952 18.0405L16.8558 19.1011L16.8556 19.1013L15.7952 18.0405ZM13.957 17.6165L12.8963 18.6772L12.8959 18.6767L13.957 17.6165ZM13.957 16.2023L12.8961 15.1418L12.8963 15.1416L13.957 16.2023ZM15.452 15.2266L16.5126 16.2872L16.0102 16.7896L15.3032 16.7192L15.452 15.2266ZM16.1708 14.5077L15.5569 15.8763C15.3688 15.792 15.1469 15.8299 14.999 15.9779L13.9383 14.9172L12.8776 13.8565C13.9375 12.7967 15.5018 12.5636 16.7847 13.1391L16.1708 14.5077ZM13.9383 14.9172L14.999 15.9779L14.4155 16.5614L13.3548 15.5007L12.2941 14.44L12.8776 13.8565L13.9383 14.9172ZM13.3548 15.5007L14.4156 16.5612C14.2202 16.7567 14.2204 17.0734 14.4155 17.2685L13.3548 18.3291L12.2941 19.3898C10.9272 18.0229 10.9277 15.8069 12.294 14.4402L13.3548 15.5007ZM13.3548 18.3291L14.4155 17.2685L14.7428 17.5958L13.6821 18.6564L12.6215 19.7171L12.2941 19.3898L13.3548 18.3291ZM13.6821 18.6564L14.7428 17.5958C14.9378 17.7908 15.2546 17.791 15.45 17.5956L16.5105 18.6564L17.5711 19.7172C16.2044 21.0836 13.9884 21.084 12.6215 19.7171L13.6821 18.6564ZM16.5105 18.6564L15.4499 17.5958L16.0334 17.0123L17.094 18.0729L18.1547 19.1336L17.5712 19.7171L16.5105 18.6564ZM17.094 18.0729L16.0334 17.0123C16.1795 16.8662 16.2185 16.6481 16.1385 16.4615L17.5173 15.8708L18.8962 15.2802C19.4413 16.5526 19.1996 18.0887 18.1547 19.1336L17.094 18.0729ZM17.5173 15.8708L18.578 16.9315L17.835 17.6745L16.7743 16.6138L15.7137 15.5532L16.4567 14.8102L17.5173 15.8708ZM16.7743 16.6138L18.2736 16.5664C18.2945 17.226 18.0544 17.9026 17.5436 18.4134L16.4829 17.3527L15.4223 16.2921C15.3182 16.3961 15.2711 16.5346 15.2751 16.6613L16.7743 16.6138ZM16.4829 17.3527L17.5436 18.4134L16.8558 19.1011L15.7952 18.0405L14.7345 16.9798L15.4223 16.2921L16.4829 17.3527ZM15.7952 18.0405L16.8556 19.1013C15.8795 20.0771 14.2967 20.0776 13.3203 19.1011L14.3809 18.0405L15.4416 16.9798C15.2461 16.7844 14.9297 16.7847 14.7347 16.9797L15.7952 18.0405ZM14.3809 18.0405L13.3203 19.1011L12.8963 18.6772L13.957 17.6165L15.0176 16.5558L15.4416 16.9798L14.3809 18.0405ZM13.957 17.6165L12.8959 18.6767C11.9207 17.7008 11.92 16.1182 12.8961 15.1418L13.957 16.2023L15.0178 17.2628C15.2133 17.0672 15.2128 16.7512 15.018 16.5563L13.957 17.6165ZM13.957 16.2023L12.8963 15.1416L13.5841 14.4539L14.6447 15.5145L15.7054 16.5752L15.0176 17.2629L13.957 16.2023ZM14.6447 15.5145L13.5841 14.4539C14.1364 13.9015 14.8848 13.6626 15.6007 13.734L15.452 15.2266L15.3032 16.7192C15.443 16.7331 15.5943 16.6862 15.7054 16.5752L14.6447 15.5145ZM15.452 15.2266L14.3913 14.1659L15.1101 13.4471L16.1708 14.5077L17.2315 15.5684L16.5126 16.2872L15.452 15.2266Z" fill="#8E8E8F" mask="url(#path-5-outside-2_9822_43516)"/>
114
+ <path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z" fill="white"/>
115
+ <rect width="2.59942" height="2.97264" rx="1" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 21.1133 12.8486)" fill="#8E8E8F"/>
116
+ <rect width="1.91578" height="4.24403" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 19.6992 13.8066)" fill="#8E8E8F"/>
117
+ <rect width="2.59942" height="2.97264" rx="1" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 17.3125 16.6455)" fill="#8E8E8F"/>
118
+ </g>
119
+ </svg>`;
120
+ /**
121
+ * 遍历割草任务,下述四个字段可以在路径中唯一确定遍历的位置(当前区域、当前块、当前行、在当前行上的路程)
122
+ */
123
+ const ACTION_BLOCK_COVER = 5;
124
+ /**
125
+ * 遍历割草块转移任务(在同一区域中的一个块转移到下一个块),下述四个字段可以在路径中唯一确定转移的位置(当前区域、前置块、当前转移路径线序号、在当前线上的路程)
126
+ */
127
+ const ACTION_BLOCK_TRANSFER = 6;
128
+ /**
129
+ * 边界割草任务(割草任务内部的巡边任务)
130
+ */
131
+ const ACTION_BOUNDARY_TASK = 8;
132
+ const SVG_MAP_VIEW_ID = 'fleet-maps-svg-map-view';
3
133
 
4
134
  /**
5
135
  * SVG基础MapView
@@ -14,7 +144,6 @@ class SvgMapView {
14
144
  this.lineScale = 1; // 线条缩放系数
15
145
  // 状态标志
16
146
  this.destroyed = false;
17
- this.showScale = false;
18
147
  // 渲染系统 - 移除节流以确保用户操作的实时响应
19
148
  // 拖动功能
20
149
  this.isDragging = false;
@@ -57,6 +186,7 @@ class SvgMapView {
57
186
  this.svg.setAttribute('shape-rendering', 'geometricPrecision');
58
187
  this.svg.setAttribute('text-rendering', 'geometricPrecision');
59
188
  this.svg.setAttribute('image-rendering', 'optimizeQuality');
189
+ this.svg.setAttribute('id', SVG_MAP_VIEW_ID);
60
190
  }
61
191
  /**
62
192
  * 创建SVG组元素
@@ -79,7 +209,9 @@ class SvgMapView {
79
209
  return;
80
210
  this.layers.push(...layers);
81
211
  this.layers.sort((a, b) => a.getLevel() - b.getLevel());
82
- this.refresh();
212
+ }
213
+ getLayer(type) {
214
+ return this.layers.find((layer) => layer.getType() === type) || null;
83
215
  }
84
216
  /**
85
217
  * 添加图层
@@ -89,14 +221,12 @@ class SvgMapView {
89
221
  return;
90
222
  this.layers.push(layer);
91
223
  this.layers.sort((a, b) => a.getLevel() - b.getLevel());
92
- this.refresh();
93
224
  }
94
225
  /**
95
226
  * 移除图层
96
227
  */
97
228
  removeLayer(layer) {
98
229
  const index = this.layers.indexOf(layer);
99
- console.log('removeLayer----->', index);
100
230
  if (index !== -1) {
101
231
  this.layers.splice(index, 1);
102
232
  this.refresh();
@@ -107,15 +237,18 @@ class SvgMapView {
107
237
  * @param type 图层类型
108
238
  */
109
239
  removeLayerByType(type) {
110
- this.layers = this.layers.filter((layer) => layer.getType() !== type);
111
- this.refresh();
240
+ const layer = this.layers.find((layer) => layer.getType() === type);
241
+ if (layer) {
242
+ this.clearLayersGroup(layer);
243
+ this.layers = this.layers.filter((cLayer) => cLayer.getType() !== type);
244
+ }
112
245
  }
113
246
  // ==================== 变换系统 ====================
114
247
  /**
115
248
  * 设置自适应视图变换 - 让SVG刚好包裹住图形
116
249
  */
117
250
  fitToView(bounds) {
118
- const padding = 10; // 添加一些边距以避免内容贴边
251
+ const padding = 20; // 添加一些边距以避免内容贴边
119
252
  const boundWidth = bounds.maxX - bounds.minX;
120
253
  const boundHeight = bounds.maxY - bounds.minY;
121
254
  // 防止宽高为0的情况
@@ -138,8 +271,8 @@ class SvgMapView {
138
271
  this.viewBox = {
139
272
  x: bounds.minX - padding,
140
273
  y: bounds.minY - padding,
141
- width: boundWidth + padding,
142
- height: boundHeight + padding,
274
+ width: boundWidth + padding * 2,
275
+ height: boundHeight + padding * 2,
143
276
  };
144
277
  // 根据宽高比选择合适的preserveAspectRatio设置
145
278
  if (Math.abs(contentAspectRatio - containerAspectRatio) < 0.01) {
@@ -151,42 +284,6 @@ class SvgMapView {
151
284
  this.svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
152
285
  }
153
286
  this.updateViewBox();
154
- this.refresh();
155
- }
156
- /**
157
- * 重置变换
158
- */
159
- resetTransform() {
160
- this.scale = 1;
161
- // 重置viewBox到默认状态
162
- const containerRect = this.container.getBoundingClientRect();
163
- this.viewBox = {
164
- x: 0,
165
- y: 0,
166
- width: containerRect.width,
167
- height: containerRect.height,
168
- };
169
- this.updateViewBox();
170
- this.refresh();
171
- }
172
- /**
173
- * 设置缩放级别
174
- */
175
- setZoom(zoomLevel) {
176
- if (zoomLevel <= 0)
177
- return;
178
- const oldScale = this.scale;
179
- this.scale = zoomLevel;
180
- // 调整viewBox以实现缩放
181
- const scaleFactor = oldScale / this.scale;
182
- const centerX = this.viewBox.x + this.viewBox.width / 2;
183
- const centerY = this.viewBox.y + this.viewBox.height / 2;
184
- this.viewBox.width *= scaleFactor;
185
- this.viewBox.height *= scaleFactor;
186
- this.viewBox.x = centerX - this.viewBox.width / 2;
187
- this.viewBox.y = centerY - this.viewBox.height / 2;
188
- this.updateViewBox();
189
- this.refresh();
190
287
  }
191
288
  /**
192
289
  * 获取当前缩放级别
@@ -194,21 +291,21 @@ class SvgMapView {
194
291
  getZoom() {
195
292
  return this.scale;
196
293
  }
197
- /**
198
- * 设置线条缩放系数
199
- */
200
- setLineScale(lineScale) {
201
- if (lineScale <= 0)
202
- return;
203
- this.lineScale = lineScale;
204
- this.refresh();
205
- }
206
294
  /**
207
295
  * 获取当前线条缩放系数
208
296
  */
209
297
  getLineScale() {
210
298
  return this.lineScale;
211
299
  }
300
+ /**
301
+ * 绘制特定的图层
302
+ */
303
+ renderLayer(type) {
304
+ const layer = this.layers.find((layer) => layer.getType() === type);
305
+ // 清空图层组
306
+ this.clearLayersGroup(layer);
307
+ this.onDrawLayers(type);
308
+ }
212
309
  // ==================== 渲染系统 ====================
213
310
  /**
214
311
  * 主渲染方法
@@ -219,25 +316,112 @@ class SvgMapView {
219
316
  // 绘制所有图层
220
317
  this.onDrawLayers();
221
318
  }
319
+ /**
320
+ * 获取图层id
321
+ */
322
+ getLayerId(layer) {
323
+ return `layer-${layer.getType()}-${layer.getLevel()}`;
324
+ }
222
325
  /**
223
326
  * 清空图层组
224
327
  */
225
- clearLayersGroup() {
226
- while (this.layersGroup.firstChild) {
227
- this.layersGroup.removeChild(this.layersGroup.firstChild);
328
+ clearLayersGroup(layer) {
329
+ if (layer) {
330
+ const layerId = this.getLayerId(layer);
331
+ const layerGroup = this.layersGroup.querySelector(`#${layerId}`);
332
+ if (layerGroup) {
333
+ this.layersGroup.removeChild(layerGroup);
334
+ }
335
+ }
336
+ else {
337
+ while (this.layersGroup.firstChild) {
338
+ this.layersGroup.removeChild(this.layersGroup.firstChild);
339
+ }
228
340
  }
229
341
  }
230
342
  /**
231
- * 绘制所有图层
343
+ * 获取图层的下一个兄弟元素
344
+ * 根据图层的level来获取
232
345
  */
233
- onDrawLayers() {
234
- for (const layer of this.layers) {
235
- if (layer.isVisible()) {
236
- const layerGroup = this.createSVGGroup(`layer-${layer.getLevel()}`);
346
+ getNextSibling(layer) {
347
+ const nextLayer = this.layers.find((cLayer) => cLayer.getLevel() > layer.getLevel());
348
+ const id = `layer-${nextLayer?.getType()}-${nextLayer?.getLevel()}`;
349
+ const nextSibling = this.layersGroup.querySelector(`#${id}`);
350
+ return nextSibling;
351
+ }
352
+ /**
353
+ * 绘制图层,不传参数则默认绘制所有图层
354
+ */
355
+ onDrawLayers(type) {
356
+ if (type) {
357
+ const layer = this.layers.find((layer) => layer.getType() === type);
358
+ if (layer) {
359
+ const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
237
360
  layer.drawSVG(layerGroup, this.scale, this.lineScale);
238
- this.layersGroup.appendChild(layerGroup);
361
+ const nextSibling = this.getNextSibling(layer);
362
+ if (nextSibling) {
363
+ this.layersGroup.insertBefore(layerGroup, nextSibling);
364
+ }
365
+ else {
366
+ this.layersGroup.appendChild(layerGroup);
367
+ }
239
368
  }
240
369
  }
370
+ else {
371
+ for (const layer of this.layers) {
372
+ if (layer.isVisible()) {
373
+ const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
374
+ layer.drawSVG(layerGroup, this.scale, this.lineScale);
375
+ this.layersGroup.appendChild(layerGroup);
376
+ }
377
+ }
378
+ }
379
+ // if (type) {
380
+ // const layer = this.layers.find((layer) => layer.getType() === type);
381
+ // const layerId = this.getLayerId(layer);
382
+ // // 记录原始位置信息
383
+ // const layerGroup = this.layersGroup.querySelector(`#${layerId}`);
384
+ // let nextSibling: Node | null = null;
385
+ // if (layerGroup) {
386
+ // // 记录被删除元素的下一个兄弟元素,用于确定插入位置
387
+ // nextSibling = layerGroup.nextSibling;
388
+ // // 删除旧的图层组
389
+ // this.layersGroup.removeChild(layerGroup);
390
+ // // 从layerId解析出图层类型和层级
391
+ // // layerId格式: layer-${type}-${level}
392
+ // const layerIdParts = layerId.split('-');
393
+ // if (layerIdParts.length >= 3) {
394
+ // const layerType = layerIdParts.slice(1, -1).join('-'); // 处理类型名中可能包含连字符的情况
395
+ // const layerLevel = parseInt(layerIdParts[layerIdParts.length - 1]);
396
+ // // 查找对应的图层对象
397
+ // const targetLayer = this.layers.find(layer =>
398
+ // layer.getType() === layerType && layer.getLevel() === layerLevel
399
+ // );
400
+ // if (targetLayer && targetLayer.isVisible()) {
401
+ // // 创建新的图层组
402
+ // const newLayerGroup = this.createSVGGroup(layerId);
403
+ // // 重新绘制图层
404
+ // targetLayer.drawSVG(newLayerGroup, this.scale, this.lineScale);
405
+ // // 在原始位置插入新元素
406
+ // if (nextSibling) {
407
+ // this.layersGroup.insertBefore(newLayerGroup, nextSibling);
408
+ // } else {
409
+ // // 如果没有下一个兄弟元素,说明原来是最后一个,直接appendChild
410
+ // this.layersGroup.appendChild(newLayerGroup);
411
+ // }
412
+ // }
413
+ // }
414
+ // }
415
+ // } else {
416
+ // // 重绘所有图层
417
+ // for (const layer of this.layers) {
418
+ // if (layer.isVisible()) {
419
+ // const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
420
+ // layer.drawSVG(layerGroup, this.scale, this.lineScale);
421
+ // this.layersGroup.appendChild(layerGroup);
422
+ // }
423
+ // }
424
+ // }
241
425
  }
242
426
  /**
243
427
  * 刷新渲染
@@ -247,12 +431,6 @@ class SvgMapView {
247
431
  return;
248
432
  this.render();
249
433
  }
250
- /**
251
- * 重新初始化SVG(用于容器大小变化)
252
- */
253
- reinitializeSVG() {
254
- this.refresh();
255
- }
256
434
  // ==================== 拖拽功能 ====================
257
435
  /**
258
436
  * 设置拖拽事件处理器
@@ -377,22 +555,6 @@ class SvgMapView {
377
555
  return null;
378
556
  }
379
557
  }
380
- /**
381
- * 自动适配viewBox到实际内容
382
- */
383
- autoFitToContent() {
384
- if (this.destroyed || this.layers.length === 0)
385
- return;
386
- const bounds = this.getLayersGroupBounds();
387
- if (bounds) {
388
- this.fitToView({
389
- minX: bounds.x,
390
- minY: bounds.y,
391
- maxX: bounds.x + bounds.width,
392
- maxY: bounds.y + bounds.height,
393
- });
394
- }
395
- }
396
558
  /**
397
559
  * 获取ViewBox信息
398
560
  */
@@ -413,13 +575,6 @@ class SvgMapView {
413
575
  * 诊断SVG尺寸信息
414
576
  */
415
577
  diagnosticSizeInfo() { }
416
- /**
417
- * 设置是否显示比例尺
418
- */
419
- setShowScale(show) {
420
- this.showScale = show;
421
- this.refresh();
422
- }
423
578
  /**
424
579
  * 获取SVG元素
425
580
  */
@@ -621,8 +776,6 @@ const create = (createState) => createState ? createImpl(createState) : createIm
621
776
 
622
777
  const useSubBoundaryBorderStore = create((set, get) => ({
623
778
  subBoundaryBorder: {},
624
- // 覆盖所有数据
625
- setSubBoundaryBorder: (subBoundaryBorder) => set({ subBoundaryBorder }),
626
779
  // 追加单个数据
627
780
  addSubBoundaryBorder: (key, element) => set((state) => ({
628
781
  subBoundaryBorder: {
@@ -630,134 +783,28 @@ const useSubBoundaryBorderStore = create((set, get) => ({
630
783
  [key]: element,
631
784
  },
632
785
  })),
633
- // 追加多个数据
634
- addMultipleSubBoundaryBorders: (borders) => set((state) => ({
635
- subBoundaryBorder: {
636
- ...state.subBoundaryBorder,
637
- ...borders,
638
- },
639
- })),
640
786
  // 清空所有数据
641
787
  clearSubBoundaryBorder: () => set({ subBoundaryBorder: {} }),
788
+ // 障碍物
789
+ obstacles: {},
790
+ addObstacles: (key, element) => set((state) => ({
791
+ obstacles: {
792
+ ...state.obstacles,
793
+ [key]: element,
794
+ },
795
+ })),
796
+ clearObstacles: () => set({ obstacles: {} }),
797
+ // svg数据
798
+ svgElements: {},
799
+ addSvgElements: (key, element) => set((state) => ({
800
+ svgElements: {
801
+ ...state.svgElements,
802
+ [key]: element,
803
+ },
804
+ })),
805
+ clearSvgElements: () => set({ svgElements: {} }),
642
806
  }));
643
807
 
644
- /**
645
- * 地图渲染相关常量配置
646
- */
647
- /**
648
- * 缩放因子 - 将米转换为像素
649
- * 与Python代码中的SVG比例一致
650
- */
651
- const SCALE_FACTOR = 50; // 50像素/米
652
- /**
653
- * 默认线宽设置
654
- */
655
- const DEFAULT_LINE_WIDTHS = {
656
- OBSTACLE: 2,
657
- CHARGING_PILE: 2,
658
- CHANNEL: 2,
659
- PATH: 20,
660
- VISION_OFF_AREA: 2,
661
- TIME_LIMIT_OBSTACLE: 1,
662
- };
663
- /**
664
- * 默认透明度设置
665
- */
666
- const DEFAULT_OPACITIES = {
667
- FULL: 1.0,
668
- HIGH: 0.7,
669
- LOW: 0.4};
670
- /**
671
- * 默认半径设置
672
- */
673
- const DEFAULT_RADII = {
674
- CHARGING_PILE: 12};
675
- /**
676
- * 图层等级
677
- */
678
- const LAYER_LEVELS = {
679
- BOUNDARY: 2,
680
- BOUNDARY_BORDER: 4};
681
- /**
682
- * 图层默认id
683
- */
684
- const LAYER_DEFAULT_TYPE = {
685
- CHANNEL: 'channel',
686
- BOUNDARY: 'boundary',
687
- PATH: 'path',
688
- BOUNDARY_BORDER: 'boundary_border',
689
- OBSTACLE: 'obstacle',
690
- CHARGING_PILE: 'charging_pile',
691
- POINT: 'point',
692
- SVG: 'svg',
693
- VISION_OFF_AREA: 'vision_off_area',
694
- ANTENNA: 'antenna',
695
- };
696
- var RobotStatus;
697
- (function (RobotStatus) {
698
- RobotStatus[RobotStatus["PARKED"] = 1] = "PARKED";
699
- RobotStatus[RobotStatus["CHARGING"] = 2] = "CHARGING";
700
- RobotStatus[RobotStatus["STANDBY"] = 3] = "STANDBY";
701
- RobotStatus[RobotStatus["MOWING"] = 4] = "MOWING";
702
- RobotStatus[RobotStatus["WORKING"] = 5] = "WORKING";
703
- RobotStatus[RobotStatus["MAPPING"] = 6] = "MAPPING";
704
- RobotStatus[RobotStatus["ERROR"] = 7] = "ERROR";
705
- RobotStatus[RobotStatus["UPGRADING"] = 8] = "UPGRADING";
706
- RobotStatus[RobotStatus["DISCONNECTED"] = 9] = "DISCONNECTED";
707
- RobotStatus[RobotStatus["UNKNOWN"] = -1] = "UNKNOWN";
708
- RobotStatus[RobotStatus["TASK_DELAY"] = 10] = "TASK_DELAY";
709
- // WAITING = 'Waiting',
710
- })(RobotStatus || (RobotStatus = {}));
711
- // RTK状态
712
- var RTK_STATE;
713
- (function (RTK_STATE) {
714
- RTK_STATE[RTK_STATE["LOW_RTK"] = 1] = "LOW_RTK";
715
- RTK_STATE[RTK_STATE["MIDDLE_RTK"] = 2] = "MIDDLE_RTK";
716
- RTK_STATE[RTK_STATE["HIGH_RTK"] = 3] = "HIGH_RTK";
717
- RTK_STATE[RTK_STATE["NO_POSTURE"] = 10] = "NO_POSTURE";
718
- RTK_STATE[RTK_STATE["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
719
- RTK_STATE[RTK_STATE["OFF_LINE"] = 19] = "OFF_LINE";
720
- })(RTK_STATE || (RTK_STATE = {}));
721
- var REAL_TIME_DATA_TYPE;
722
- (function (REAL_TIME_DATA_TYPE) {
723
- REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["LOCATION"] = 1] = "LOCATION";
724
- REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["PROCESS"] = 2] = "PROCESS";
725
- })(REAL_TIME_DATA_TYPE || (REAL_TIME_DATA_TYPE = {}));
726
- const ISOLATED_BOUNDARY_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
727
- <g opacity="0.6">
728
- <rect width="24" height="24" rx="12" fill="#1E1E1F" fill-opacity="0.5"/>
729
- <path d="M8.20573 12.4961C6.739 11.1707 4.85775 11.2284 3.60938 11.1707C6.17744 13.0156 6.05584 15.7887 5.67404 16.9446H14.327C13.837 13.1673 15.5321 10.1289 16.4408 9.08187C15.0342 9.46118 13.5794 10.8303 13.0278 11.4674C12.8102 8.94572 13.992 5.97489 14.61 4.80469C12.5235 5.92214 11.0501 8.26056 10.4938 9.56261C9.22803 7.5947 6.83894 7.17806 5.883 6.9432C8.20573 10.2373 8.00039 11.1707 8.20573 12.4961Z" fill="white"/>
730
- <mask id="path-3-outside-1_9822_43516" maskUnits="userSpaceOnUse" x="13.2344" y="7.20545" width="12.0208" height="12.0208" fill="black">
731
- <rect fill="white" x="13.2344" y="7.20545" width="12.0208" height="12.0208"/>
732
- <path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z"/>
733
- </mask>
734
- <path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z" fill="white"/>
735
- <path d="M20.6852 11.1208L21.7458 10.0601L21.7459 10.0601L20.6852 11.1208ZM17.8568 11.1208L16.7961 10.0601L16.7962 10.0601L17.8568 11.1208ZM17.2733 11.7043L16.2126 10.6437L16.2126 10.6436L17.2733 11.7043ZM16.8631 13.9319L17.9238 14.9926L16.381 16.5354L15.493 14.5425L16.8631 13.9319ZM17.5916 13.2034L19.0855 13.0678L19.149 13.7674L18.6523 14.2641L17.5916 13.2034ZM20.4056 12.1421L21.4662 11.0814L21.4665 11.0817L20.4056 12.1421ZM18.9699 14.5348L17.9093 13.4741L18.3741 13.0093L19.0309 13.036L18.9699 14.5348ZM18.2235 15.2812L17.6298 16.6587L15.5997 15.7837L17.1628 14.2206L18.2235 15.2812ZM20.429 14.86L21.4897 15.9207L21.4897 15.9207L20.429 14.86ZM21.0125 14.2765L22.0732 15.3371L22.0732 15.3372L21.0125 14.2765ZM20.6852 11.1208L19.6246 12.1815C19.4294 11.9862 19.1127 11.9863 18.9174 12.1815L17.8568 11.1208L16.7962 10.0601C18.163 8.69337 20.379 8.6934 21.7458 10.0601L20.6852 11.1208ZM17.8568 11.1208L18.9175 12.1814L18.334 12.7649L17.2733 11.7043L16.2126 10.6436L16.7961 10.0601L17.8568 11.1208ZM17.2733 11.7043L18.334 12.7649C18.1877 12.9112 18.1488 13.1318 18.2333 13.3214L16.8631 13.9319L15.493 14.5425C14.9228 13.2629 15.1537 11.7026 16.2126 10.6437L17.2733 11.7043ZM16.8631 13.9319L15.8025 12.8713L16.531 12.1428L17.5916 13.2034L18.6523 14.2641L17.9238 14.9926L16.8631 13.9319ZM17.5916 13.2034L16.0978 13.339C16.0334 12.6302 16.2727 11.8914 16.8196 11.3445L17.8803 12.4052L18.9409 13.4658C19.051 13.3558 19.098 13.206 19.0855 13.0678L17.5916 13.2034ZM17.8803 12.4052L16.8196 11.3445L17.5067 10.6574L18.5674 11.7181L19.628 12.7788L18.9409 13.4658L17.8803 12.4052ZM18.5674 11.7181L17.5067 10.6574C18.483 9.68112 20.0659 9.68112 21.0422 10.6574L19.9816 11.7181L18.9209 12.7788C19.1162 12.974 19.4328 12.974 19.628 12.7788L18.5674 11.7181ZM19.9816 11.7181L21.0422 10.6574L21.4662 11.0814L20.4056 12.1421L19.3449 13.2027L18.9209 12.7788L19.9816 11.7181ZM20.4056 12.1421L21.4665 11.0817C22.4419 12.0577 22.4428 13.6404 21.4662 14.617L20.4056 13.5563L19.3449 12.4956C19.1493 12.6913 19.1498 13.0076 19.3446 13.2024L20.4056 12.1421ZM20.4056 13.5563L21.4662 14.617L20.7791 15.304L19.7185 14.2434L18.6578 13.1827L19.3449 12.4956L20.4056 13.5563ZM19.7185 14.2434L20.7791 15.304C20.2628 15.8204 19.5762 16.0607 18.909 16.0335L18.9699 14.5348L19.0309 13.036C18.9025 13.0308 18.7629 13.0777 18.6578 13.1827L19.7185 14.2434ZM18.9699 14.5348L20.0306 15.5954L19.2841 16.3419L18.2235 15.2812L17.1628 14.2206L17.9093 13.4741L18.9699 14.5348ZM18.2235 15.2812L18.8172 13.9037C19.0042 13.9843 19.2223 13.9455 19.3684 13.7993L20.429 14.86L21.4897 15.9207C20.4427 16.9676 18.9034 17.2077 17.6298 16.6587L18.2235 15.2812ZM20.429 14.86L19.3684 13.7994L19.9519 13.2159L21.0125 14.2765L22.0732 15.3372L21.4897 15.9207L20.429 14.86ZM21.0125 14.2765L19.9518 13.2159C20.1471 13.0206 20.1471 12.704 19.9519 12.5088L21.0125 11.4481L22.0732 10.3874C23.4401 11.7543 23.4399 13.9703 22.0732 15.3371L21.0125 14.2765ZM21.0125 11.4481L19.9519 12.5088L19.6246 12.1814L20.6852 11.1208L21.7459 10.0601L22.0732 10.3874L21.0125 11.4481Z" fill="#8E8E8F" mask="url(#path-3-outside-1_9822_43516)"/>
736
- <mask id="path-5-outside-2_9822_43516" maskUnits="userSpaceOnUse" x="9.22265" y="10.388" width="12.7279" height="12.7279" fill="black">
737
- <rect fill="white" x="9.22265" y="10.388" width="12.7279" height="12.7279"/>
738
- <path d="M16.1708 14.5077C15.4353 14.1778 14.5422 14.3133 13.9383 14.9172L13.3548 15.5007C12.574 16.2818 12.5738 17.5481 13.3548 18.3291L13.6821 18.6564C14.4631 19.4374 15.7295 19.4373 16.5105 18.6564L17.094 18.0729C17.6895 17.4775 17.8299 16.6003 17.5173 15.8708L16.7743 16.6138C16.7828 16.8803 16.6863 17.1494 16.4829 17.3527L15.7952 18.0405C15.4046 18.4309 14.7714 18.431 14.3809 18.0405L13.957 17.6165C13.5667 17.226 13.5666 16.5927 13.957 16.2023L14.6447 15.5145C14.8654 15.2939 15.1639 15.1979 15.452 15.2266L16.1708 14.5077Z"/>
739
- </mask>
740
- <path d="M16.1708 14.5077C15.4353 14.1778 14.5422 14.3133 13.9383 14.9172L13.3548 15.5007C12.574 16.2818 12.5738 17.5481 13.3548 18.3291L13.6821 18.6564C14.4631 19.4374 15.7295 19.4373 16.5105 18.6564L17.094 18.0729C17.6895 17.4775 17.8299 16.6003 17.5173 15.8708L16.7743 16.6138C16.7828 16.8803 16.6863 17.1494 16.4829 17.3527L15.7952 18.0405C15.4046 18.4309 14.7714 18.431 14.3809 18.0405L13.957 17.6165C13.5667 17.226 13.5666 16.5927 13.957 16.2023L14.6447 15.5145C14.8654 15.2939 15.1639 15.1979 15.452 15.2266L16.1708 14.5077Z" fill="white"/>
741
- <path d="M16.1708 14.5077L16.7847 13.1391L18.7701 14.0297L17.2315 15.5684L16.1708 14.5077ZM13.3548 15.5007L12.294 14.4402L12.2941 14.44L13.3548 15.5007ZM16.5105 18.6564L17.5712 19.7171L17.5711 19.7172L16.5105 18.6564ZM17.5173 15.8708L16.4567 14.8102L18.0236 13.2433L18.8962 15.2802L17.5173 15.8708ZM16.7743 16.6138L15.2751 16.6613L15.2545 16.0123L15.7137 15.5532L16.7743 16.6138ZM15.7952 18.0405L16.8558 19.1011L16.8556 19.1013L15.7952 18.0405ZM13.957 17.6165L12.8963 18.6772L12.8959 18.6767L13.957 17.6165ZM13.957 16.2023L12.8961 15.1418L12.8963 15.1416L13.957 16.2023ZM15.452 15.2266L16.5126 16.2872L16.0102 16.7896L15.3032 16.7192L15.452 15.2266ZM16.1708 14.5077L15.5569 15.8763C15.3688 15.792 15.1469 15.8299 14.999 15.9779L13.9383 14.9172L12.8776 13.8565C13.9375 12.7967 15.5018 12.5636 16.7847 13.1391L16.1708 14.5077ZM13.9383 14.9172L14.999 15.9779L14.4155 16.5614L13.3548 15.5007L12.2941 14.44L12.8776 13.8565L13.9383 14.9172ZM13.3548 15.5007L14.4156 16.5612C14.2202 16.7567 14.2204 17.0734 14.4155 17.2685L13.3548 18.3291L12.2941 19.3898C10.9272 18.0229 10.9277 15.8069 12.294 14.4402L13.3548 15.5007ZM13.3548 18.3291L14.4155 17.2685L14.7428 17.5958L13.6821 18.6564L12.6215 19.7171L12.2941 19.3898L13.3548 18.3291ZM13.6821 18.6564L14.7428 17.5958C14.9378 17.7908 15.2546 17.791 15.45 17.5956L16.5105 18.6564L17.5711 19.7172C16.2044 21.0836 13.9884 21.084 12.6215 19.7171L13.6821 18.6564ZM16.5105 18.6564L15.4499 17.5958L16.0334 17.0123L17.094 18.0729L18.1547 19.1336L17.5712 19.7171L16.5105 18.6564ZM17.094 18.0729L16.0334 17.0123C16.1795 16.8662 16.2185 16.6481 16.1385 16.4615L17.5173 15.8708L18.8962 15.2802C19.4413 16.5526 19.1996 18.0887 18.1547 19.1336L17.094 18.0729ZM17.5173 15.8708L18.578 16.9315L17.835 17.6745L16.7743 16.6138L15.7137 15.5532L16.4567 14.8102L17.5173 15.8708ZM16.7743 16.6138L18.2736 16.5664C18.2945 17.226 18.0544 17.9026 17.5436 18.4134L16.4829 17.3527L15.4223 16.2921C15.3182 16.3961 15.2711 16.5346 15.2751 16.6613L16.7743 16.6138ZM16.4829 17.3527L17.5436 18.4134L16.8558 19.1011L15.7952 18.0405L14.7345 16.9798L15.4223 16.2921L16.4829 17.3527ZM15.7952 18.0405L16.8556 19.1013C15.8795 20.0771 14.2967 20.0776 13.3203 19.1011L14.3809 18.0405L15.4416 16.9798C15.2461 16.7844 14.9297 16.7847 14.7347 16.9797L15.7952 18.0405ZM14.3809 18.0405L13.3203 19.1011L12.8963 18.6772L13.957 17.6165L15.0176 16.5558L15.4416 16.9798L14.3809 18.0405ZM13.957 17.6165L12.8959 18.6767C11.9207 17.7008 11.92 16.1182 12.8961 15.1418L13.957 16.2023L15.0178 17.2628C15.2133 17.0672 15.2128 16.7512 15.018 16.5563L13.957 17.6165ZM13.957 16.2023L12.8963 15.1416L13.5841 14.4539L14.6447 15.5145L15.7054 16.5752L15.0176 17.2629L13.957 16.2023ZM14.6447 15.5145L13.5841 14.4539C14.1364 13.9015 14.8848 13.6626 15.6007 13.734L15.452 15.2266L15.3032 16.7192C15.443 16.7331 15.5943 16.6862 15.7054 16.5752L14.6447 15.5145ZM15.452 15.2266L14.3913 14.1659L15.1101 13.4471L16.1708 14.5077L17.2315 15.5684L16.5126 16.2872L15.452 15.2266Z" fill="#8E8E8F" mask="url(#path-5-outside-2_9822_43516)"/>
742
- <path d="M20.6852 11.1208C19.9042 10.3398 18.6378 10.3398 17.8568 11.1208L17.2733 11.7043C16.6707 12.3069 16.5358 13.1973 16.8631 13.9319L17.5916 13.2034C17.5657 12.9181 17.6619 12.6236 17.8803 12.4052L18.5674 11.7181C18.9579 11.3276 19.5911 11.3276 19.9816 11.7181L20.4056 12.1421C20.7959 12.5326 20.796 13.1658 20.4056 13.5563L19.7185 14.2434C19.5128 14.449 19.2393 14.5457 18.9699 14.5348L18.2235 15.2812C18.9538 15.596 19.8325 15.4565 20.429 14.86L21.0125 14.2765C21.7935 13.4955 21.7936 12.2291 21.0125 11.4481L20.6852 11.1208Z" fill="white"/>
743
- <rect width="2.59942" height="2.97264" rx="1" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 21.1133 12.8486)" fill="#8E8E8F"/>
744
- <rect width="1.91578" height="4.24403" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 19.6992 13.8066)" fill="#8E8E8F"/>
745
- <rect width="2.59942" height="2.97264" rx="1" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 17.3125 16.6455)" fill="#8E8E8F"/>
746
- </g>
747
- </svg>`;
748
- /**
749
- * 遍历割草任务,下述四个字段可以在路径中唯一确定遍历的位置(当前区域、当前块、当前行、在当前行上的路程)
750
- */
751
- const ACTION_BLOCK_COVER = 5;
752
- /**
753
- * 遍历割草块转移任务(在同一区域中的一个块转移到下一个块),下述四个字段可以在路径中唯一确定转移的位置(当前区域、前置块、当前转移路径线序号、在当前线上的路程)
754
- */
755
- const ACTION_BLOCK_TRANSFER = 6;
756
- /**
757
- * 边界割草任务(割草任务内部的巡边任务)
758
- */
759
- const ACTION_BOUNDARY_TASK = 8;
760
-
761
808
  /**
762
809
  * 路径图层
763
810
  * 专门处理路径元素的渲染
@@ -808,51 +855,90 @@ class ChannelLayer extends BaseLayer {
808
855
  }
809
856
  // �� 修改:计算包含所有分区和通道的边界框
810
857
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
811
- // 1. 先计算所有分区的边界
812
- for (const partitionId in subBoundaryBorder) {
813
- const boundaryData = subBoundaryBorder[partitionId];
814
- if (boundaryData && boundaryData.coordinates && boundaryData.coordinates.length > 0) {
815
- for (const coord of boundaryData.coordinates) {
816
- minX = Math.min(minX, coord[0]);
817
- minY = Math.min(minY, coord[1]);
818
- maxX = Math.max(maxX, coord[0]);
819
- maxY = Math.max(maxY, coord[1]);
858
+ // 1. 先计算所有分区的边界,如果能拿到边界的svg的大小,就使用这个如果拿不到,就根据分区去计算
859
+ const svg = document.getElementById(SVG_MAP_VIEW_ID);
860
+ if (svg && svg instanceof SVGSVGElement && svg.viewBox) {
861
+ const viewBox = svg.viewBox.baseVal;
862
+ minX = viewBox.x;
863
+ minY = viewBox.y;
864
+ maxX = viewBox.x + viewBox.width;
865
+ maxY = viewBox.y + viewBox.height;
866
+ }
867
+ else {
868
+ for (const partitionId in subBoundaryBorder) {
869
+ const boundaryData = subBoundaryBorder[partitionId];
870
+ if (boundaryData && boundaryData.coordinates && boundaryData.coordinates.length > 0) {
871
+ for (const coord of boundaryData.coordinates) {
872
+ minX = Math.min(minX, coord[0]);
873
+ minY = Math.min(minY, coord[1]);
874
+ maxX = Math.max(maxX, coord[0]);
875
+ maxY = Math.max(maxY, coord[1]);
876
+ }
820
877
  }
821
878
  }
822
879
  }
823
880
  // 2. 再计算所有通道的边界
824
881
  for (const element of this.elements) {
825
- const tunnelConnection = element.originalData?.connection;
826
- if (tunnelConnection && Array.isArray(tunnelConnection)) {
827
- const clipPathId = `channel-exclude-${element.originalData?.id || Math.random().toString(36).substr(2, 9)}`;
828
- // 检查是否已存在该 clipPath
829
- const existingClipPath = defs.querySelector(`#${clipPathId}`);
830
- if (existingClipPath)
831
- continue;
832
- // 创建 clipPath
833
- const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
834
- clipPath.setAttribute('id', clipPathId);
835
- clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
836
- // === 合成一个 path ===
837
- let d = `M ${minX} ${minY} L ${maxX} ${minY} L ${maxX} ${maxY} L ${minX} ${maxY} Z`;
838
- for (const partitionId of tunnelConnection) {
839
- const boundaryData = subBoundaryBorder[partitionId];
840
- if (boundaryData && boundaryData.coordinates.length >= 3) {
841
- d += ` M ${boundaryData.coordinates[0][0]} ${boundaryData.coordinates[0][1]}`;
842
- for (let i = 1; i < boundaryData.coordinates.length; i++) {
843
- d += ` L ${boundaryData.coordinates[i][0]} ${boundaryData.coordinates[i][1]}`;
844
- }
845
- d += ' Z';
882
+ // const tunnelConnection = element.originalData?.connection;
883
+ // if (tunnelConnection && Array.isArray(tunnelConnection)) {
884
+ // const clipPathId = `channel-exclude-${
885
+ // element.originalData?.id || Math.random().toString(36).substr(2, 9)
886
+ // }`;
887
+ // // 检查是否已存在该 clipPath
888
+ // const existingClipPath = defs.querySelector(`#${clipPathId}`);
889
+ // if (existingClipPath) continue;
890
+ // // 创建 clipPath
891
+ // const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
892
+ // clipPath.setAttribute('id', clipPathId);
893
+ // clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
894
+ // // === 合成一个 path ===
895
+ // let d = `M ${minX} ${minY} L ${maxX} ${minY} L ${maxX} ${maxY} L ${minX} ${maxY} Z`;
896
+ // for (const partitionId of tunnelConnection) {
897
+ // const boundaryData = subBoundaryBorder[partitionId];
898
+ // if (boundaryData && boundaryData.coordinates.length >= 3) {
899
+ // d += ` M ${boundaryData.coordinates[0][0]} ${boundaryData.coordinates[0][1]}`;
900
+ // for (let i = 1; i < boundaryData.coordinates.length; i++) {
901
+ // d += ` L ${boundaryData.coordinates[i][0]} ${boundaryData.coordinates[i][1]}`;
902
+ // }
903
+ // d += ' Z';
904
+ // }
905
+ // }
906
+ // const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
907
+ // path.setAttribute('d', d);
908
+ // path.setAttribute('clip-rule', 'evenodd'); // 关键
909
+ // clipPath.appendChild(path);
910
+ // defs.appendChild(clipPath);
911
+ // clipPathIdsMap[element.originalData?.id.toString()] = clipPathId;
912
+ // } else {
913
+ const clipPathId = `channel-exclude-all-${element.originalData?.id || Math.random().toString(36).substr(2, 9)}`;
914
+ // 检查是否已存在该 clipPath
915
+ const existingClipPath = defs.querySelector(`#${clipPathId}`);
916
+ if (existingClipPath)
917
+ continue;
918
+ // 创建 clipPath
919
+ const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
920
+ clipPath.setAttribute('id', clipPathId);
921
+ clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
922
+ // === 合成一个 path ===
923
+ let d = `M ${minX} ${minY} L ${maxX} ${minY} L ${maxX} ${maxY} L ${minX} ${maxY} Z`;
924
+ for (const partitionId in subBoundaryBorder) {
925
+ const boundaryData = subBoundaryBorder[partitionId];
926
+ if (boundaryData && boundaryData.coordinates.length >= 3) {
927
+ d += ` M ${boundaryData.coordinates[0][0]} ${boundaryData.coordinates[0][1]}`;
928
+ for (let i = 1; i < boundaryData.coordinates.length; i++) {
929
+ d += ` L ${boundaryData.coordinates[i][0]} ${boundaryData.coordinates[i][1]}`;
846
930
  }
931
+ d += ' Z';
847
932
  }
848
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
849
- path.setAttribute('d', d);
850
- path.setAttribute('clip-rule', 'evenodd'); // 关键
851
- clipPath.appendChild(path);
852
- defs.appendChild(clipPath);
853
- clipPathIdsMap[element.originalData?.id.toString()] = clipPathId;
854
933
  }
934
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
935
+ path.setAttribute('d', d);
936
+ path.setAttribute('clip-rule', 'evenodd'); // 关键
937
+ clipPath.appendChild(path);
938
+ defs.appendChild(clipPath);
939
+ clipPathIdsMap[element.originalData?.id.toString()] = clipPathId;
855
940
  }
941
+ // }
856
942
  return clipPathIdsMap;
857
943
  }
858
944
  /**
@@ -916,13 +1002,14 @@ class PathLayer extends BaseLayer {
916
1002
  this.level = 3;
917
1003
  this.scale = 1;
918
1004
  this.lineScale = 1;
1005
+ this.boundaryPaths = {};
919
1006
  this.type = LAYER_DEFAULT_TYPE.PATH;
920
1007
  }
921
1008
  /**
922
1009
  * 创建所有分区并集的 clipPath
923
1010
  */
924
1011
  createUnionClipPath(svgGroup) {
925
- const { subBoundaryBorder } = useSubBoundaryBorderStore.getState();
1012
+ const { subBoundaryBorder, obstacles, svgElements } = useSubBoundaryBorderStore.getState();
926
1013
  // 确保 defs 元素存在
927
1014
  let defs = svgGroup.querySelector('defs');
928
1015
  if (!defs) {
@@ -936,7 +1023,7 @@ class PathLayer extends BaseLayer {
936
1023
  defs.removeChild(existing);
937
1024
  // 合成所有分区的 path
938
1025
  let d = '';
939
- // 合成所有边界
1026
+ // 1. 外圈(主边界,顺时针)
940
1027
  Object.values(subBoundaryBorder).forEach((item) => {
941
1028
  const bCoords = item.coordinates;
942
1029
  if (bCoords.length >= 3) {
@@ -947,6 +1034,48 @@ class PathLayer extends BaseLayer {
947
1034
  d += ' Z ';
948
1035
  }
949
1036
  });
1037
+ // 2. 内圈(禁区,逆时针)
1038
+ Object.values(obstacles).forEach((item) => {
1039
+ const bCoords = item.coordinates;
1040
+ if (bCoords.length >= 3) {
1041
+ d += `M ${bCoords[bCoords.length - 1][0]} ${bCoords[bCoords.length - 1][1]}`;
1042
+ for (let i = bCoords.length - 2; i >= 0; i--) {
1043
+ d += ` L ${bCoords[i][0]} ${bCoords[i][1]}`;
1044
+ }
1045
+ d += ' Z ';
1046
+ }
1047
+ });
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
+ }
1076
+ }
1077
+ }
1078
+ });
950
1079
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
951
1080
  path.setAttribute('d', d);
952
1081
  const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
@@ -971,48 +1100,132 @@ class PathLayer extends BaseLayer {
971
1100
  // 2. 创建一个组,应用 clipPath
972
1101
  const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
973
1102
  group.setAttribute('clip-path', `url(#${clipPathId})`);
974
- group.setAttribute('opacity', '0.35'); // 统一透明度,防止叠加脏乱
975
- // 3. 渲染所有路径
976
- for (const element of this.elements) {
977
- this.renderPathToGroup(group, element);
978
- }
1103
+ group.setAttribute('opacity', '0.5'); // 统一透明度,防止叠加脏乱
1104
+ // 3. 优化渲染:按样式分组并合并路径
1105
+ this.renderOptimizedPaths(group);
979
1106
  svgGroup.appendChild(group);
980
1107
  }
981
1108
  /**
982
- * 渲染单个路径到指定的组中
1109
+ * 优化渲染:按样式分组并合并路径,减少 DOM 节点数量
983
1110
  */
984
- renderPathToGroup(group, element) {
985
- const { coordinates, style } = element;
986
- if (coordinates.length < 2)
987
- return;
988
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
989
- // 构建路径数据
990
- let pathData = `M ${coordinates[0][0]} ${coordinates[0][1]}`;
991
- for (let i = 1; i < coordinates.length; i++) {
992
- pathData += ` L ${coordinates[i][0]} ${coordinates[i][1]}`;
993
- }
994
- path.style.mixBlendMode = 'normal';
995
- // 设置路径属性
996
- path.setAttribute('d', pathData);
997
- // 直接给fill的颜色设置透明度会导致path重叠的部分颜色叠加,所以使用fill填充实色,通过fill-opacity设置透明度
998
- path.setAttribute('fill', 'none');
999
- // path.setAttribute('fill-opacity', '0.4');
1000
- path.setAttribute('stroke', style.strokeColor || '#000000');
1001
- path.setAttribute('mix-blend-mode', 'normal');
1002
- const lineWidth = Math.max(style.lineWidth || 1, 0.5);
1003
- path.setAttribute('stroke-width', lineWidth.toString());
1004
- path.setAttribute('stroke-linecap', 'round');
1005
- path.setAttribute('stroke-linejoin', 'round');
1006
- // 注意:这里不设置 opacity,因为透明度由父组控制
1007
- // path.setAttribute('vector-effect', 'non-scaling-stroke');
1008
- if (style.lineDash && style.lineDash.length > 0) {
1009
- path.setAttribute('stroke-dasharray', style.lineDash.join(','));
1111
+ renderOptimizedPaths(group) {
1112
+ // 按样式分组存储路径数据
1113
+ const styleGroups = new Map();
1114
+ // 收集所有路径数据并按样式分组
1115
+ for (const element of this.elements) {
1116
+ // 类型断言:PathLayer 中的 elements 实际上是 PathElements 结构
1117
+ const pathElement = element;
1118
+ const { id, elements } = pathElement;
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
+ });
1010
1138
  }
1011
- else if (style.strokeDasharray) {
1012
- path.setAttribute('stroke-dasharray', style.strokeDasharray);
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 中(保持兼容性)
1164
+ elements.forEach((element) => {
1165
+ const { id } = element;
1166
+ if (!this.boundaryPaths[id]) {
1167
+ this.boundaryPaths[id] = [];
1168
+ }
1169
+ this.boundaryPaths[id].push(path);
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
+ }
1013
1221
  }
1014
- path.classList.add('vector-path');
1015
- group.appendChild(path);
1222
+ return transformedCommands.join(' ');
1223
+ }
1224
+ /**
1225
+ * 生成样式键,用于路径分组
1226
+ */
1227
+ generateStyleKey(style) {
1228
+ return `${style.lineColor || '#000000'}-${style.lineWidth || 1}-${style.opacity || 1}`;
1016
1229
  }
1017
1230
  }
1018
1231
 
@@ -1120,7 +1333,7 @@ class ObstacleLayer extends BaseLayer {
1120
1333
  }
1121
1334
  }
1122
1335
 
1123
- var chargingPileImage = "data:image/png;base64,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";
1336
+ var chargingPileImage = "data:image/png;base64,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";
1124
1337
 
1125
1338
  /**
1126
1339
  * 充电桩图层
@@ -1310,7 +1523,7 @@ class SvgElementLayer extends BaseLayer {
1310
1523
  // 在transformGroup上应用变换:平移到中心,旋转,缩放,然后居中SVG
1311
1524
  const transform = [
1312
1525
  `translate(${center[0]}, ${center[1]})`,
1313
- `rotate(${(direction * 180) / Math.PI})`,
1526
+ `rotate(${-(direction * 180) / Math.PI})`,
1314
1527
  `scale(${userScale})`,
1315
1528
  `translate(${-originalWidth / 2}, ${-originalHeight / 2})`,
1316
1529
  ].join(' ');
@@ -1339,7 +1552,6 @@ class SvgElementLayer extends BaseLayer {
1339
1552
  renderSvgPlaceholder(svgGroup, center, metadata, style) {
1340
1553
  const size = (metadata?.scale || 1) * 20;
1341
1554
  const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
1342
- console.log('style==', style);
1343
1555
  rect.setAttribute('x', ((center[0] - size / 2) / 50).toString());
1344
1556
  rect.setAttribute('y', ((center[1] - size / 2) / 50).toString());
1345
1557
  rect.setAttribute('width', size.toString());
@@ -1409,9 +1621,10 @@ class VisionOffLayer extends BaseLayer {
1409
1621
  */
1410
1622
  const BOUNDARY_STYLES = {
1411
1623
  lineColor: '#ffffff',
1412
- fillColor: 'rgba(239, 255, 237, 0.15)', // 更鲜艳的绿色半透明填充,增强可见性
1624
+ fillColor: 'rgba(239, 255, 237, 0.1)', // 更鲜艳的绿色半透明填充,增强可见性
1413
1625
  lineWidth: 2,
1414
1626
  opacity: DEFAULT_OPACITIES.FULL,
1627
+ mowingLineColor: 'rgba(99, 216, 174, 1)',
1415
1628
  };
1416
1629
  const VISION_OFF_AREA_STYLES = {
1417
1630
  lineColor: 'rgba(108, 167, 255, 1)',
@@ -1436,14 +1649,15 @@ const DOODLE_STYLES = {
1436
1649
  lineColor: '#ff5722',
1437
1650
  fillColor: '#ff9800', // 粉色半透明填充
1438
1651
  lineWidth: DEFAULT_LINE_WIDTHS.TIME_LIMIT_OBSTACLE,
1439
- opacity: DEFAULT_OPACITIES.HIGH,
1652
+ opacity: DEFAULT_OPACITIES.DOODLE,
1440
1653
  };
1441
1654
  const PATH_EDGE_STYLES = {
1442
1655
  lineWidth: DEFAULT_LINE_WIDTHS.PATH,
1443
- opacity: DEFAULT_OPACITIES.LOW,
1444
- edgeLineColor: 'rgba(194, 203, 212)',
1656
+ opacity: DEFAULT_OPACITIES.MEDIUM,
1657
+ edgeLineColor: 'rgba(231, 238, 246)',
1445
1658
  transLineColor: 'transparent',
1446
- mowingLineColor: 'rgba(194, 203, 212)',
1659
+ mowedLineColor: 'rgba(231, 238, 246)',
1660
+ mowingLineColor: 'rgba(123, 200, 187)',
1447
1661
  };
1448
1662
  const CHANNEL_STYLES = {
1449
1663
  lineColor: 'purple',
@@ -1477,13 +1691,13 @@ const DEFAULT_STYLES = {
1477
1691
  function convertPointsFormat(points) {
1478
1692
  if (!points || points.length === 0)
1479
1693
  return null;
1480
- return points.map(point => {
1694
+ return points.map((point) => {
1481
1695
  if (point.length >= 2) {
1482
1696
  // 对前两个元素应用缩放因子,保留其他元素
1483
1697
  return [
1484
1698
  point[0] * SCALE_FACTOR,
1485
1699
  -point[1] * SCALE_FACTOR, // Y轴翻转,与Python代码一致
1486
- ...point.slice(2) // 保留第三个及以后的元素
1700
+ ...point.slice(2), // 保留第三个及以后的元素
1487
1701
  ];
1488
1702
  }
1489
1703
  return point;
@@ -1498,7 +1712,7 @@ function convertPositionFormat(position) {
1498
1712
  return null;
1499
1713
  return {
1500
1714
  x: position[0] * SCALE_FACTOR,
1501
- y: -position[1] * SCALE_FACTOR // Y轴翻转
1715
+ y: -position[1] * SCALE_FACTOR, // Y轴翻转
1502
1716
  };
1503
1717
  }
1504
1718
  /**
@@ -1507,9 +1721,146 @@ function convertPositionFormat(position) {
1507
1721
  function convertCoordinate(x, y) {
1508
1722
  return {
1509
1723
  x: x * SCALE_FACTOR,
1510
- y: -y * SCALE_FACTOR // Y轴翻转
1724
+ y: -y * SCALE_FACTOR, // Y轴翻转
1511
1725
  };
1512
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
+ }
1513
1864
 
1514
1865
  /**
1515
1866
  * 按Python逻辑创建路径段:根据连续的两点之间的关系确定线段类型
@@ -1527,11 +1878,11 @@ function createPathSegmentsByType(list) {
1527
1878
  let currentSegmentType = null;
1528
1879
  for (const currentPoint of list) {
1529
1880
  const currentCoord = {
1530
- x: currentPoint.postureX * SCALE_FACTOR,
1531
- y: -currentPoint.postureY * SCALE_FACTOR // Y轴翻转
1881
+ x: currentPoint.postureX,
1882
+ y: currentPoint.postureY
1532
1883
  };
1533
1884
  if (lastPoint !== null) {
1534
- // 判断上一个点和当前点是否需要绘制 (Python逻辑)
1885
+ // 判断上一个点和当前点是否需要绘制 (iso端逻辑)
1535
1886
  const lastShouldDraw = lastPoint.pathType === '00' || lastPoint.pathType === '01' || lastPoint.knifeRotation === '01';
1536
1887
  const currentShouldDraw = currentPoint.pathType === '00' || currentPoint.pathType === '01' || currentPoint.knifeRotation === '01';
1537
1888
  let segmentType;
@@ -1551,8 +1902,8 @@ function createPathSegmentsByType(list) {
1551
1902
  // 开始新段
1552
1903
  currentSegment = [
1553
1904
  {
1554
- x: lastPoint.postureX * SCALE_FACTOR,
1555
- y: -lastPoint.postureY * SCALE_FACTOR
1905
+ x: lastPoint.postureX,
1906
+ y: lastPoint.postureY
1556
1907
  },
1557
1908
  currentCoord
1558
1909
  ];
@@ -1772,6 +2123,136 @@ function calculateMapGpsCenter(mapData) {
1772
2123
  };
1773
2124
  }
1774
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
+ }
1775
2256
  /**
1776
2257
  * 通过 mapData 和 pathData 生成所有 boundary 的数据
1777
2258
  * @param mapData 地图数据
@@ -1780,41 +2261,21 @@ function calculateMapGpsCenter(mapData) {
1780
2261
  */
1781
2262
  function generateBoundaryData(mapData, pathData) {
1782
2263
  const boundaryData = [];
2264
+ let chargingPileBoundary = undefined;
1783
2265
  if (!mapData || !mapData.sub_maps) {
1784
2266
  return boundaryData;
1785
2267
  }
1786
2268
  // 第一步:收集所有TUNNEL数据的connection信息
1787
- const connectedBoundaryIds = new Set();
1788
- // 1.1 遍历子地图中的TUNNEL元素
1789
- for (const subMap of mapData.sub_maps) {
1790
- if (!subMap.elements)
1791
- continue;
1792
- // 找到该子地图中所有 type 为 TUNNEL 的元素
1793
- const tunnelElements = subMap.elements.filter(element => element.type === 'TUNNEL');
1794
- for (const tunnelElement of tunnelElements) {
1795
- const connection = tunnelElement.connection;
1796
- if (connection) {
1797
- // connection可能是单个数字或数组
1798
- if (Array.isArray(connection)) {
1799
- connection.forEach(id => connectedBoundaryIds.add(id));
1800
- }
1801
- else if (typeof connection === 'number') {
1802
- connectedBoundaryIds.add(connection);
1803
- }
1804
- }
1805
- }
1806
- }
1807
- // 1.2 遍历mapData中的tunnels字段
2269
+ const connectIds = [];
2270
+ // 遍历mapData中的tunnels字段
1808
2271
  if (mapData.tunnels && Array.isArray(mapData.tunnels)) {
1809
2272
  for (const tunnel of mapData.tunnels) {
1810
2273
  const connection = tunnel.connection;
1811
2274
  if (connection) {
1812
2275
  // connection可能是单个数字或数组
1813
2276
  if (Array.isArray(connection)) {
1814
- connection.forEach(id => connectedBoundaryIds.add(id));
1815
- }
1816
- else if (typeof connection === 'number') {
1817
- connectedBoundaryIds.add(connection);
2277
+ connection.sort();
2278
+ connectIds.push(connection.join('-'));
1818
2279
  }
1819
2280
  }
1820
2281
  }
@@ -1825,7 +2286,9 @@ function generateBoundaryData(mapData, pathData) {
1825
2286
  if (!subMap.elements)
1826
2287
  continue;
1827
2288
  // 每个sub_map的elements是边界坐标,没有sub_map只有一个boundary数据
1828
- const boundaryElement = subMap.elements.find(element => element.type === 'BOUNDARY');
2289
+ const boundaryElement = subMap.elements.find((element) => element.type === 'BOUNDARY');
2290
+ // 如果当前subMap存在充电桩且充电桩存在tunnel,说明当前subMap中的boundary是初始boundary,这个boundary不为孤立区域
2291
+ const hasTunnelToChargingPile = subMap.elements.some((element) => element.type === 'CHARGING_PILE' && element.tunnel);
1829
2292
  // 创建基础的 boundary 数据(来自 mapData)
1830
2293
  const boundary = {
1831
2294
  // 从 BOUNDARY 元素复制属性
@@ -1834,8 +2297,6 @@ function generateBoundaryData(mapData, pathData) {
1834
2297
  area: subMap?.area,
1835
2298
  points: convertPointsFormat(boundaryElement?.points) || [],
1836
2299
  type: boundaryElement.type,
1837
- // 判断是否为孤立子区域
1838
- isIsolated: !connectedBoundaryIds.has(boundaryElement.id)
1839
2300
  };
1840
2301
  // 如果有 pathData,尝试匹配对应的分区数据
1841
2302
  if (pathData) {
@@ -1851,185 +2312,43 @@ function generateBoundaryData(mapData, pathData) {
1851
2312
  boundary.endTime = partitionData.endTime;
1852
2313
  }
1853
2314
  }
2315
+ if (hasTunnelToChargingPile) {
2316
+ chargingPileBoundary = boundary;
2317
+ }
1854
2318
  boundaryData.push(boundary);
1855
2319
  }
1856
- return boundaryData;
1857
- }
1858
-
1859
- /**
1860
- * 数据格式化转换类 - TypeScript版本
1861
- * 对应Java FormatUtils.java中的bytes2Int函数
1862
- */
1863
- class FormatUtils {
1864
- /**
1865
- * 从data数组的第0位开始取4个字节组成一个int型数
1866
- * 对应Java: public static int bytes2Int(byte[] data)
1867
- *
1868
- * @param data 源字节数组
1869
- * @returns 返回int型数
1870
- */
1871
- // public static bytes2Int(data: Uint8Array): number {
1872
- // return FormatUtils.bytes2IntWithOffset(data, 0, false);
1873
- // }
1874
- /**
1875
- * 从data数组的第position位置开始取4个字节组成int型数
1876
- * 对应Java: public static int bytes2Int(byte[] data, int offset, boolean bigEndian)
1877
- *
1878
- * @param data 源字节数组
1879
- * @param offset 要组成int型数据的起始位置
1880
- * @param bigEndian 是否为大端
1881
- * @returns 返回int型数
1882
- */
1883
- static bytes2Int(data, offset, bigEndian) {
1884
- return FormatUtils.bytes2IntWidthLength(data, offset, bigEndian, 4);
1885
- }
1886
- /**
1887
- * 从data数组的第position位置开始取几个字节组成int型数
1888
- * 对应Java: public static int bytes2Int(byte[] data, int offset, boolean bigEndian, int length)
1889
- *
1890
- * @param data 源字节数组
1891
- * @param offset 要组成int型数据的起始位置
1892
- * @param bigEndian 是否为大端
1893
- * @param length 取的字节长度
1894
- * @returns 返回int型数
1895
- */
1896
- static bytes2IntWidthLength(data, offset, bigEndian, length) {
1897
- if (!data || offset < 0 || offset > data.length) {
1898
- return 0;
1899
- }
1900
- let result = 0;
1901
- try {
1902
- // 创建DataView来处理字节序
1903
- const buffer = new ArrayBuffer(length);
1904
- const view = new DataView(buffer);
1905
- // 将指定长度的数据复制到buffer中
1906
- const tempArray = new Uint8Array(buffer);
1907
- for (let i = 0; i < length && offset + i < data.length; i++) {
1908
- tempArray[i] = data[offset + i];
1909
- }
1910
- // 根据字节序读取数据(使用有符号整数,与Java保持一致)
1911
- if (length === 1) {
1912
- result = view.getInt8(0);
1913
- }
1914
- else if (length === 2) {
1915
- result = view.getInt16(0, !bigEndian); // DataView的littleEndian参数与bigEndian相反
1916
- }
1917
- else if (length === 4) {
1918
- result = view.getInt32(0, !bigEndian); // 改为 getInt32,返回有符号整数
1919
- }
1920
- else {
1921
- // 对于其他长度,手动处理
1922
- result = FormatUtils.manualBytes2Int(data, offset, bigEndian, length);
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);
1923
2329
  }
1924
2330
  }
1925
- catch (e) {
1926
- // console.error(
1927
- // `${FormatUtils.TAG}: bytes2Int: Exception : data = ${data}, offset = ${offset}`,
1928
- // e
1929
- // );
1930
- // console.log(
1931
- // `${FormatUtils.TAG}: bytes2Int: Exception = ${e instanceof Error ? e.message : e}`
1932
- // );
1933
- }
1934
- return result;
1935
2331
  }
1936
- /**
1937
- * 手动处理字节到整数的转换(用于非标准长度)
1938
- *
1939
- * @param data 源字节数组
1940
- * @param offset 起始位置
1941
- * @param bigEndian 是否为大端
1942
- * @param length 字节长度
1943
- * @returns 转换后的整数
1944
- */
1945
- static manualBytes2Int(data, offset, bigEndian, length) {
1946
- let result = 0;
1947
- if (bigEndian) {
1948
- // 大端序:最高位字节在最低地址
1949
- for (let i = 0; i < length && offset + i < data.length; i++) {
1950
- result = (result << 8) | (data[offset + i] & 0xff);
1951
- }
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;
1952
2337
  }
1953
2338
  else {
1954
- // 小端序:最低位字节在最低地址
1955
- for (let i = length - 1; i >= 0; i--) {
1956
- if (offset + i < data.length) {
1957
- result = (result << 8) | (data[offset + i] & 0xff);
1958
- }
1959
- }
1960
- }
1961
- return result;
1962
- }
1963
- /**
1964
- * 将字节数组转换为十六进制字符串
1965
- * 对应Java: public static String bytesToHex(byte[] bytes)
1966
- *
1967
- * @param bytes 字节数组
1968
- * @returns 十六进制字符串
1969
- */
1970
- static bytesToHex(bytes) {
1971
- if (!bytes) {
1972
- return 'null';
1973
- }
1974
- const hexArray = '0123456789ABCDEF'.split('');
1975
- const hexChars = [];
1976
- for (let j = 0; j < bytes.length; j++) {
1977
- const v = bytes[j] & 0xff;
1978
- hexChars.push(hexArray[v >>> 4]);
1979
- hexChars.push(hexArray[v & 0x0f]);
1980
- }
1981
- return hexChars.join('');
1982
- }
1983
- /**
1984
- * 将十六进制字符串转换为字节数组
1985
- * 对应Java: public static byte[] hexStringToByteArray(String s)
1986
- *
1987
- * @param s 十六进制字符串
1988
- * @returns 字节数组
1989
- */
1990
- static hexStringToByteArray(s) {
1991
- if (!s || s.length <= 0) {
1992
- return null;
1993
- }
1994
- s = s.trim();
1995
- const len = s.length;
1996
- const data = new Uint8Array(len / 2);
1997
- for (let i = 0; i < len - 1; i += 2) {
1998
- try {
1999
- const high = parseInt(s.charAt(i), 16);
2000
- const low = parseInt(s.charAt(i + 1), 16);
2001
- data[i / 2] = (high << 4) + low;
2002
- }
2003
- catch (e) {
2004
- console.log('hexStringToByteArray: ' + e);
2005
- }
2339
+ boundary.isIsolated = true;
2006
2340
  }
2007
- return data;
2008
2341
  }
2342
+ return boundaryData;
2009
2343
  }
2010
- FormatUtils.TAG = 'FormatUtils';
2011
- // 使用示例
2012
- // export function example() {
2013
- // // 示例1:从字节数组开头读取4字节整数(小端序)
2014
- // const data1 = new Uint8Array([0x12, 0x34, 0x56, 0x78]);
2015
- // const result1 = FormatUtils.bytes2Int(data1); // 应该返回 0x78563412
2016
- // // 示例2:从指定位置读取4字节整数(大端序)
2017
- // const data2 = new Uint8Array([0x00, 0x00, 0x12, 0x34, 0x56, 0x78]);
2018
- // const result2 = FormatUtils.bytes2Int(data2, 2, true); // 应该返回 0x12345678
2019
- // // 示例3:读取2字节整数
2020
- // const data3 = new Uint8Array([0x12, 0x34]);
2021
- // const result3 = FormatUtils.bytes2Int(data3, 0, false, 2); // 应该返回 0x3412
2022
- // console.log(`Result1: 0x${result1.toString(16)}`);
2023
- // console.log(`Result2: 0x${result2.toString(16)}`);
2024
- // console.log(`Result3: 0x${result3.toString(16)}`);
2025
- // // 示例4:十六进制字符串转换
2026
- // const hexString = "12345678";
2027
- // const byteArray = FormatUtils.hexStringToByteArray(hexString);
2028
- // const backToHex = FormatUtils.bytesToHex(byteArray);
2029
- // console.log(`Original: ${hexString}`);
2030
- // console.log(`To bytes: ${byteArray}`);
2031
- // console.log(`Back to hex: ${backToHex}`);
2032
- // }
2344
+
2345
+ var RealTimeDataType;
2346
+ (function (RealTimeDataType) {
2347
+ RealTimeDataType[RealTimeDataType["LOCATION"] = 1] = "LOCATION";
2348
+ RealTimeDataType[RealTimeDataType["PROCESS"] = 2] = "PROCESS";
2349
+ RealTimeDataType[RealTimeDataType["PARTITION"] = 3] = "PARTITION";
2350
+ RealTimeDataType[RealTimeDataType["STATUS"] = 4] = "STATUS";
2351
+ })(RealTimeDataType || (RealTimeDataType = {}));
2033
2352
 
2034
2353
  /**
2035
2354
  * 射线法判断点是否在多边形内部
@@ -2125,50 +2444,6 @@ const getPartitionId = (partitionBoundary, postureX, postureY) => {
2125
2444
  })?.id;
2126
2445
  return partitionId;
2127
2446
  };
2128
- /**
2129
- *
2130
- * 支持分区割草后的解析,加入了分区数量、分区列表
2131
- * 8 割草路径类型
2132
- * 4 割草启动类型 0:自动 1:手动
2133
- * 4 当前割草边界id
2134
- * 4 当前边界割草百分比 比如9800----98%
2135
- * @returns
2136
- */
2137
- const parseMapWorkPosition = (mapWorkPosition) => {
2138
- let isMowing = false;
2139
- let mowStartType = null;
2140
- let currentMowBoundaryId = null;
2141
- let currentMowProgress = null;
2142
- const bytes = new Uint8Array(mapWorkPosition.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || []);
2143
- if (mapWorkPosition.length >= 8) {
2144
- // 以下两种状态认为是割草中
2145
- // 1. action为8,且subAction为6
2146
- // 2. action为5
2147
- const action = FormatUtils.bytes2Int(bytes, 0, true);
2148
- if (action === ACTION_BOUNDARY_TASK) {
2149
- const subAction = FormatUtils.bytes2Int(bytes, 4, true);
2150
- if (subAction === ACTION_BLOCK_TRANSFER) {
2151
- // action=8且subAction=6:边界任务中的块转移
2152
- isMowing = true;
2153
- }
2154
- }
2155
- else if (action === ACTION_BLOCK_COVER) {
2156
- // action=5:块覆盖割草
2157
- isMowing = true;
2158
- }
2159
- if (mapWorkPosition.length >= 16) {
2160
- mowStartType = FormatUtils.bytes2Int(bytes, 8, true);
2161
- currentMowBoundaryId = FormatUtils.bytes2Int(bytes, 12, true);
2162
- currentMowProgress = FormatUtils.bytes2Int(bytes, 16, true);
2163
- }
2164
- }
2165
- return {
2166
- isMowing,
2167
- mowStartType,
2168
- currentMowBoundaryId,
2169
- currentMowProgress,
2170
- };
2171
- };
2172
2447
  /**
2173
2448
  * 处理实时数据的消息,这里的实时数据消息有两种,一种是实时轨迹,一种是割草进度,其中这两种下发的时间频次不一样
2174
2449
  * 实时轨迹的路径需要依靠割草进度时候的割草状态判断,目前只能根据上一次获取到的割草进度的状态来处理,如果一开始没有割草的状态,则默认为不割草,后续会根据割草进度来更新
@@ -2176,7 +2451,7 @@ const parseMapWorkPosition = (mapWorkPosition) => {
2176
2451
  * @param param0
2177
2452
  * @returns
2178
2453
  */
2179
- const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundary, }) => {
2454
+ const handleMultipleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundary, }) => {
2180
2455
  // 先将数据进行倒排,这样好插入数据
2181
2456
  if (realTimeData.length > 0) {
2182
2457
  realTimeData.reverse();
@@ -2192,7 +2467,6 @@ const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundar
2192
2467
  // 割草轨迹
2193
2468
  const { postureX, postureY, vehicleState } = item;
2194
2469
  const currentPartitionId = getPartitionId(partitionBoundary, Number(postureX), Number(postureY));
2195
- console.log('currentPartitionId===', currentPartitionId);
2196
2470
  if (currentPartitionId && newPathData?.[currentPartitionId]) {
2197
2471
  const currentPathData = newPathData[currentPartitionId];
2198
2472
  newPathData[currentPartitionId] = {
@@ -2204,7 +2478,7 @@ const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundar
2204
2478
  postureY: Number(postureY),
2205
2479
  knifeRotation: mowingStatus && vehicleState === RobotStatus.MOWING ? '01' : '00', // "knifeRotation": "01",//刀盘是否转动 00-否 01-是
2206
2480
  // knifeRotation: '01', // "knifeRotation": "01",//刀盘是否转动 00-否 01-是
2207
- pathType: '01', //"pathType": "01",//路径类型 : 00-巡边 01-弓字型割草 02-地图测试 03-转移路径 04-避障路径 05-恢复/脱困路径
2481
+ pathType: '', //"pathType": "01",//路径类型 : 00-巡边 01-弓字型割草 02-地图测试 03-转移路径 04-避障路径 05-恢复/脱困路径
2208
2482
  partitionId: currentPartitionId.toString(), // TODO:不知道为什么这里的id需要是字符串类型?
2209
2483
  },
2210
2484
  ],
@@ -2213,21 +2487,20 @@ const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundar
2213
2487
  }
2214
2488
  else if (item.type === REAL_TIME_DATA_TYPE.PROCESS) {
2215
2489
  // 割草进度
2216
- const { mapWorkPosition } = item;
2217
- if (mapWorkPosition) {
2218
- // 解析mapWorkPosition数据,获取到当前的割草状态,割草进度,割草边界id
2219
- const { isMowing, currentMowBoundaryId, currentMowProgress } = parseMapWorkPosition(mapWorkPosition);
2220
- // const { isMowing, currentMowBoundaryId, currentMowProgress } = parseMapWorkPosition(
2221
- // '000000060000000000000001000000030000001BA5A5A5A5A5A5A5A5240000A024010D50FFFFFFED000000000000000000000000000000010000000000000000'
2222
- // );
2223
- console.log('isMowing===', isMowing, currentMowBoundaryId, currentMowProgress);
2224
- mowingStatus = isMowing;
2225
- const currentPartitionId = currentMowBoundaryId ? currentMowBoundaryId.toString() : null;
2226
- if (currentMowProgress && currentPartitionId && newPathData?.[currentPartitionId]) {
2227
- newPathData[currentPartitionId].partitionPercentage = currentMowProgress / 100;
2228
- newPathData[currentPartitionId].finishedArea =
2229
- (newPathData[currentPartitionId].area * currentMowProgress) / 10000;
2230
- }
2490
+ const { action, subAction, currentMowBoundary, currentMowProgress } = item;
2491
+ // 设置状态
2492
+ if ((action === ACTION_BOUNDARY_TASK && subAction && subAction === ACTION_BLOCK_TRANSFER) ||
2493
+ action === ACTION_BLOCK_COVER) {
2494
+ mowingStatus = true;
2495
+ }
2496
+ else {
2497
+ mowingStatus = false;
2498
+ }
2499
+ const currentPartitionId = currentMowBoundary ? currentMowBoundary.toString() : null;
2500
+ if (currentMowProgress && currentPartitionId && newPathData?.[currentPartitionId]) {
2501
+ newPathData[currentPartitionId].partitionPercentage = currentMowProgress / 100;
2502
+ newPathData[currentPartitionId].finishedArea =
2503
+ (newPathData[currentPartitionId].area * currentMowProgress) / 10000;
2231
2504
  }
2232
2505
  }
2233
2506
  });
@@ -2236,6 +2509,39 @@ const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundar
2236
2509
  isMowing: mowingStatus,
2237
2510
  };
2238
2511
  };
2512
+ /**
2513
+ * 根据实时数据,获取到割草状态
2514
+ * @param realTimeData 实时数据
2515
+ * @param isMowing 上一次的割草状态
2516
+ * @returns 新的割草状态
2517
+ */
2518
+ const getProcessMowingDataFromRealTimeData = ({ realTimeData, isMowing, pathData, }) => {
2519
+ let newMowingStatus = isMowing;
2520
+ let newPathData = pathData || {};
2521
+ // 找到返回的第一个实时进度的点
2522
+ const firstProcessData = realTimeData.find((item) => item.type === RealTimeDataType.PROCESS);
2523
+ if (firstProcessData) {
2524
+ // console.log('firstProcessData==', firstProcessData);
2525
+ const { action, subAction, currentMowBoundary, currentMowProgress } = firstProcessData;
2526
+ // 设置状态
2527
+ if ((action === ACTION_BOUNDARY_TASK && subAction && subAction === ACTION_BLOCK_TRANSFER) ||
2528
+ action === ACTION_BLOCK_COVER) {
2529
+ newMowingStatus = true;
2530
+ }
2531
+ else {
2532
+ newMowingStatus = false;
2533
+ }
2534
+ if (currentMowBoundary && newPathData?.[currentMowBoundary]) {
2535
+ newPathData[currentMowBoundary].partitionPercentage = currentMowProgress / 100;
2536
+ newPathData[currentMowBoundary].finishedArea =
2537
+ (newPathData[currentMowBoundary].area * currentMowProgress) / 10000;
2538
+ }
2539
+ }
2540
+ return {
2541
+ isMowing: newMowingStatus,
2542
+ pathData: newPathData,
2543
+ };
2544
+ };
2239
2545
 
2240
2546
  var iMower = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAABYlAAAWJQFJUiTwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABElSURBVHgBpVp7cFTXef/Ouc99SitYwUorIMJPsNtg/H4ATmwT3NaNmxK3rtvOJGPS6UzHaf9oJ/0HmGkz9UwnTetHZ/xHk5nWdgPOlMbAuG7S4tZjdzAqxoAchACDkBAsaKV96D7POfnOuffuLkJItnPFZe+9ex7f73t/312AT3kIIUh8RZJTPpPnjh07aPK5a9curfM+GRPPv+bZ9c5PS9eiA+ViRJIr2nMOHDhAK5VeKm+KxTQ9evRjsfzObto84YlMxiK2bRLX9UW1apJSaQJcd6lw3Tq17RyXc5pFTyxzyqK7u0IPHZrmU1MHWalUIoVCgQ8PbxXbt0NrN9xcfC4AEeGSGYob2u7dw0jwjCYJ6erK0IYm9Jxhdmum2YVUdVuU5uUEHEyEDuXWQpJknCkYnyGC1kCLHjPGIAjYmNCNKpviVUqbbJLYzNO7mNU7wy/s3cu2IxJckSe0fGYACbcdp6wFwUXT8xpGodz3NAXyO7ja7fh9PhoLn+sg7d1ruMgxXOf16kTjNSh2+UVIhyO5K6xw+jTfunUr/0wAJPG7d++mxWKRVCqW0TQ8qyeTviGTSf0QN1mLLAHBhRwH6i9ZumOL73/veahWr+AGBAqFHnjuz/7imh2lvNQfJQoMpUorxxqN5pMBrY+WMxnfNM1w/fr1TEpiPlr1+YiXn4ODg/T0aV9vNj2r0GffbKdSb4QhL4chA/xEABw4npJ4kSDouO5dWoJGfVY9Mwwbmg0npp+0gCgAMeHy1HX81LWBdDa1p1kVj525dGXSMGbdvXvrUpVhPhD6fKhQbTSnXKZN47yFRpmx7dSfszAse14AgRei7obAGQJIJAFzpIu3jzz66y1C5X1TglE6Ez+IwUTcJ6Ah8Yahg2HqYFpGOZWz/mrWyzxn0IxYuVJNCGIQV21F5pOABFCpgO1pWs4weDmXzR90HR+9iQc+AmAoBWmEajou6rgOnDl9EiYmxpXayKNarcaERlukUilARih16uvrh1KprK4jsyeAOEAzNDBNA+yUCZZt1pkbfqk25Y37+Woj53ke2kIw1xb0ucTv3LmTrNy4UTfTSwwxdcFezUa/MaavB3fWRQB+xH0uFG2S2B//+HX45MxowvgWR8RVvL5KGK3rdevuhoe/vBl6CkvUA8oixihOU5Jbe2bfnotW6fExusLNFrI+zHPQuQ82btxIMxWLpPwLxkNnd309XZv4I8/1FfGS+2HM/fHxMXjpxb+NiFdcjIgi8TWNRR0ZKrQ4nSiRvD/84QfwMq4xMTGm1pRr+6imcj/PDSBkrH/1+f/5JksVjDDs1b6OjmUxAKJS2SQsi+ueZ1uWM31vEAq1aBAEuEmk+9XqFPzwBy+D57ltAoG09DMx1E7iSQKQtsdJkJ7vwg/+6R9hGteUazM8pZR9X+4pgLnuCjrrmzL+/DF6xcUAkGr1FYrEU00DPQg5DcOwxR25uBTvOwfebhGfeJOEtRGxMcHxNaVtQP2o//fccxfcffddcMstN6tpcq09e36k1paeTdpYiCBQAuAjDZrmoqqHGsxzXOOFPG8AQfka5zr1GScM9V0RzqPFXdeFj44MxQR18FtERLd1P5EGQC6Xg2eeeRqe+f3fU9cSSOJuJ8Yn4N/3/AT+5dVXwXFm0dBtwKil7IwzJgSCIMTUDKOHjIywhSUgF16+vBHbmQuh72MKgIuIxOcLuHhxItLlDr1XC9GO+9gG5JC+/j547V9fhWe3PYueKK0kGcTcDfC6uKwXnv3Ws/Da668hc5qKWZF7lpJABiITLcvGRafhwQdTZEEJyGixc+duuPFGJgyDEs6E3FAmI2pRCWJ6eiomMglIEc9FwnPS9kOYoMHLL78EvcVeJDaEer0Be9/cByMnTiiQGGHxvANKfX3Qi0B+9+mvwn++/R74CFAyS6muypl8HG3Rd999VywIQHJ1//79pNHQCKWchEFAeKw+rdRBtFUHYrIj9afRXSt1JfDCCy9AsbdXcXr/vn3w99//BwRRb+0nwUiQ3/7T5+ChDRvUnHXrboX33j8c2YN0qwFvcb1QeHBhFZJxYOXKlQolcltw5BoPg2ix+IwQRBxO9P8qdYqN4LHNm2GJ5HzAZGSH7/71d6HRbCgvRDUafeI5eXESvvOdv4RDHxxS3O4qdEF3d15xXzIvxP2D2AtUq9dKYK4NiOPHUSz6LNF1Rhlrex5pByK2g7mepjWfJu6SwH33PxDpOzLhpRdfVLmOpnIeouxDfsrx8pmc8/zzf6PASq+3YkUJor3RA6I7lcyUNGG9sLgEJifPkjBMCzkp8HzFBRHnPJyLFt/bIOKELMksSaRaa9bepog/cWIELl26FBGuzghEAobEzyo4ZmRkBOdwSKXTivgwkCoUCAtXlDQBrG0njvMBkDtLL4ScUKOYlprkcWiPuM+VCrVyMjxu69Hh/uWW+oyDrTpMK6U4emFyMvZIcdLWAhKDiJ9JIKMnTyoJ6IbRkoDfVTrCDd6iaW4yNzcOEInSMGaI5wFcfugP39BOHXoCdb838kICs0UrcqN4/8ajBbgPiU+48r2P6vB3R2fVJjMzM5DOZMBGF5gEsiQZmkODkioqEvQsWaoMPlT5FofGqvV7mwO/+nYQuCSTylJZqs7NSK/JLdauBck5TCcsYL2rneqdX/tnEIkHEhiI8oqO+5ZqcE/aA17DYgo9izy/vQpLNDNSqVOnRpUN9A+swDnZlu5rWlv/W5E6VsP+8goVI6amptVeTumWIRxLUjRF0mkmGo3DYq4ErgFwHK14yRJNjUKOC6LRCo+jsFSjbDavNh2uYcBBonmtHn/WYOxyA+phZAvHjn6kuCmLmS8/tqWlNq38PzHo+Hziyd9G6dpqzvmx8UgyQlIrpWGIGq6fza5bNBIrXQjDrHCQajQuTgU0VdoPkRvVdQNy+TzUMapvOwLw/qQL/3fBhbfHA/jWUUlc5GZ/+tY+VKOaksLmx5+Eex/Y0PZAGm1VYhpe34ffbX78q8qN1msNmJ6qKpAm1cYYoWh4nqJvbGxm4YosroXVtYEAfIzBhqZXkKZZIkg60nWB+XsRy8Ua/Kxmwk9njJYet9IKvHA9B37ybz+Crz31B+rZU09/AwZX3wz/i4nghYnzatDg4E1w5z0Pwl33PKA4L+efPfMJZqI+cjvrINCGjKh+QDk6Jo6BGxYEIA+spESh4KNW+EwTqahupzCFChQBwH/ZXFc7dSZRGkE7UuhoHIcDP3tLVWGbf+231HdfvONe+OL6e9ttjHgN6a3k9HOnTsP4+XFJPNbG9DLWmYy5hBtGU2SzS+btSlwDYGhIhmx0g2YeXZcTAjFCXdPO4frlJB4UCkvbE0hHoQ6ddEnd1uA/9u+By1ifPooqUsDKKwLYITMUb/XKZfgYbUanJrpfG9VKRxvRptAKmdCp0EiWo3Gjn3DEogAeeaSAelZnmDpiKLak5Yaop+eTPEHGgmW9fXEsaOc9rWKRtANdkjv9/wfvwdDBd+ELqEK33r5O1cdylEyfPz56GOvpE/CVLU+Bnc9ExGuqNh5B+ws1IoImmxV6kEIvVFk0mRPY20Q1qvM8Mzh60tAF7mNtdjaiNTLkVDqrvFGjWe/wyXMcREsyqFyaBEKw/DyJxI7EA+LOBKGQyXZBPt+jiKcqtSBgWOYZLAawHtRDg4nw/PkAq8Xi4qnE8PAw5sHy2mWUikAPCTMM8ywu7CR9H6lG5fIX2hn1NRVZG4QiSLlMTXkfyV1Uj5jTunpeWj6A3k1X49R8NPCcnZE5t8+5H4o0ZQMDOb59+ya+IAB5YD9SDGAml812M44ZNSqgJ5uiaAdKjVRDC9WoVFp5dbnYkQfFd/Ez6XDi7JNIEG0gKi/C65tu+pW4KcbVJ4IZ1TTh4ySf61ZoY3BevToVAsDi2agk4uDBBhZidUxLOPZhRAC4mGWZH4HyOJEUlmLnrYhnNA/aRXtHggeJnUgvFXM2AhVtKwH0FktKfZJWjaQonbaPYCHoy72pLmnQ2G4MsHOj8LwSkMStWQNsaspAQ9bQlVIPG3puriv7YcLtpLhZv34DGpvVIrpd4ItWinBV5zdOBJM0XDdMWHfHhqiEFFGmK0FlUunDSLRLMYdjTQibTYeXBgejILQYAHlgB0zkcjQEx0QuMB/p9VK2fcEy9dPyex7XyLadgfsf2ILpgtnhfWKVSeqFjjZKJwflnAdwbgrXaGW66rk+kcrYk4QyF3ulXhg6YbGoqS71fLReA0CqEXbn8KqL6TqqD+i+rVsOMHAwuzyimCpzIxHVCflcN2zY8BuYPy2H+RwSgc4cKOL+kqXLYcPGJ3BuoZUk8lg6+XzuAPZPHAhlNkOxu2kFExMTTLbY5wMwb3NXGvIrrwyJgYErAQZmzzByLsYCp7ik573KxUsPcx52Swkkh1Sju+/6EtRqU9i1GId6YwpT4qCTLaojkUOwuVwPgu1Vz8Kw3S2Mkjqzury3530qyGxIhRt6od/dnfLHxnwG1zn0632xbdt6/uabQ6Fh1D2us1mNmE30zVeWl5btOnni5DZZbXXqt7yS6rBy5Y0tzneqrOh4Fvg+AIh2/iT9Pnam+0vLdqNkm6FOZ/GBA7rwGw2Hy1dPnwlA/GpJXLlS511d6aAZzrpUhzrmdrVlxaXHZqan3zp37uxXonKzHZA7nUSn7c59PteZSMMt95feKi4rHpPuT4RhIwy4a2GHNItZxJYtW8X13tBcVwJyr9tvz7FLl8KgXuEOz/FZTK5rxNDSN91ww39lU6nK8Z8f/83A9aLXTNBOKEjcJwLoNAcC8/WssRM3c8utt75ZHuj/EG1rBi1hBr1YAyXh+H532Gicm9f7LApAIsZXomzNmm8GqdRxp+449ZyRt/BlnY0GafSv6D/WN9B37tToqduuXL68otGo92LbMS+J93031yK0g2ZM1OryAm2mnsvnKn3l8siKgZUnUSrTaMRVSkQVX8LUNG7UAydwA8QwOlrkCwEgsJAIhKBDQ0Pa6dN1S77ssFiY94W+FBMs2dDvwcDajWqVxeiaQgIsmfdx6XBE7EdbLQR5y4X6TxDVJsONPRziIWkNvKlSwa8A41NCGBXfpNN26OMrnYqP3ie83vuxBSUQS4HvkIHt9LBPnHpTwyRSDwQNBWe2jS17zl1BIc84SxGqGerkTJNJjQQf9eswZkQWga5ePhShdEB4ulwQlwhWx/g/IywdQUCVz842ltp5dxSc4E+2LEz8ogDksQM3/u/iJT4yUkfPmHPyecxhsF9FNO4TrLOx5JvWqMjgNhYSbSDxGleZg4gTBqIolyjQWIXstyFCnwnqmzp1goDXDUIa4axXQ2tuYtHvjMKUN3UwxWALLHosCkAWuZswr61UdnvF4u1Yu48zDO0sbxY8xvwmFnw21w1bowHWlsQALgtdTnVkHb4YxKJKx+ZUJAKsvLimE9nvDgkNsVA0XKKlnIC6Tle64F64MOsODq4Kht/5BP3+O9hlf1gsSh58yiN58Y01s27bg7rrVo0GWKYGgWmyNJbQvsENhuGIqxcRNMRLU7rIgOjYVfDVM41LEJaFIPANnqaZQQMMv2Cw4JJW9+FCX1AoqBfbknA+X/L2uQHEIFRPC2seOjg4ROv1Jdpw5RO9iIVTrcY0o0cnKbuHum6F6K5GbJsJV32mom4H3uuuxU3T55XKZZHJdGM0tJhMWwDWsOFhUL+TWOz3EZ3H4irUiTZ2LFI75FsPTJnQza4KisUD8lUGzWLnLJXSiQOT4uKMid09DeSPP2ZmPLFsWVmMjp6EG25oQKPREKtWbeWbNgFHiZKtW+9r+frPQvwvfcQ/jYH5fi6T/KwmGSdpu/rnNFd9B/P9xCbxwgv9/OYXnA/kSHUmmvYAAAAASUVORK5CYII=";
2241
2547
 
@@ -2249,13 +2555,15 @@ var hNoPosition = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEoAAABKCAYAAAA
2249
2555
 
2250
2556
  var hDisabled = "data:image/png;base64,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";
2251
2557
 
2558
+ var x3Edger = "data:image/png;base64,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";
2559
+
2252
2560
  var x3Mower = "data:image/png;base64,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";
2253
2561
 
2254
2562
  var x3NoPosition = "data:image/png;base64,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";
2255
2563
 
2256
2564
  var x3Disabled = "data:image/png;base64,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";
2257
2565
 
2258
- function getMowerImageByModal(mowerModal) {
2566
+ function getMowerImageByModal(mowerModal, hasEdger) {
2259
2567
  if (mowerModal.includes('i')) {
2260
2568
  return iMower;
2261
2569
  }
@@ -2263,7 +2571,7 @@ function getMowerImageByModal(mowerModal) {
2263
2571
  return hMower;
2264
2572
  }
2265
2573
  else if (mowerModal.includes('x3')) {
2266
- return x3Mower;
2574
+ return hasEdger ? x3Edger : x3Mower;
2267
2575
  }
2268
2576
  return iMower;
2269
2577
  }
@@ -2291,10 +2599,12 @@ function getNoPositionMowerImageByModal(mowerModal) {
2291
2599
  }
2292
2600
  return iNoPosition;
2293
2601
  }
2294
- function getMowerImage(positonConfig) {
2295
- const model = positonConfig.vehicleModel || '';
2602
+ function getMowerImage(positonConfig, modelType, hasEdger) {
2603
+ if (!positonConfig)
2604
+ return '';
2605
+ const model = modelType?.toLowerCase() || 'i';
2296
2606
  const state = positonConfig.vehicleState;
2297
- const mowerImage = getMowerImageByModal(model);
2607
+ const mowerImage = getMowerImageByModal(model, hasEdger);
2298
2608
  const disabledImage = getDisabledMowerImageByModal(model);
2299
2609
  const noPositionImage = getNoPositionMowerImageByModal(model);
2300
2610
  const positonOutOfRange = isOutOfRange(positonConfig);
@@ -2329,9 +2639,9 @@ function isInvalidPosition(positonConfig) {
2329
2639
  }
2330
2640
  function isOutOfRange(positonConfig) {
2331
2641
  return (positonConfig.postureX != null &&
2332
- Math.abs(positonConfig.postureX) > 1000 * SCALE_FACTOR &&
2642
+ Math.abs(positonConfig.postureX) > 1000 &&
2333
2643
  positonConfig.postureY != null &&
2334
- Math.abs(positonConfig.postureY) > 1000 * SCALE_FACTOR);
2644
+ Math.abs(positonConfig.postureY) > 1000);
2335
2645
  }
2336
2646
 
2337
2647
  /** Detect free variable `global` from Node.js. */
@@ -4397,7 +4707,7 @@ function cloneBuffer(buffer, isDeep) {
4397
4707
  }
4398
4708
 
4399
4709
  /** Built-in value references. */
4400
- var Uint8Array$1 = root.Uint8Array;
4710
+ var Uint8Array = root.Uint8Array;
4401
4711
 
4402
4712
  /**
4403
4713
  * Creates a clone of `arrayBuffer`.
@@ -4408,7 +4718,7 @@ var Uint8Array$1 = root.Uint8Array;
4408
4718
  */
4409
4719
  function cloneArrayBuffer(arrayBuffer) {
4410
4720
  var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
4411
- new Uint8Array$1(result).set(new Uint8Array$1(arrayBuffer));
4721
+ new Uint8Array(result).set(new Uint8Array(arrayBuffer));
4412
4722
  return result;
4413
4723
  }
4414
4724
 
@@ -4759,6 +5069,36 @@ var merge = createAssigner(function(object, source, srcIndex) {
4759
5069
  */
4760
5070
  var round = createRound('round');
4761
5071
 
5072
+ /**
5073
+ * 工具模块类型定义
5074
+ */
5075
+ /**
5076
+ * 路径段类型枚举
5077
+ */
5078
+ var PathSegmentType;
5079
+ (function (PathSegmentType) {
5080
+ PathSegmentType["EDGE"] = "edge";
5081
+ PathSegmentType["MOWING"] = "mowing";
5082
+ PathSegmentType["TRANS"] = "trans";
5083
+ })(PathSegmentType || (PathSegmentType = {}));
5084
+ /**
5085
+ * 单位类型枚举
5086
+ */
5087
+ var UnitsType;
5088
+ (function (UnitsType) {
5089
+ UnitsType["Metric"] = "Metric";
5090
+ UnitsType["Imperial"] = "Imperial";
5091
+ })(UnitsType || (UnitsType = {}));
5092
+ /**
5093
+ * 面积单位类型枚举
5094
+ */
5095
+ var UnitsAreaType;
5096
+ (function (UnitsAreaType) {
5097
+ UnitsAreaType["SQUARE_METER"] = "m\u00B2";
5098
+ UnitsAreaType["SQUARE_FOOT"] = "ft\u00B2";
5099
+ UnitsAreaType["ACRE"] = "ac";
5100
+ })(UnitsAreaType || (UnitsAreaType = {}));
5101
+
4762
5102
  /**
4763
5103
  * 默认航向相对于canvas的偏移角度: 航向默认是东
4764
5104
  */
@@ -4786,10 +5126,6 @@ function radToDegree(radian) {
4786
5126
  function distance(x1, y1, x2, y2) {
4787
5127
  return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
4788
5128
  }
4789
- // 计算前进方向和车头的夹角
4790
- function calAngle(x1, y1, x2, y2) {
4791
- return Math.atan2((y2 - y1), (x2 - x1));
4792
- }
4793
5129
  const mathRound = (value, decimals = 2) => {
4794
5130
  return Number.isInteger(value) ? value : round(value, decimals);
4795
5131
  };
@@ -4836,17 +5172,6 @@ function formatNumberWithMetricPrefix(value, round = true, decimals = 2) {
4836
5172
  return `${mathFn(value / 1000000000, decimals)}B`;
4837
5173
  }
4838
5174
  }
4839
- var UnitsType;
4840
- (function (UnitsType) {
4841
- UnitsType["Metric"] = "metric";
4842
- UnitsType["Imperial"] = "imperial";
4843
- })(UnitsType || (UnitsType = {}));
4844
- var UnitsAreaType;
4845
- (function (UnitsAreaType) {
4846
- UnitsAreaType["SQUARE_METER"] = "m\u00B2";
4847
- UnitsAreaType["SQUARE_FOOT"] = "ft\u00B2";
4848
- UnitsAreaType["ACRE"] = "ac";
4849
- })(UnitsAreaType || (UnitsAreaType = {}));
4850
5175
  /**
4851
5176
  * 转换割草面积的方法
4852
5177
  * @param area 面积数值(单位:m²)
@@ -5035,6 +5360,25 @@ class BoundaryBorderLayer extends BaseLayer {
5035
5360
  this.scale = 1;
5036
5361
  this.level = LAYER_LEVELS.BOUNDARY_BORDER; // 中等层级
5037
5362
  this.type = LAYER_DEFAULT_TYPE.BOUNDARY_BORDER;
5363
+ this.boudaryBorderPaths = {};
5364
+ this.mowingBoundarys = [];
5365
+ }
5366
+ /**
5367
+ * 设置当前割草任务的边界
5368
+ */
5369
+ setMowingBoundarys(mowingBoundarys) {
5370
+ if (!mowingBoundarys) {
5371
+ this.mowingBoundarys = this.elements?.map(item => item?.originalData?.id);
5372
+ }
5373
+ else {
5374
+ this.mowingBoundarys = mowingBoundarys;
5375
+ }
5376
+ }
5377
+ /**
5378
+ * 获取当前割草任务的边界
5379
+ */
5380
+ getMowingBoundarys() {
5381
+ return this.mowingBoundarys;
5038
5382
  }
5039
5383
  /**
5040
5384
  * SVG渲染方法
@@ -5044,18 +5388,38 @@ class BoundaryBorderLayer extends BaseLayer {
5044
5388
  return;
5045
5389
  }
5046
5390
  this.scale = scale;
5047
- // 只渲染边界边框类型的元素
5391
+ // 将元素分为两组:非割草边界和割草边界
5392
+ const nonMowingElements = [];
5393
+ const mowingElements = [];
5394
+ // 只处理边界边框类型的元素
5048
5395
  for (const element of this.elements) {
5049
5396
  if (element.type === 'boundary_border') {
5050
- 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
+ }
5051
5406
  }
5052
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
+ }
5053
5416
  }
5054
5417
  /**
5055
5418
  * 渲染边界边框
5056
5419
  */
5057
5420
  renderBoundaryBorder(svgGroup, element) {
5058
- const { coordinates, style } = element;
5421
+ const { coordinates, style, originalData } = element;
5422
+ const { id } = originalData || {};
5059
5423
  if (coordinates.length < 2)
5060
5424
  return;
5061
5425
  // 1. 先遍历所有的coordinates,把所有点分为若干段的path
@@ -5066,20 +5430,21 @@ class BoundaryBorderLayer extends BaseLayer {
5066
5430
  return;
5067
5431
  if (segment.type === 2) {
5068
5432
  // type=2: 直接添加到svgGroup中
5069
- this.createDirectPath(svgGroup, segment.points, style);
5433
+ this.createDirectPath(svgGroup, segment.points, style, id);
5070
5434
  }
5071
5435
  else if (segment.type === 1) {
5072
5436
  // type=1: 使用PathMeasure逻辑生成平行路径
5073
5437
  // this.createDirectPath(svgGroup, segment.points, style);
5074
- this.createParallelPathsWithMeasure(svgGroup, segment.points, style);
5438
+ this.createParallelPathsWithMeasure(svgGroup, segment.points, style, id);
5075
5439
  }
5076
5440
  });
5077
5441
  }
5078
5442
  /**
5079
5443
  * 创建直接路径(type=2)
5080
5444
  */
5081
- createDirectPath(svgGroup, points, style) {
5082
- const strokeColor = style.lineColor;
5445
+ createDirectPath(svgGroup, points, style, id) {
5446
+ const isMowing = this.mowingBoundarys.includes(Number(id));
5447
+ const strokeColor = isMowing ? style.mowingLineColor : style.lineColor;
5083
5448
  const lineWidth = dp2px(style.lineWidth || 3);
5084
5449
  const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
5085
5450
  // 构建路径数据
@@ -5102,13 +5467,18 @@ class BoundaryBorderLayer extends BaseLayer {
5102
5467
  path.setAttribute('opacity', (style.opacity || 1).toString());
5103
5468
  path.setAttribute('vector-effect', 'non-scaling-stroke');
5104
5469
  path.classList.add('vector-boundary-solid');
5470
+ if (!this.boudaryBorderPaths[id]) {
5471
+ this.boudaryBorderPaths[id] = [];
5472
+ }
5473
+ this.boudaryBorderPaths[id].push(path);
5105
5474
  svgGroup.appendChild(path);
5106
5475
  }
5107
5476
  /**
5108
5477
  * 使用PathMeasure逻辑创建平行路径(type=1)
5109
5478
  */
5110
- createParallelPathsWithMeasure(svgGroup, points, style) {
5111
- const strokeColor = style.lineColor;
5479
+ createParallelPathsWithMeasure(svgGroup, points, style, id) {
5480
+ const isMowing = this.mowingBoundarys.includes(Number(id));
5481
+ const strokeColor = isMowing ? style.mowingLineColor : style.lineColor;
5112
5482
  const lineWidth = dp2px(style.lineWidth || 3);
5113
5483
  // 获取当前SVG的缩放级别,计算固定屏幕像素间距
5114
5484
  const fixedScreenDistance = lineWidth; // 固定的屏幕像素距离
@@ -5131,6 +5501,10 @@ class BoundaryBorderLayer extends BaseLayer {
5131
5501
  // 或者可以根据当前缩放级别动态计算dash array
5132
5502
  path.style.strokeDasharray = `${lineWidth}px ${lineWidth * 2}px`;
5133
5503
  path.classList.add(`vector-boundary-parallel-${index + 1}`);
5504
+ if (!this.boudaryBorderPaths[id]) {
5505
+ this.boudaryBorderPaths[id] = [];
5506
+ }
5507
+ this.boudaryBorderPaths[id].push(path);
5134
5508
  svgGroup.appendChild(path);
5135
5509
  });
5136
5510
  }
@@ -5220,6 +5594,9 @@ class BoundaryBorderLayer extends BaseLayer {
5220
5594
  }
5221
5595
  return segments;
5222
5596
  }
5597
+ resetPaths() {
5598
+ this.boudaryBorderPaths = {};
5599
+ }
5223
5600
  }
5224
5601
 
5225
5602
  var antennaOneOnline = "data:image/png;base64,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";
@@ -5432,7 +5809,7 @@ class DrawLayer extends BaseLayer {
5432
5809
  this.pointLayer,
5433
5810
  this.svgElementLayer,
5434
5811
  this.visionOffLayer,
5435
- ];
5812
+ ]?.filter((layer) => layer.getElements().length > 0);
5436
5813
  }
5437
5814
  getPathLayers() {
5438
5815
  return this.pathLayer;
@@ -5447,6 +5824,14 @@ class BoundaryDataBuilder {
5447
5824
  * 创建边界元素数据
5448
5825
  */
5449
5826
  static create(type, coordinates, style) {
5827
+ const len = coordinates?.length || 0;
5828
+ const firstPoint = coordinates?.[0];
5829
+ const lastPoint = coordinates?.[len - 1];
5830
+ const isClosed = firstPoint?.[0] === lastPoint?.[0] && firstPoint?.[1] === lastPoint?.[1];
5831
+ // 如果地图没有闭合,则手动新增闭合点,避免border最后一部分没有闭合的情况
5832
+ if (!isClosed) {
5833
+ coordinates.push([firstPoint?.[0], firstPoint?.[1], lastPoint?.[2]]);
5834
+ }
5450
5835
  return {
5451
5836
  type,
5452
5837
  coordinates,
@@ -5761,7 +6146,6 @@ class MapDataProcessor {
5761
6146
  if (mapData.vision_off_areas && mapData.vision_off_areas.length > 0) {
5762
6147
  allElements.push(...mapData.vision_off_areas);
5763
6148
  }
5764
- console.log('allElements', allElements);
5765
6149
  // 按照元素类型分组并设置不同的层级
5766
6150
  return this.createLayeredMapData(allElements);
5767
6151
  }
@@ -5850,8 +6234,13 @@ class MapDataProcessor {
5850
6234
  // 为ObstacleData创建兼容的MapElement接口
5851
6235
  const mapElement = element;
5852
6236
  const obstacleElement = ObstacleDataBuilder.fromMapElement(mapElement, this.mapConfig.obstacle);
5853
- if (obstacleElement)
6237
+ if (obstacleElement) {
5854
6238
  result.push(obstacleElement);
6239
+ const { addObstacles } = useSubBoundaryBorderStore.getState();
6240
+ addObstacles(`obstacle-${obstacleElement.originalData.id}`, {
6241
+ ...obstacleElement,
6242
+ });
6243
+ }
5855
6244
  }
5856
6245
  catch (error) {
5857
6246
  console.warn(`Error processing OBSTACLE element:`, element, error);
@@ -5912,9 +6301,13 @@ class MapDataProcessor {
5912
6301
  element.direction !== undefined) {
5913
6302
  const mapElement = element;
5914
6303
  const svgElement = SvgElementDataBuilder.fromMapElement(mapElement, this.mapConfig.doodle);
5915
- console.log('svgElement==', svgElement);
5916
- if (svgElement)
6304
+ if (svgElement) {
5917
6305
  result.push(svgElement);
6306
+ const { addSvgElements } = useSubBoundaryBorderStore.getState();
6307
+ addSvgElements(`time-limit-obstacle-${svgElement.originalData.id}`, {
6308
+ ...svgElement,
6309
+ });
6310
+ }
5918
6311
  }
5919
6312
  // 如果有points数据,按传统方式绘制
5920
6313
  else if ('points' in element &&
@@ -5923,8 +6316,13 @@ class MapDataProcessor {
5923
6316
  element.points.length >= 3) {
5924
6317
  const mapElement = element;
5925
6318
  const polygonElement = ObstacleDataBuilder.createTimeLimitObstacle(mapElement, this.mapConfig.obstacle);
5926
- if (polygonElement)
6319
+ if (polygonElement) {
5927
6320
  result.push(polygonElement);
6321
+ const { addObstacles } = useSubBoundaryBorderStore.getState();
6322
+ addObstacles(`time-limit-obstacle-${polygonElement.originalData.id}`, {
6323
+ ...polygonElement,
6324
+ });
6325
+ }
5928
6326
  }
5929
6327
  }
5930
6328
  catch (error) {
@@ -5969,35 +6367,15 @@ class PathDataBuilder {
5969
6367
  style,
5970
6368
  };
5971
6369
  }
5972
- // /**
5973
- // * 创建隧道元素数据
5974
- // */
5975
- // static createTunnel(element: MapElement): DrawElement | null {
5976
- // const convertedPoints = convertPointsFormat(element.tunnel?.points || element.points);
5977
- // if (!convertedPoints || convertedPoints.length < 2) return null;
5978
- // return this.create(convertedPoints, DEFAULT_STYLES.PATH_EDGE);
5979
- // }
5980
- // /**
5981
- // * 创建充电桩内部隧道路径(用于CHARGING_PILE元素中的tunnel.points)
5982
- // */
5983
- // static createChargingPileTunnelPath(element: MapElement): DrawElement | null {
5984
- // if (!element.tunnel || !element.tunnel.points) return null;
5985
- // const convertedTunnelPoints = convertPointsFormat(element.tunnel.points);
5986
- // if (!convertedTunnelPoints || convertedTunnelPoints.length < 2) return null;
5987
- // return this.create(convertedTunnelPoints, {
5988
- // strokeColor: DEFAULT_STYLES.CHANNEL.strokeColor,
5989
- // lineWidth: 2,
5990
- // opacity: 1.0
5991
- // });
5992
- // }
5993
6370
  /**
5994
6371
  * 创建边缘路径
5995
6372
  */
5996
6373
  static createEdgePath(points, config) {
5997
- const drawElement = this.create(points, {
5998
- strokeColor: config?.color || DEFAULT_STYLES.PATH_EDGE.strokeColor,
5999
- lineWidth: config?.lineWidth || 2,
6000
- opacity: config?.opacity || 1.0,
6374
+ const convertedPoints = convertPointsFormat(points);
6375
+ const drawElement = this.create(convertedPoints, {
6376
+ lineColor: config?.color,
6377
+ lineWidth: config?.lineWidth,
6378
+ opacity: config?.opacity,
6001
6379
  });
6002
6380
  drawElement.originalData = points;
6003
6381
  return drawElement;
@@ -6006,10 +6384,11 @@ class PathDataBuilder {
6006
6384
  * 创建割草路径
6007
6385
  */
6008
6386
  static createMowingPath(points, config) {
6009
- const drawElement = this.create(points, {
6010
- strokeColor: config?.color || DEFAULT_STYLES.PATH_EDGE.strokeColor,
6011
- lineWidth: config?.lineWidth || DEFAULT_STYLES.PATH_EDGE.lineWidth,
6012
- opacity: config?.opacity || 1.0,
6387
+ const convertedPoints = convertPointsFormat(points);
6388
+ const drawElement = this.create(convertedPoints, {
6389
+ lineColor: config?.color,
6390
+ lineWidth: config?.lineWidth,
6391
+ opacity: config?.opacity,
6013
6392
  });
6014
6393
  drawElement.originalData = points;
6015
6394
  return drawElement;
@@ -6018,11 +6397,11 @@ class PathDataBuilder {
6018
6397
  * 创建传输路径
6019
6398
  */
6020
6399
  static createTransPath(points, config) {
6021
- const drawElement = this.create(points, {
6022
- strokeColor: config?.color || DEFAULT_STYLES.PATH_EDGE.strokeColor,
6023
- lineWidth: config?.lineWidth || DEFAULT_STYLES.PATH_EDGE.lineWidth,
6024
- lineDash: [5, 5], // 虚线样式
6025
- opacity: config?.opacity || 1.0,
6400
+ const convertedPoints = convertPointsFormat(points);
6401
+ const drawElement = this.create(convertedPoints, {
6402
+ lineColor: config?.color,
6403
+ lineWidth: config?.lineWidth,
6404
+ opacity: config?.opacity,
6026
6405
  });
6027
6406
  drawElement.originalData = points;
6028
6407
  return drawElement;
@@ -6041,55 +6420,59 @@ class PathDataProcessor {
6041
6420
  if (!pathData || typeof pathData !== 'object') {
6042
6421
  return [];
6043
6422
  }
6044
- // 获取所有分区的路径数据
6045
- const allPathItems = Object.values(pathData).reduce((acc, partitionData) => {
6046
- if (partitionData && partitionData.points && partitionData.points.length > 0) {
6047
- acc.push(...partitionData.points);
6048
- }
6049
- return acc;
6050
- }, []);
6051
6423
  // 合并配置
6052
6424
  const config = mapConfig.path;
6053
- // 使用Python相同的逻辑:按线段分组而不是按点分组
6054
- const pathSegments = createPathSegmentsByType(allPathItems);
6055
- const elements = [];
6056
- // 处理边缘路径段
6057
- for (const segment of pathSegments.edge) {
6058
- // 转换 Point[] 为 number[][]
6059
- const points = segment.points.map((point) => [point.x, point.y]);
6060
- const element = PathDataBuilder.createEdgePath(points, {
6061
- lineWidth: config.lineWidth,
6062
- color: config.edgeLineColor,
6063
- opacity: config.opacity,
6064
- });
6065
- element.originalData = points;
6066
- elements.push(element);
6067
- }
6068
- // 处理割草路径段
6069
- for (const segment of pathSegments.mowing) {
6070
- // 转换 Point[] 为 number[][]
6071
- const points = segment.points.map((point) => [point.x, point.y]);
6072
- const element = PathDataBuilder.createMowingPath(points, {
6073
- lineWidth: config.lineWidth,
6074
- color: config.mowingLineColor,
6075
- opacity: config.opacity,
6076
- });
6077
- element.originalData = points;
6078
- elements.push(element);
6079
- }
6080
- // 处理传输路径段(只有在showTransPaths为true时才添加)
6081
- for (const segment of pathSegments.trans) {
6082
- // 转换 Point[] 为 number[][]
6083
- const points = segment.points.map((point) => [point.x, point.y]);
6084
- const element = PathDataBuilder.createTransPath(points, {
6085
- lineWidth: config.lineWidth,
6086
- color: config.transLineColor,
6087
- opacity: config.opacity,
6088
- });
6089
- element.originalData = points;
6090
- elements.push(element);
6091
- }
6092
- return elements;
6425
+ const result = Object.keys(pathData).map((key) => {
6426
+ const id = Number(key);
6427
+ const points = pathData[key].points || [];
6428
+ // 使用Python相同的逻辑:按线段分组而不是按点分组
6429
+ const pathSegments = createPathSegmentsByType(points);
6430
+ const elements = [];
6431
+ // 处理边缘路径段
6432
+ for (const segment of pathSegments.edge) {
6433
+ // 转换 Point[] 为 number[][]
6434
+ const points = segment.points.map((point) => [point.x, point.y]);
6435
+ const element = PathDataBuilder.createEdgePath(points, {
6436
+ lineWidth: config.lineWidth,
6437
+ color: config.edgeLineColor,
6438
+ opacity: config.opacity,
6439
+ });
6440
+ element.originalData = points;
6441
+ element.pathType = PathSegmentType.EDGE;
6442
+ elements.push(element);
6443
+ }
6444
+ // 处理割草路径段
6445
+ for (const segment of pathSegments.mowing) {
6446
+ // 转换 Point[] 为 number[][]
6447
+ const points = segment.points.map((point) => [point.x, point.y]);
6448
+ const element = PathDataBuilder.createMowingPath(points, {
6449
+ lineWidth: config.lineWidth,
6450
+ color: config.mowedLineColor,
6451
+ opacity: config.opacity,
6452
+ });
6453
+ element.originalData = points;
6454
+ element.pathType = PathSegmentType.MOWING;
6455
+ elements.push(element);
6456
+ }
6457
+ // 处理传输路径段(只有在showTransPaths为true时才添加)
6458
+ for (const segment of pathSegments.trans) {
6459
+ // 转换 Point[] 为 number[][]
6460
+ const points = segment.points.map((point) => [point.x, point.y]);
6461
+ const element = PathDataBuilder.createTransPath(points, {
6462
+ lineWidth: config.lineWidth,
6463
+ color: config.transLineColor,
6464
+ opacity: config.opacity,
6465
+ });
6466
+ element.originalData = points;
6467
+ element.pathType = PathSegmentType.TRANS;
6468
+ elements.push(element);
6469
+ }
6470
+ return {
6471
+ id,
6472
+ elements,
6473
+ };
6474
+ });
6475
+ return result;
6093
6476
  }
6094
6477
  }
6095
6478
 
@@ -6098,15 +6481,19 @@ class PathDataProcessor {
6098
6481
  * 专门处理边界标签的创建、定位和管理
6099
6482
  */
6100
6483
  class BoundaryLabelsManager {
6101
- constructor(svgView, boundaryData) {
6484
+ constructor(svgView, boundaryData, { unitType, language }) {
6102
6485
  this.container = null;
6103
6486
  this.overlayDiv = null;
6104
6487
  this.globalClickHandler = null;
6105
6488
  // 当前展开的边界id
6106
6489
  this.currentExpandedBoundaryId = null;
6490
+ // 旋转角度
6491
+ this.rotation = 0;
6107
6492
  this.svgView = svgView;
6108
6493
  this.boundaryData = boundaryData;
6109
6494
  this.initializeContainer();
6495
+ this.unitType = unitType;
6496
+ this.language = language;
6110
6497
  }
6111
6498
  /**
6112
6499
  * 初始化容器
@@ -6172,7 +6559,7 @@ class BoundaryLabelsManager {
6172
6559
  labelDiv.setAttribute('data-boundary-id', boundary.id.toString());
6173
6560
  // 样式设置
6174
6561
  labelDiv.style.position = 'absolute';
6175
- labelDiv.style.backgroundColor = 'rgba(30, 30, 31, 0.3)';
6562
+ labelDiv.style.backgroundColor = 'rgba(30, 30, 31, 0.6)';
6176
6563
  labelDiv.style.color = 'rgba(255, 255, 255, 1)';
6177
6564
  labelDiv.style.padding = '6px';
6178
6565
  labelDiv.style.borderRadius = '12px';
@@ -6180,17 +6567,17 @@ class BoundaryLabelsManager {
6180
6567
  labelDiv.style.fontWeight = 'bold';
6181
6568
  labelDiv.style.whiteSpace = 'nowrap';
6182
6569
  labelDiv.style.maxWidth = '220px';
6183
- labelDiv.style.transform = 'translate(-50%, -50%)';
6570
+ labelDiv.style.transform = `translate(-50%, -50%) rotate(${-this.rotation}deg)`;
6184
6571
  labelDiv.style.pointerEvents = 'auto';
6185
6572
  labelDiv.style.boxShadow = '0 2px 8px rgba(0,0,0,0.4)';
6186
6573
  labelDiv.style.cursor = 'pointer';
6187
- labelDiv.style.transition = 'background-color 0.2s ease, transform 0.2s ease';
6574
+ labelDiv.style.transition = 'background-color 0.2s ease';
6188
6575
  labelDiv.style.userSelect = 'none';
6189
6576
  labelDiv.style.zIndex = BoundaryLabelsManager.Z_INDEX.DEFAULT.toString();
6190
6577
  // 计算进度
6191
- const progress = boundary.finishedArea && boundary.area ?
6192
- `${Math.round((boundary.finishedArea / boundary.area) * 100)}%` :
6193
- '0%';
6578
+ const progress = boundary.finishedArea && boundary.area
6579
+ ? `${Math.floor((boundary.finishedArea / boundary.area) * 100)}%`
6580
+ : '0%';
6194
6581
  // 基础内容(始终显示)
6195
6582
  const baseContent = document.createElement('div');
6196
6583
  baseContent.className = 'boundary-label-base';
@@ -6202,15 +6589,19 @@ class BoundaryLabelsManager {
6202
6589
  extendedContent.style.marginTop = '6px';
6203
6590
  extendedContent.style.fontSize = '11px';
6204
6591
  extendedContent.style.opacity = '0.9';
6205
- extendedContent.style.display = this.currentExpandedBoundaryId === boundary.id ? 'block' : 'none';
6592
+ extendedContent.style.display =
6593
+ this.currentExpandedBoundaryId === boundary.id ? 'block' : 'none';
6206
6594
  extendedContent.style.borderTop = '1px solid rgba(255,255,255,0.2)';
6207
6595
  extendedContent.style.paddingTop = '6px';
6596
+ const boundaryLayer = this.svgView.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
6597
+ const mowingBoundarys = boundaryLayer.getMowingBoundarys();
6208
6598
  // 面积信息
6209
- const totalArea = convertAreaByUnits(boundary.area || 0, 'metric');
6210
- 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);
6211
6601
  const coverageText = `Coverage: ${finishedArea.value}/${totalArea.value}`;
6602
+ const isMowing = mowingBoundarys.includes(boundary.id);
6212
6603
  // 日期信息
6213
- const dateText = formatBoundaryDateText(boundary.endTime || 0);
6604
+ const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0);
6214
6605
  const covertHtml = `<div style="margin-bottom: 3px; font-weight: bold;">${coverageText}</div>`;
6215
6606
  const dateHtml = `<div>${dateText}</div>`;
6216
6607
  extendedContent.innerHTML = boundary.finishedArea > 0 ? `${covertHtml}${dateHtml}` : covertHtml;
@@ -6273,7 +6664,6 @@ class BoundaryLabelsManager {
6273
6664
  this.collapseOtherLabels(boundaryId);
6274
6665
  // 展开当前标签
6275
6666
  extendedContent.style.display = 'block';
6276
- labelDiv.style.whiteSpace = 'normal';
6277
6667
  this.currentExpandedBoundaryId = boundaryId;
6278
6668
  }
6279
6669
  /**
@@ -6283,7 +6673,7 @@ class BoundaryLabelsManager {
6283
6673
  if (!this.container)
6284
6674
  return;
6285
6675
  const allLabels = this.container.querySelectorAll('.boundary-label');
6286
- allLabels.forEach(label => {
6676
+ allLabels.forEach((label) => {
6287
6677
  const labelBoundaryId = label.getAttribute('data-boundary-id');
6288
6678
  if (labelBoundaryId && labelBoundaryId !== boundaryId.toString()) {
6289
6679
  const extendedContent = label.querySelector('.boundary-label-extended');
@@ -6329,7 +6719,6 @@ class BoundaryLabelsManager {
6329
6719
  // 计算边界中心点的地图坐标
6330
6720
  const mapCenter = this.calculatePolygonCentroid(boundary.points);
6331
6721
  if (!mapCenter) {
6332
- console.warn(`BoundaryLabelsManager: 无法计算边界 ${boundary.name} (ID: ${boundary.id}) 的中心点`);
6333
6722
  return;
6334
6723
  }
6335
6724
  // 直接使用预计算的数据进行坐标转换
@@ -6386,7 +6775,7 @@ class BoundaryLabelsManager {
6386
6775
  if (points.length < 3)
6387
6776
  return null;
6388
6777
  // 过滤有效点
6389
- const validPoints = points.filter(point => point.length >= 2);
6778
+ const validPoints = points.filter((point) => point.length >= 2);
6390
6779
  if (validPoints.length < 3)
6391
6780
  return null;
6392
6781
  // 确保多边形是封闭的(如果不是,自动闭合)
@@ -6414,7 +6803,6 @@ class BoundaryLabelsManager {
6414
6803
  area = area / 2;
6415
6804
  // 如果面积为0,回退到简单的平均值计算
6416
6805
  if (Math.abs(area) < 1e-10) {
6417
- console.warn('BoundaryLabelsManager: 多边形面积为0,使用平均值计算重心');
6418
6806
  return this.calculateAverageCenter(validPoints);
6419
6807
  }
6420
6808
  centroidX = centroidX / (6 * area);
@@ -6435,7 +6823,7 @@ class BoundaryLabelsManager {
6435
6823
  });
6436
6824
  return {
6437
6825
  x: sumX / points.length,
6438
- y: sumY / points.length
6826
+ y: sumY / points.length,
6439
6827
  };
6440
6828
  }
6441
6829
  /**
@@ -6509,12 +6897,6 @@ class BoundaryLabelsManager {
6509
6897
  this.container.style.display = visible ? 'block' : 'none';
6510
6898
  }
6511
6899
  }
6512
- /**
6513
- * 获取边界数据
6514
- */
6515
- getBoundaryData() {
6516
- return this.boundaryData;
6517
- }
6518
6900
  /**
6519
6901
  * 根据ID获取特定边界的标签元素
6520
6902
  */
@@ -6531,7 +6913,7 @@ class BoundaryLabelsManager {
6531
6913
  return;
6532
6914
  const allLabels = this.container.querySelectorAll('.boundary-label');
6533
6915
  this.currentExpandedBoundaryId = null;
6534
- allLabels.forEach(label => {
6916
+ allLabels.forEach((label) => {
6535
6917
  const extendedContent = label.querySelector('.boundary-label-extended');
6536
6918
  if (extendedContent) {
6537
6919
  extendedContent.style.display = 'none';
@@ -6546,7 +6928,7 @@ class BoundaryLabelsManager {
6546
6928
  if (!this.container)
6547
6929
  return;
6548
6930
  const allLabels = this.container.querySelectorAll('.boundary-label');
6549
- allLabels.forEach(label => {
6931
+ allLabels.forEach((label) => {
6550
6932
  const labelElement = label;
6551
6933
  if (interactive) {
6552
6934
  labelElement.style.pointerEvents = 'auto';
@@ -6565,34 +6947,26 @@ class BoundaryLabelsManager {
6565
6947
  });
6566
6948
  }
6567
6949
  /**
6568
- * 重置标签层级(公共方法)
6569
- */
6570
- resetZIndex() {
6571
- this.resetLabelZIndex();
6572
- }
6573
- /**
6574
- * 获取层级常量(静态方法)
6575
- */
6576
- static getZIndexConstants() {
6577
- return BoundaryLabelsManager.Z_INDEX;
6578
- }
6579
- /**
6580
- * 调试方法:检查当前所有边界标签的层级
6950
+ * 设置标签旋转角度,使其与地图旋转相反,保持水平状态
6951
+ * @param rotation 地图的旋转角度(度)
6581
6952
  */
6582
- debugCheckZIndex() {
6953
+ setRotation(rotation) {
6583
6954
  if (!this.container)
6584
6955
  return;
6585
- const allLabels = this.container.querySelectorAll('.boundary-label');
6586
- allLabels.forEach((label, index) => {
6956
+ this.rotation = rotation;
6957
+ const labels = this.container.querySelectorAll('.boundary-label');
6958
+ labels.forEach((label) => {
6587
6959
  const labelElement = label;
6588
- labelElement.style.zIndex;
6960
+ // 应用与地图旋转相反的旋转,保持标签水平
6961
+ const counterRotation = -rotation;
6962
+ labelElement.style.transform = `translate(-50%, -50%) rotate(${counterRotation}deg)`;
6589
6963
  });
6590
6964
  }
6591
6965
  }
6592
6966
  // 简化的层级定义
6593
6967
  BoundaryLabelsManager.Z_INDEX = {
6594
6968
  DEFAULT: 900, // 默认层级
6595
- ACTIVE: 9999 // 点击激活时的高层级
6969
+ ACTIVE: 9999, // 点击激活时的高层级
6596
6970
  };
6597
6971
 
6598
6972
  /**
@@ -6605,6 +6979,10 @@ class ChargingPileManager {
6605
6979
  this.container = null;
6606
6980
  this.overlayDiv = null;
6607
6981
  this.pileElements = new Map();
6982
+ // 原始旋转角度
6983
+ this.originalRotation = 0;
6984
+ // 旋转角度
6985
+ this.rotation = 0;
6608
6986
  this.svgView = svgView;
6609
6987
  this.initializeContainer();
6610
6988
  }
@@ -6659,7 +7037,9 @@ class ChargingPileManager {
6659
7037
  // 将弧度转换为角度
6660
7038
  const angle = (direction * 180) / Math.PI;
6661
7039
  const rotationDegree = 270 - angle; // 坐标系转换
6662
- pileDiv.style.transform = `translate(-50%, -50%) rotate(${rotationDegree}deg)`;
7040
+ this.originalRotation = rotationDegree;
7041
+ const actualRotation = rotationDegree - this.rotation;
7042
+ pileDiv.style.transform = `translate(-50%, -50%) rotate(${actualRotation}deg)`;
6663
7043
  // 添加动画
6664
7044
  // this.addChargingPileAnimation(pileDiv, imgElement, rotationDegree);
6665
7045
  // 生成唯一ID
@@ -6708,30 +7088,15 @@ class ChargingPileManager {
6708
7088
  updatePositions() {
6709
7089
  if (!this.overlayDiv || !this.container)
6710
7090
  return;
6711
- const divRect = this.overlayDiv.getBoundingClientRect();
6712
- const divWidth = divRect.width;
6713
- const divHeight = divRect.height;
6714
- // 获取SVG的viewBox
6715
- const svg = this.svgView.getSVG();
6716
- if (!svg)
6717
- return;
6718
- const viewBox = svg.viewBox.baseVal;
6719
- const viewBoxData = {
6720
- x: viewBox.x,
6721
- y: viewBox.y,
6722
- width: viewBox.width,
6723
- height: viewBox.height,
6724
- };
6725
- this.updatePositionsWithPrecomputedData(divWidth, divHeight, viewBoxData);
7091
+ this.updatePositionsWithPrecomputedData();
6726
7092
  }
6727
7093
  /**
6728
7094
  * 使用预计算数据更新位置
6729
7095
  */
6730
- updatePositionsWithPrecomputedData(divWidth, divHeight, viewBox) {
7096
+ updatePositionsWithPrecomputedData() {
6731
7097
  this.chargingPileElements.forEach((element, _index) => {
6732
7098
  const center = element.coordinates[0];
6733
- const pixelPosition = this.convertMapCoordinateToPixelWithPrecomputedData(center[0], center[1], divWidth, divHeight, viewBox);
6734
- console.log('updatePositionsWithPrecomputedData----->', element, pixelPosition, divWidth, divHeight, viewBox);
7099
+ const pixelPosition = this.convertMapCoordinateToPixelWithPrecomputedData(center[0], center[1]);
6735
7100
  if (pixelPosition) {
6736
7101
  const pileId = `pile_${center[0]}_${center[1]}`;
6737
7102
  const pileElement = this.pileElements.get(pileId);
@@ -6745,7 +7110,15 @@ class ChargingPileManager {
6745
7110
  /**
6746
7111
  * 使用预计算数据进行坐标转换
6747
7112
  */
6748
- convertMapCoordinateToPixelWithPrecomputedData(mapX, mapY, divWidth, divHeight, viewBox) {
7113
+ convertMapCoordinateToPixelWithPrecomputedData(mapX, mapY) {
7114
+ // 获取叠加层div的CSS尺寸
7115
+ const divWidth = parseFloat(this.overlayDiv.style.width) || this.overlayDiv.offsetWidth;
7116
+ const divHeight = parseFloat(this.overlayDiv.style.height) || this.overlayDiv.offsetHeight;
7117
+ // 获取SVG的viewBox
7118
+ const svg = this.svgView.getSVG();
7119
+ if (!svg)
7120
+ return { x: 0, y: 0 };
7121
+ const viewBox = svg.viewBox.baseVal;
6749
7122
  // 计算地图坐标在viewBox中的相对位置
6750
7123
  const relativeX = (mapX - viewBox.x) / viewBox.width;
6751
7124
  const relativeY = (mapY - viewBox.y) / viewBox.height;
@@ -6764,26 +7137,6 @@ class ChargingPileManager {
6764
7137
  this.container.innerHTML = '';
6765
7138
  }
6766
7139
  }
6767
- /**
6768
- * 设置可见性
6769
- */
6770
- setVisible(visible) {
6771
- if (this.container) {
6772
- this.container.style.display = visible ? 'block' : 'none';
6773
- }
6774
- }
6775
- /**
6776
- * 获取充电桩数量
6777
- */
6778
- getElementCount() {
6779
- return this.chargingPileElements.length;
6780
- }
6781
- /**
6782
- * 充电桩不需要动态层级调整(为了接口统一而保留)
6783
- */
6784
- resetZIndex() {
6785
- // 充电桩层级始终保持固定,无需重置
6786
- }
6787
7140
  /**
6788
7141
  * 销毁管理器
6789
7142
  */
@@ -6795,10 +7148,21 @@ class ChargingPileManager {
6795
7148
  this.container = null;
6796
7149
  this.overlayDiv = null;
6797
7150
  }
7151
+ /**
7152
+ * 设置充电桩旋转角度
7153
+ */
7154
+ setRotation(rotation) {
7155
+ this.rotation = rotation;
7156
+ const allContainers = this.container.querySelectorAll('.charging-pile');
7157
+ allContainers.forEach((container) => {
7158
+ const pileElement = container;
7159
+ pileElement.style.transform = `translate(-50%, -50%) rotate(${this.originalRotation - this.rotation}deg)`;
7160
+ });
7161
+ }
6798
7162
  }
6799
7163
  // 简化的层级定义 - 充电桩只需要一个固定层级
6800
7164
  ChargingPileManager.Z_INDEX = {
6801
- CHARGING_PILE: 800, // 充电桩图标固定层级
7165
+ CHARGING_PILE: 750, // 充电桩图标固定层级
6802
7166
  };
6803
7167
 
6804
7168
  /**
@@ -6816,6 +7180,8 @@ class AntennaManager {
6816
7180
  this.globalClickHandler = null;
6817
7181
  this.antennaTooltipFlag = false;
6818
7182
  this.singleAntennaTooltipFlag = false;
7183
+ // 旋转角度
7184
+ this.rotation = 0;
6819
7185
  this.svgView = svgView;
6820
7186
  this.initializeContainer();
6821
7187
  this.setupGlobalClickHandler();
@@ -6888,7 +7254,7 @@ class AntennaManager {
6888
7254
  const antennaContainer = document.createElement('div');
6889
7255
  antennaContainer.className = 'antenna-container-item';
6890
7256
  antennaContainer.style.position = 'absolute';
6891
- antennaContainer.style.transform = 'translate(-50%, -50%)';
7257
+ antennaContainer.style.transform = `translate(-50%, -50%) rotate(${-this.rotation}deg)`;
6892
7258
  antennaContainer.style.pointerEvents = 'auto';
6893
7259
  antennaContainer.style.zIndex = AntennaManager.Z_INDEX.DEFAULT.toString();
6894
7260
  antennaContainer.setAttribute('data-antenna-id', antennaData.type.toString());
@@ -6929,8 +7295,6 @@ class AntennaManager {
6929
7295
  this.singleAntennaTooltipFlag = true;
6930
7296
  }
6931
7297
  this.expandTooltip(antennaContainer);
6932
- // 调试:检查层级变化
6933
- this.debugCheckZIndex();
6934
7298
  });
6935
7299
  // 添加悬停效果
6936
7300
  antennaDiv.addEventListener('mouseenter', () => {
@@ -7180,39 +7544,6 @@ class AntennaManager {
7180
7544
  this.container.innerHTML = '';
7181
7545
  }
7182
7546
  }
7183
- /**
7184
- * 设置可见性
7185
- */
7186
- setVisible(visible) {
7187
- if (this.container) {
7188
- this.container.style.display = visible ? 'block' : 'none';
7189
- }
7190
- }
7191
- /**
7192
- * 获取天线数量
7193
- */
7194
- getElementCount() {
7195
- return this.antennaElements.length;
7196
- }
7197
- /**
7198
- * 重置天线层级(公共方法)
7199
- */
7200
- resetZIndex() {
7201
- this.resetAntennaZIndex();
7202
- }
7203
- /**
7204
- * 获取层级常量(静态方法)
7205
- */
7206
- static getZIndexConstants() {
7207
- return AntennaManager.Z_INDEX;
7208
- }
7209
- /**
7210
- * 调试方法:检查当前所有天线的层级
7211
- */
7212
- debugCheckZIndex() {
7213
- if (!this.container)
7214
- return;
7215
- }
7216
7547
  /**
7217
7548
  * 销毁管理器
7218
7549
  */
@@ -7229,10 +7560,21 @@ class AntennaManager {
7229
7560
  this.container = null;
7230
7561
  this.overlayDiv = null;
7231
7562
  }
7563
+ /**
7564
+ * 设置天线旋转角度
7565
+ */
7566
+ setRotation(rotation) {
7567
+ this.rotation = rotation;
7568
+ const allContainers = this.container.querySelectorAll('.antenna-container-item');
7569
+ allContainers.forEach((container) => {
7570
+ const antennaContainer = container;
7571
+ antennaContainer.style.transform = `translate(-50%, -50%) rotate(${-this.rotation}deg)`;
7572
+ });
7573
+ }
7232
7574
  }
7233
7575
  // 简化的层级定义
7234
7576
  AntennaManager.Z_INDEX = {
7235
- DEFAULT: 750, // 默认层级
7577
+ DEFAULT: 800, // 默认层级
7236
7578
  ACTIVE: 9999, // 点击激活时的高层级
7237
7579
  };
7238
7580
 
@@ -7283,8 +7625,8 @@ const EDIT_BEHAVIOR = {
7283
7625
  SUCCESS_MESSAGE_DURATION_MS: 3000,
7284
7626
  };
7285
7627
 
7286
- class MowerPostionManager {
7287
- constructor(svgView, mowerPositonConfig, overlayDiv, onAnimationComplete) {
7628
+ class MowerPositionManager {
7629
+ constructor(svgView, mowerPositionConfig, modelType, overlayDiv, onAnimationComplete, onMowingPositionChange) {
7288
7630
  this.container = null;
7289
7631
  this.overlayDiv = null;
7290
7632
  this.mowerElement = null;
@@ -7301,9 +7643,11 @@ class MowerPostionManager {
7301
7643
  this.deltaPosition = null;
7302
7644
  this.onlyUpdateTheta = false;
7303
7645
  this.svgView = svgView;
7304
- this.mowerPositonConfig = mowerPositonConfig;
7646
+ this.mowerPositionConfig = mowerPositionConfig;
7647
+ this.modelType = modelType;
7305
7648
  this.overlayDiv = overlayDiv;
7306
7649
  this.onAnimationComplete = onAnimationComplete;
7650
+ this.onMowingPositionChange = onMowingPositionChange;
7307
7651
  this.initializeContainer();
7308
7652
  }
7309
7653
  get animationFlag() {
@@ -7349,12 +7693,13 @@ class MowerPostionManager {
7349
7693
  imgElement.style.height = '100%';
7350
7694
  imgElement.style.objectFit = 'contain';
7351
7695
  // 获取图片源
7352
- const imageSrc = getMowerImage(this.mowerPositonConfig);
7696
+ const imageSrc = getMowerImage(this.mowerPositionConfig);
7353
7697
  if (imageSrc) {
7354
7698
  imgElement.src = imageSrc;
7355
7699
  }
7356
7700
  this.mowerElement.appendChild(imgElement);
7357
7701
  this.container.appendChild(this.mowerElement);
7702
+ this.updatePosition(this.mowerPositionConfig);
7358
7703
  }
7359
7704
  /**
7360
7705
  * 设置叠加层div引用(用于坐标转换)
@@ -7368,54 +7713,69 @@ class MowerPostionManager {
7368
7713
  getElement() {
7369
7714
  return this.container;
7370
7715
  }
7716
+ //
7717
+ setEdger(edger) {
7718
+ this.hasEdger = edger;
7719
+ }
7371
7720
  /**
7372
7721
  * 根据最后一次有效的位置更新数据
7373
7722
  */
7374
- updatePostionByLastPosition(chargingPilesPositonConfig, animationTime = 0) {
7375
- this.mowerPositonConfig = chargingPilesPositonConfig;
7376
- console.log('updatePostionByLastPosition----->', chargingPilesPositonConfig, this.lastPosition);
7723
+ updatePositionByLastPosition(chargingPilesPositionConfig) {
7724
+ if (!chargingPilesPositionConfig)
7725
+ return;
7726
+ this.mowerPositionConfig = chargingPilesPositionConfig;
7727
+ const positonOutOfRange = isOutOfRange(chargingPilesPositionConfig);
7728
+ const positionInValid = isInvalidPosition(chargingPilesPositionConfig);
7729
+ let postureX = 0;
7730
+ let postureY = 0;
7731
+ let postureTheta = 0;
7377
7732
  const lastPosition = this.lastPosition;
7378
- if (!lastPosition) {
7379
- this.updatePosition(chargingPilesPositonConfig, animationTime);
7733
+ if (positonOutOfRange || positionInValid) {
7734
+ postureX = lastPosition?.x || 0;
7735
+ postureY = lastPosition?.y || 0;
7736
+ postureTheta = lastPosition?.rotation || 0;
7380
7737
  }
7381
7738
  else {
7382
- this.updatePosition({
7383
- ...chargingPilesPositonConfig,
7384
- postureX: lastPosition.x,
7385
- postureY: lastPosition.y,
7386
- postureTheta: lastPosition.rotation,
7387
- }, 0);
7739
+ postureX = chargingPilesPositionConfig.postureX || 0;
7740
+ postureY = chargingPilesPositionConfig.postureY || 0;
7741
+ postureTheta = chargingPilesPositionConfig.postureTheta || 0;
7388
7742
  }
7743
+ // 检查是否需要更新图片
7744
+ this.updateMowerImage(chargingPilesPositionConfig);
7745
+ // 立即更新位置
7746
+ this.setElementPosition(postureX, postureY, postureTheta);
7389
7747
  }
7390
7748
  /**
7391
7749
  * 更新割草机位置
7392
7750
  */
7393
- updatePosition(positonConfig, animationTime = 0) {
7394
- console.log('updatePosition----->', positonConfig);
7751
+ updatePosition(positionConfig, animationTime = 0) {
7395
7752
  // 检查是否需要更新图片
7396
- this.updateMowerImage(positonConfig);
7753
+ this.updateMowerImage(positionConfig);
7754
+ // 更新配置
7755
+ this.mowerPositionConfig = positionConfig;
7756
+ const postureX = positionConfig?.postureX || this.lastPosition?.x || 0;
7757
+ const postureY = positionConfig?.postureY || this.lastPosition?.y || 0;
7758
+ const postureTheta = positionConfig?.postureTheta || this.lastPosition?.rotation || 0;
7397
7759
  // 停止当前动画(如果有)
7398
7760
  this.stopAnimation();
7399
7761
  // 第一个点
7400
7762
  if (!this.currentPosition) {
7401
7763
  this.currentPosition = {
7402
- x: positonConfig.postureX,
7403
- y: positonConfig.postureY,
7404
- rotation: positonConfig.postureTheta,
7764
+ x: postureX,
7765
+ y: postureY,
7766
+ rotation: postureTheta,
7405
7767
  };
7406
7768
  this.setElementPosition(this.currentPosition.x, this.currentPosition.y, this.currentPosition.rotation);
7407
7769
  return;
7408
7770
  }
7409
7771
  // 根据动画时长决定更新方式
7410
7772
  if (animationTime > 0) {
7411
- this.startAnimationToPosition(positonConfig, animationTime);
7773
+ this.startAnimationToPosition(positionConfig, animationTime);
7412
7774
  }
7413
7775
  else {
7414
7776
  // 立即更新位置
7415
- this.setElementPosition(positonConfig.postureX, positonConfig.postureY, positonConfig.postureTheta);
7777
+ this.setElementPosition(positionConfig.postureX, positionConfig.postureY, positionConfig.postureTheta);
7416
7778
  }
7417
- // 更新配置
7418
- this.mowerPositonConfig = positonConfig;
7419
7779
  }
7420
7780
  /**
7421
7781
  * 更新割草机图片
@@ -7426,9 +7786,14 @@ class MowerPostionManager {
7426
7786
  const imgElement = this.mowerElement.querySelector('img');
7427
7787
  if (!imgElement)
7428
7788
  return;
7429
- const imageSrc = getMowerImage(positonConfig);
7789
+ const imageSrc = getMowerImage(positonConfig, this.modelType, this.hasEdger);
7430
7790
  if (imageSrc) {
7431
7791
  imgElement.src = imageSrc;
7792
+ imgElement.style.display = 'block';
7793
+ }
7794
+ else {
7795
+ imgElement.style.display = 'none';
7796
+ return;
7432
7797
  }
7433
7798
  }
7434
7799
  /**
@@ -7438,7 +7803,9 @@ class MowerPostionManager {
7438
7803
  const { x: pointX, y: pointY } = convertCoordinate(x, y);
7439
7804
  const targetPixelPosition = this.convertMapCoordinateToOverlayPixel(pointX, pointY);
7440
7805
  const targetRotation = radToDegree(theta);
7806
+ this.currentPosition = { x: x, y: y, rotation: theta };
7441
7807
  this.lastPosition = { x, y, rotation: theta };
7808
+ // console.log('setElementPosition', x, y, theta, targetRotation, positonOutOfRange, positionValid, targetPixelPosition);
7442
7809
  if (!this.mowerElement)
7443
7810
  return;
7444
7811
  this.mowerElement.style.left = `${targetPixelPosition?.x}px`;
@@ -7464,29 +7831,48 @@ class MowerPostionManager {
7464
7831
  startAnimationToPosition(positionConfig, duration) {
7465
7832
  if (!this.mowerElement || !this.currentPosition)
7466
7833
  return;
7467
- console.log('before startAnimationToPosition----->', this.startPosition, this.currentPosition, this.targetPosition, positionConfig);
7468
- this.startPosition = { ...this.currentPosition, rotation: radNormalize(this.currentPosition.rotation) };
7469
7834
  this.targetPosition = {
7470
7835
  x: positionConfig.postureX,
7471
7836
  y: positionConfig.postureY,
7472
7837
  rotation: positionConfig.postureTheta,
7473
7838
  };
7839
+ const isTargetPositionInvalid = isInvalidPosition({
7840
+ postureX: this.targetPosition.x,
7841
+ postureY: this.targetPosition.y,
7842
+ postureTheta: this.targetPosition.rotation,
7843
+ });
7844
+ const isTargetPositionOutOfRange = isOutOfRange({
7845
+ postureX: this.targetPosition.x,
7846
+ postureY: this.targetPosition.y,
7847
+ postureTheta: this.targetPosition.rotation,
7848
+ });
7849
+ // 如果目标坐标点不合理,则舍弃不使用
7850
+ if (isTargetPositionInvalid || isTargetPositionOutOfRange) {
7851
+ return;
7852
+ }
7853
+ this.startPosition = {
7854
+ ...this.currentPosition,
7855
+ rotation: radNormalize(this.currentPosition.rotation),
7856
+ };
7474
7857
  this.animationDuration = duration;
7475
7858
  this.startTime = window.performance.now();
7476
7859
  this.isAnimating = true;
7477
7860
  this.onlyUpdateTheta =
7478
7861
  distance(this.currentPosition.x, this.currentPosition.y, positionConfig.postureX, positionConfig.postureY) < 0.2;
7479
7862
  // 为了实现倒车时,割草机图标旋转方向正确,需要计算当前位置和目标位置的夹角
7480
- const calTheta = calAngle(this.startPosition.x, this.startPosition.y, this.targetPosition.x, this.targetPosition.y);
7863
+ // const calTheta = calAngle(
7864
+ // this.startPosition.x,
7865
+ // this.startPosition.y,
7866
+ // this.targetPosition.x,
7867
+ // this.targetPosition.y
7868
+ // );
7481
7869
  const startTheta = this.startPosition.rotation;
7482
- const targetTheta = this.onlyUpdateTheta ? this.targetPosition.rotation : calTheta;
7483
- console.log('begain startAnimationToPosition----->', this.startPosition, this.targetPosition, this.onlyUpdateTheta);
7870
+ const targetTheta = this.targetPosition.rotation;
7484
7871
  this.deltaPosition = {
7485
7872
  x: this.onlyUpdateTheta ? 0 : this.targetPosition.x - this.startPosition.x,
7486
7873
  y: this.onlyUpdateTheta ? 0 : this.targetPosition.y - this.startPosition.y,
7487
- rotation: radNormalize(targetTheta - startTheta)
7874
+ rotation: radNormalize(targetTheta - startTheta),
7488
7875
  };
7489
- console.log('deltaPosition----->', targetTheta, startTheta, calTheta, this.deltaPosition);
7490
7876
  // 开始动画循环
7491
7877
  this.animateStep();
7492
7878
  }
@@ -7497,7 +7883,7 @@ class MowerPostionManager {
7497
7883
  * 动画步骤
7498
7884
  */
7499
7885
  animateStep() {
7500
- if (!this.isAnimating || !this.currentPosition || !this.targetPosition || !this.startPosition)
7886
+ if (!this.isAnimating || !this.targetPosition || !this.startPosition)
7501
7887
  return;
7502
7888
  const currentTime = window.performance.now();
7503
7889
  const elapsed = currentTime - this.startTime;
@@ -7508,20 +7894,24 @@ class MowerPostionManager {
7508
7894
  const currentX = this.startPosition.x + this.deltaPosition.x * easedProgress;
7509
7895
  const currentY = this.startPosition.y + this.deltaPosition.y * easedProgress;
7510
7896
  const currentRotation = this.startPosition.rotation + this.deltaPosition.rotation * easedProgress;
7511
- this.currentPosition = { x: currentX, y: currentY, rotation: currentRotation };
7512
- // console.log('animateStep----->', this.onlyUpdateTheta, currentX, currentY, currentRotation);
7513
- // console.log('animateStep1----->', onlyUpdateTheta, progress, calTheta, currentRotation);
7897
+ // 假设在这里进行更新路径数据
7898
+ if (this.onMowingPositionChange) {
7899
+ this.onMowingPositionChange({
7900
+ x: currentX,
7901
+ y: currentY,
7902
+ vehicleState: this.mowerPositionConfig?.vehicleState,
7903
+ });
7904
+ }
7905
+ // console.log('animateStep-->', this.startPosition, this.deltaPosition, this.targetPosition, easedProgress)
7906
+ this.setElementPosition(currentX, currentY, currentRotation);
7514
7907
  // 继续动画或结束
7515
7908
  if (progress < 1) {
7516
7909
  // 设置当前位置
7517
- this.setElementPosition(currentX, currentY, currentRotation);
7518
7910
  this.animationId = window.requestAnimationFrame(() => this.animateStep());
7519
7911
  }
7520
7912
  else {
7521
- this.setElementPosition(currentX, currentY, currentRotation);
7522
7913
  // 动画完成
7523
7914
  this.stopAnimation();
7524
- console.log('end animateStep----->', this.lastPosition, this.currentPosition, this.targetPosition);
7525
7915
  // 通知动画完成
7526
7916
  if (this.onAnimationComplete) {
7527
7917
  this.onAnimationComplete();
@@ -7566,12 +7956,6 @@ class MowerPostionManager {
7566
7956
  this.container.style.display = visible ? 'block' : 'none';
7567
7957
  }
7568
7958
  }
7569
- /**
7570
- * 检查是否正在动画中
7571
- */
7572
- getIsAnimating() {
7573
- return this.isAnimating;
7574
- }
7575
7959
  /**
7576
7960
  * 销毁管理器
7577
7961
  */
@@ -7587,13 +7971,86 @@ class MowerPostionManager {
7587
7971
  }
7588
7972
  }
7589
7973
 
7974
+ // 记录割草状态,状态变更的时候,变量不触发重新渲染
7975
+ const useProcessMowingState = create((set) => ({
7976
+ processStateIsMowing: false,
7977
+ updateProcessStateIsMowing: (isMowing) => set({ processStateIsMowing: isMowing }),
7978
+ resetProcessStateIsMowing: () => set({ processStateIsMowing: false }),
7979
+ }));
7980
+
7981
+ /**
7982
+ * 高级节流函数
7983
+ * @param func 要节流的函数
7984
+ * @param delay 延迟时间(毫秒)
7985
+ * @param options 配置选项
7986
+ * @returns 节流后的函数
7987
+ */
7988
+ function throttleAdvanced(func, delay, options = { leading: true, trailing: true }) {
7989
+ let lastExecTime = 0;
7990
+ let timeoutId = null;
7991
+ let lastArgs = null;
7992
+ return function throttled(...args) {
7993
+ const currentTime = Date.now();
7994
+ lastArgs = args;
7995
+ // 如果距离上次执行的时间小于延迟时间
7996
+ if (currentTime - lastExecTime < delay) {
7997
+ // 清除之前的定时器
7998
+ if (timeoutId) {
7999
+ clearTimeout(timeoutId);
8000
+ }
8001
+ // 设置新的定时器
8002
+ timeoutId = setTimeout(() => {
8003
+ if (options.trailing && lastArgs) {
8004
+ lastExecTime = Date.now();
8005
+ func.apply(this, lastArgs);
8006
+ lastArgs = null;
8007
+ }
8008
+ timeoutId = null;
8009
+ }, delay - (currentTime - lastExecTime));
8010
+ }
8011
+ else {
8012
+ // 如果距离上次执行的时间已经超过延迟时间
8013
+ if (options.leading) {
8014
+ lastExecTime = currentTime;
8015
+ func.apply(this, args);
8016
+ }
8017
+ }
8018
+ };
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
+ }
8044
+
7590
8045
  // Google Maps 叠加层类 - 带编辑功能
7591
8046
  class MowerMapOverlay {
7592
- constructor(bounds, mapData, mowerPositonConfig, pathData, isEditMode = false, mapConfig = {}, antennaConfig = {}, 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) {
7593
8048
  this.div = null;
7594
8049
  this.svgMapView = null;
7595
8050
  this.offscreenContainer = null;
7596
8051
  this.overlayView = null;
8052
+ this.defaultTransform = { x: 0, y: 0, rotation: 0 };
8053
+ this.hasEdger = false;
7597
8054
  // boundary数据
7598
8055
  this.boundaryData = [];
7599
8056
  // 边界标签管理器
@@ -7603,7 +8060,7 @@ class MowerMapOverlay {
7603
8060
  // 天线管理器
7604
8061
  this.antennaManager = null;
7605
8062
  // 割草机位置管理器
7606
- this.mowerPostionManager = null;
8063
+ this.mowerPositionManager = null;
7607
8064
  // 当前动画时长
7608
8065
  this.currentAnimationTime = 0;
7609
8066
  // 是否正在用户动画中(区分用户主动触发的动画和地图重绘)
@@ -7626,16 +8083,38 @@ class MowerMapOverlay {
7626
8083
  // 初始状态记录(用于计算相对于初始状态的偏移)
7627
8084
  this.initialOffset = { x: 0, y: 0 };
7628
8085
  this.initialRotation = 0;
8086
+ this.mowPartitionData = null;
8087
+ this.updatePathDataByMowingPositionThrottled = throttleAdvanced(this.updatePathDataByMowingPosition, 300, {
8088
+ leading: true,
8089
+ trailing: false,
8090
+ });
7629
8091
  this.bounds = bounds;
7630
8092
  this.mapData = mapData;
8093
+ this.partitionBoundary = partitionBoundary;
7631
8094
  this.pathData = pathData;
7632
8095
  this.isEditMode = isEditMode;
8096
+ this.unitType = unitType;
8097
+ this.language = language;
7633
8098
  this.mapConfig = mapConfig;
7634
8099
  this.antennaConfig = antennaConfig;
7635
8100
  this.onMapLoad = onMapLoad;
7636
8101
  this.onPathLoad = onPathLoad;
7637
8102
  this.dragCallbacks = dragCallbacks;
7638
- this.mowerPositonConfig = mowerPositonConfig;
8103
+ this.mowerPositionConfig = mowerPositionConfig;
8104
+ this.mowPartitionData = mowPartitionData;
8105
+ this.modelType = modelType;
8106
+ // 设置默认的transform
8107
+ if (defaultTransform) {
8108
+ this.defaultTransform = {
8109
+ x: defaultTransform.x ?? 0,
8110
+ y: defaultTransform.y ?? 0,
8111
+ rotation: defaultTransform.rotation ?? 0,
8112
+ };
8113
+ // defaultTransform的x对应经度偏移量,y对应纬度偏移量
8114
+ this.latLngOffset.lng = this.defaultTransform.x;
8115
+ this.latLngOffset.lat = this.defaultTransform.y;
8116
+ this.currentRotation = this.defaultTransform.rotation;
8117
+ }
7639
8118
  // 创建 OverlayView 实例
7640
8119
  if (window.google && window.google.maps) {
7641
8120
  this.overlayView = new window.google.maps.OverlayView();
@@ -7650,26 +8129,28 @@ class MowerMapOverlay {
7650
8129
  this.overlayView.handleSave = this.handleSave.bind(this);
7651
8130
  this.overlayView.setCustomIcons = this.setCustomIcons.bind(this);
7652
8131
  this.overlayView.getCurrentDragState = this.getCurrentDragState.bind(this);
8132
+ this.overlayView.setTransform = this.setTransform.bind(this);
8133
+ this.overlayView.resetToDefaultTransform = this.resetToDefaultTransform.bind(this);
7653
8134
  }
7654
8135
  this.boundaryData = generateBoundaryData(mapData, pathData);
7655
8136
  }
7656
- updatePosition(positonConfig, animationTime = 2200) {
7657
- console.log('updatePosition==', positonConfig, animationTime);
8137
+ updatePosition(positionConfig, animationTime = 2200) {
7658
8138
  // 保存当前动画时长
7659
8139
  this.currentAnimationTime = animationTime;
7660
8140
  // 标记是否为用户动画(大于0表示用户主动触发的动画)
7661
8141
  this.isUserAnimation = animationTime > 0;
7662
8142
  // 更新割草机位置配置
7663
- this.mowerPositonConfig = positonConfig;
8143
+ this.mowerPositionConfig = positionConfig;
7664
8144
  // 更新割草机位置管理器
7665
- if (this.mowerPostionManager) {
7666
- this.mowerPostionManager.updatePosition(positonConfig, animationTime);
8145
+ if (this.mowerPositionManager) {
8146
+ this.mowerPositionManager.updatePosition(positionConfig, animationTime);
7667
8147
  }
7668
8148
  }
7669
- updatePostionByLastPosition(chargingPilesPositonConfig, animationTime = 2200) {
8149
+ updatePositionByLastPosition(chargingPilesPositionConfig) {
8150
+ this.mowerPositionConfig = chargingPilesPositionConfig;
7670
8151
  // 更新配置
7671
- if (this.mowerPostionManager) {
7672
- this.mowerPostionManager.updatePostionByLastPosition(chargingPilesPositonConfig, animationTime);
8152
+ if (this.mowerPositionManager) {
8153
+ this.mowerPositionManager.updatePositionByLastPosition(chargingPilesPositionConfig);
7673
8154
  }
7674
8155
  }
7675
8156
  setMap(map) {
@@ -7677,6 +8158,12 @@ class MowerMapOverlay {
7677
8158
  this.overlayView.setMap(map);
7678
8159
  }
7679
8160
  }
8161
+ setEdger(edger) {
8162
+ this.hasEdger = edger;
8163
+ if (this.mowerPositionManager) {
8164
+ this.mowerPositionManager.setEdger(edger);
8165
+ }
8166
+ }
7680
8167
  getMap() {
7681
8168
  return this.overlayView ? this.overlayView.getMap() : null;
7682
8169
  }
@@ -7686,6 +8173,23 @@ class MowerMapOverlay {
7686
8173
  getPanes() {
7687
8174
  return this.overlayView ? this.overlayView.getPanes() : null;
7688
8175
  }
8176
+ resetBorderLayerHighlight() {
8177
+ this.mowPartitionData = null;
8178
+ const boundaryBorderLayer = this.svgMapView?.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
8179
+ if (!boundaryBorderLayer)
8180
+ return;
8181
+ boundaryBorderLayer.setMowingBoundarys([]);
8182
+ this.svgMapView?.renderLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
8183
+ }
8184
+ setBorderLayerHighlight(mowPartitionData) {
8185
+ this.mowPartitionData = mowPartitionData;
8186
+ const boundaryBorderLayer = this.svgMapView?.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
8187
+ const partitionIds = mowPartitionData?.partitionIds || [];
8188
+ if (!boundaryBorderLayer)
8189
+ return;
8190
+ boundaryBorderLayer.setMowingBoundarys(partitionIds);
8191
+ this.svgMapView?.renderLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
8192
+ }
7689
8193
  onAdd() {
7690
8194
  // 创建包含SVG的div
7691
8195
  this.div = document.createElement('div');
@@ -7708,6 +8212,7 @@ class MowerMapOverlay {
7708
8212
  this.createBoundaryLabelsManager();
7709
8213
  // 创建割草机位置管理器
7710
8214
  this.createMowerPositionManager();
8215
+ this.setManagerRotation(this.defaultTransform.rotation);
7711
8216
  // 如果处于编辑模式,创建编辑界面
7712
8217
  if (this.isEditMode) {
7713
8218
  this.createEditInterface();
@@ -7746,23 +8251,17 @@ class MowerMapOverlay {
7746
8251
  const map = this.getMap();
7747
8252
  if (!map || !this.svgMapView)
7748
8253
  return;
7749
- map.getZoom();
7750
- // const center = map.getCenter();
7751
- // 基础公式:像素/米 = 156543.03392 * cos(latitude) / (2 ^ zoom)
7752
- // const metersPerPixel =
7753
- // (156543.03392 * Math.cos((center.lat() * Math.PI) / 180)) / Math.pow(2, currentZoom);
7754
- // 缩放比例 = 1 / 米/像素
7755
- // const scale = (1 / metersPerPixel) * 50;
7756
- // 应用缩放到SVG
7757
- // this.svgMapView.setZoom(scale);
8254
+ this.draw();
7758
8255
  }
7759
8256
  // 创建边界标签管理器
7760
8257
  createBoundaryLabelsManager() {
7761
8258
  if (!this.div || !this.svgMapView)
7762
8259
  return;
7763
- console.log('this.boundaryData----->', this.boundaryData);
7764
8260
  // 创建边界标签管理器
7765
- 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
+ });
7766
8265
  // 设置叠加层div引用
7767
8266
  this.boundaryLabelsManager.setOverlayDiv(this.div);
7768
8267
  // 添加所有边界标签
@@ -7806,11 +8305,12 @@ class MowerMapOverlay {
7806
8305
  if (!this.div || !this.svgMapView)
7807
8306
  return;
7808
8307
  // 创建割草机位置管理器,传入动画完成回调
7809
- this.mowerPostionManager = new MowerPostionManager(this.svgMapView, this.mowerPositonConfig, this.div);
8308
+ this.mowerPositionManager = new MowerPositionManager(this.svgMapView, this.mowerPositionConfig, this.modelType, this.div, () => { }, this.updatePathDataByMowingPositionThrottled.bind(this));
7810
8309
  // 设置叠加层div引用
7811
- this.mowerPostionManager.setOverlayDiv(this.div);
8310
+ this.mowerPositionManager.setOverlayDiv(this.div);
8311
+ this.mowerPositionManager.setEdger(this.hasEdger);
7812
8312
  // 获取容器并添加到主div
7813
- const container = this.mowerPostionManager.getElement();
8313
+ const container = this.mowerPositionManager.getElement();
7814
8314
  if (container) {
7815
8315
  this.div.appendChild(container);
7816
8316
  }
@@ -7837,23 +8337,9 @@ class MowerMapOverlay {
7837
8337
  this.boundaryLabelsManager.updatePositionsWithPrecomputedData(width, height, viewBoxInfo);
7838
8338
  }
7839
8339
  // 更新管理器位置
7840
- updateManagerPositions(width, height) {
8340
+ updateManagerPositions(_width, _height) {
7841
8341
  if (!this.div)
7842
8342
  return;
7843
- // 获取SVG元素和其viewBox
7844
- const svgElement = this.div.querySelector('svg');
7845
- if (!svgElement)
7846
- return;
7847
- const viewBox = svgElement.viewBox.baseVal;
7848
- if (!viewBox)
7849
- return;
7850
- // 构造viewBox信息对象
7851
- ({
7852
- x: viewBox.x,
7853
- y: viewBox.y,
7854
- width: viewBox.width,
7855
- height: viewBox.height,
7856
- });
7857
8343
  // 更新充电桩位置
7858
8344
  if (this.chargingPileManager) {
7859
8345
  this.chargingPileManager.updatePositions();
@@ -7902,7 +8388,7 @@ class MowerMapOverlay {
7902
8388
  this.rotateHandle.style.pointerEvents = 'auto';
7903
8389
  this.rotateHandle.innerHTML = DEFAULT_ROTATE_ICON;
7904
8390
  this.editContainer.appendChild(this.rotateHandle);
7905
- // 创建拖拽手柄(左上角)
8391
+ // 创建拖拽手柄(左下角)- 仅在移动设备上显示
7906
8392
  this.dragHandle = document.createElement('div');
7907
8393
  this.dragHandle.style.position = 'absolute';
7908
8394
  this.dragHandle.style.bottom = '-20px';
@@ -7913,6 +8399,10 @@ class MowerMapOverlay {
7913
8399
  this.dragHandle.style.zIndex = EDIT_STYLES.Z_INDEX.HANDLE;
7914
8400
  this.dragHandle.style.pointerEvents = 'auto';
7915
8401
  this.dragHandle.innerHTML = DEFAULT_DRAG_ICON;
8402
+ // 在PC设备上隐藏拖拽手柄
8403
+ if (!isMobileDevice()) {
8404
+ this.dragHandle.style.display = 'none';
8405
+ }
7916
8406
  this.editContainer.appendChild(this.dragHandle);
7917
8407
  // 将编辑容器添加到主div
7918
8408
  this.div.appendChild(this.editContainer);
@@ -7928,9 +8418,10 @@ class MowerMapOverlay {
7928
8418
  }
7929
8419
  // 获取当前拖拽状态
7930
8420
  getCurrentDragState() {
8421
+ // 返回基于地图中心点的经纬度偏移量
7931
8422
  return {
7932
- offsetX: this.latLngOffset.lng * 111320 + this.tempPixelOffset.x - this.initialOffset.x, // 简化的经度到米的转换
7933
- offsetY: this.latLngOffset.lat * 110540 + this.tempPixelOffset.y - this.initialOffset.y, // 简化的纬度到米的转换
8423
+ x: this.latLngOffset.lng + this.tempPixelOffset.x - this.initialOffset.x,
8424
+ y: this.latLngOffset.lat + this.tempPixelOffset.y - this.initialOffset.y,
7934
8425
  rotation: this.currentRotation - this.initialRotation,
7935
8426
  isDragging: this.isDragging,
7936
8427
  isRotating: this.isRotating,
@@ -7953,7 +8444,6 @@ class MowerMapOverlay {
7953
8444
  this.boundaryLabelsManager.collapseAllLabels();
7954
8445
  }
7955
8446
  this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
7956
- console.log('开始旋转操作');
7957
8447
  });
7958
8448
  // 旋转手柄的触摸事件
7959
8449
  this.rotateHandle.addEventListener('touchstart', (e) => {
@@ -7969,39 +8459,41 @@ class MowerMapOverlay {
7969
8459
  this.boundaryLabelsManager.collapseAllLabels();
7970
8460
  }
7971
8461
  this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
7972
- console.log('开始旋转操作(触摸)');
7973
- }, { passive: false });
7974
- // 拖拽手柄的鼠标事件
7975
- this.dragHandle.addEventListener('mousedown', (e) => {
7976
- e.preventDefault();
7977
- e.stopPropagation();
7978
- e.stopImmediatePropagation();
7979
- this.isDragging = true;
7980
- this.startPos = { x: e.clientX, y: e.clientY };
7981
- this.dragHandle.style.cursor = 'grabbing';
7982
- // 开始编辑时关闭所有展开的边界标签
7983
- if (this.boundaryLabelsManager) {
7984
- this.boundaryLabelsManager.collapseAllLabels();
7985
- }
7986
- this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
7987
- console.log('开始拖动操作(通过手柄)');
7988
- });
7989
- // 拖拽手柄的触摸事件
7990
- this.dragHandle.addEventListener('touchstart', (e) => {
7991
- e.preventDefault();
7992
- e.stopPropagation();
7993
- e.stopImmediatePropagation();
7994
- this.isDragging = true;
7995
- const touch = e.touches[0];
7996
- this.startPos = { x: touch.clientX, y: touch.clientY };
7997
- this.dragHandle.style.cursor = 'grabbing';
7998
- this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
7999
- console.log('开始拖动操作(通过手柄,触摸)');
8000
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
+ }
8001
8490
  // 编辑容器的鼠标事件(整个区域拖拽)
8002
8491
  this.editContainer.addEventListener('mousedown', (e) => {
8003
- console.log('开始拖动操作(整个叠加层)');
8004
- 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) {
8005
8497
  return;
8006
8498
  }
8007
8499
  e.preventDefault();
@@ -8018,8 +8510,11 @@ class MowerMapOverlay {
8018
8510
  });
8019
8511
  // 编辑容器的触摸事件(整个区域拖拽)
8020
8512
  this.editContainer.addEventListener('touchstart', (e) => {
8021
- console.log('开始拖动操作(整个叠加层,触摸)');
8022
- 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) {
8023
8518
  return;
8024
8519
  }
8025
8520
  e.preventDefault();
@@ -8079,7 +8574,6 @@ class MowerMapOverlay {
8079
8574
  e.preventDefault();
8080
8575
  e.stopPropagation();
8081
8576
  e.stopImmediatePropagation();
8082
- console.log('结束编辑操作');
8083
8577
  }
8084
8578
  // 如果是拖拽结束,将像素偏移量转换为地理坐标偏移量
8085
8579
  if (this.isDragging) {
@@ -8101,7 +8595,6 @@ class MowerMapOverlay {
8101
8595
  e.preventDefault();
8102
8596
  e.stopPropagation();
8103
8597
  e.stopImmediatePropagation();
8104
- console.log('结束编辑操作(触摸)');
8105
8598
  }
8106
8599
  // 如果是拖拽结束,将像素偏移量转换为地理坐标偏移量
8107
8600
  if (this.isDragging) {
@@ -8207,11 +8700,12 @@ class MowerMapOverlay {
8207
8700
  // 确保旋转角度在0-360度范围内
8208
8701
  this.currentRotation = ((this.currentRotation % 360) + 360) % 360;
8209
8702
  // 应用旋转变换到DOM元素,同时保持位移
8703
+ // 更新边界标签的旋转角度,使其保持水平状态
8704
+ this.setManagerRotation(this.currentRotation);
8210
8705
  const transform = `translate(${this.tempPixelOffset.x}px, ${this.tempPixelOffset.y}px) rotate(${this.currentRotation}deg)`;
8211
8706
  this.div.style.transform = transform;
8212
8707
  // 更新鼠标起始位置为当前位置,为下次计算做准备
8213
8708
  this.startPos = { x: mouseCurrentX, y: mouseCurrentY };
8214
- console.log('旋转角度:', this.currentRotation, '角度增量:', angleDifferenceDegrees);
8215
8709
  }
8216
8710
  // 将像素偏移量转换为地理坐标偏移量
8217
8711
  convertPixelOffsetToLatLng() {
@@ -8241,17 +8735,8 @@ class MowerMapOverlay {
8241
8735
  // 累积更新地理坐标偏移量(不是直接赋值!)
8242
8736
  this.latLngOffset.lat += latOffset;
8243
8737
  this.latLngOffset.lng += lngOffset;
8244
- console.log('精确转换偏移量:', {
8245
- pixelOffset: this.tempPixelOffset,
8246
- centerLatLng: { lat: centerLatLng.lat(), lng: centerLatLng.lng() },
8247
- offsetLatLng: { lat: offsetLatLng.lat(), lng: offsetLatLng.lng() },
8248
- latOffset,
8249
- lngOffset,
8250
- newLatLngOffset: this.latLngOffset,
8251
- });
8252
8738
  // 重置临时像素偏移量
8253
8739
  this.tempPixelOffset = { x: 0, y: 0 };
8254
- // 重新绘制以应用新的地理坐标偏移量
8255
8740
  this.draw();
8256
8741
  }
8257
8742
  // 获取编辑数据
@@ -8287,8 +8772,6 @@ class MowerMapOverlay {
8287
8772
  editData: editData,
8288
8773
  timestamp: new Date().toISOString(),
8289
8774
  };
8290
- // 在这里可以添加保存逻辑,比如发送到服务器
8291
- console.log('保存编辑数据:', saveData);
8292
8775
  // 显示保存成功提示
8293
8776
  this.showSaveSuccess();
8294
8777
  return saveData;
@@ -8354,6 +8837,63 @@ class MowerMapOverlay {
8354
8837
  this.div.style.pointerEvents = 'none';
8355
8838
  }
8356
8839
  }
8840
+ /**
8841
+ * 设置旋转角度
8842
+ */
8843
+ setManagerRotation(rotation) {
8844
+ if (this.boundaryLabelsManager) {
8845
+ this.boundaryLabelsManager.setRotation(rotation);
8846
+ }
8847
+ if (this.antennaManager) {
8848
+ this.antennaManager.setRotation(rotation);
8849
+ }
8850
+ if (this.chargingPileManager) {
8851
+ this.chargingPileManager.setRotation(rotation);
8852
+ }
8853
+ }
8854
+ // 设置transform
8855
+ setTransform(transform) {
8856
+ if (typeof transform.x === 'number')
8857
+ this.tempPixelOffset.x = transform.x;
8858
+ if (typeof transform.y === 'number')
8859
+ this.tempPixelOffset.y = transform.y;
8860
+ if (typeof transform.rotation === 'number')
8861
+ this.currentRotation = transform.rotation;
8862
+ this.defaultTransform = {
8863
+ x: transform.x,
8864
+ y: transform.y,
8865
+ rotation: transform.rotation,
8866
+ };
8867
+ // defaultTransform的x对应经度偏移量,y对应纬度偏移量
8868
+ this.latLngOffset.lng = this.defaultTransform.x;
8869
+ this.latLngOffset.lat = this.defaultTransform.y;
8870
+ this.setManagerRotation(this.currentRotation);
8871
+ this.draw();
8872
+ }
8873
+ // 重置到默认的transform
8874
+ resetToDefaultTransform() {
8875
+ // 重置所有偏移和旋转相关的状态,x对应经度偏移,y对应纬度偏移
8876
+ this.latLngOffset.lng = this.defaultTransform.x;
8877
+ this.latLngOffset.lat = this.defaultTransform.y;
8878
+ this.tempPixelOffset = { x: 0, y: 0 };
8879
+ this.currentRotation = this.defaultTransform.rotation;
8880
+ this.initialOffset = { x: 0, y: 0 };
8881
+ this.initialRotation = 0;
8882
+ // 重新绘制
8883
+ // 重置边界标签的旋转角度
8884
+ if (this.boundaryLabelsManager) {
8885
+ this.boundaryLabelsManager.setRotation(this.defaultTransform.rotation);
8886
+ }
8887
+ if (this.antennaManager) {
8888
+ this.antennaManager.setRotation(this.defaultTransform.rotation);
8889
+ }
8890
+ if (this.chargingPileManager) {
8891
+ this.chargingPileManager.setRotation(this.defaultTransform.rotation);
8892
+ }
8893
+ this.draw();
8894
+ // 触发拖拽回调,通知外部状态更新
8895
+ this.dragCallbacks?.onDragEnd?.(this.getCurrentDragState());
8896
+ }
8357
8897
  initializeSvgMapView() {
8358
8898
  if (!this.offscreenContainer)
8359
8899
  return;
@@ -8364,8 +8904,10 @@ class MowerMapOverlay {
8364
8904
  this.loadMapData();
8365
8905
  // 加载路径数据
8366
8906
  if (this.pathData && this.svgMapView) {
8367
- this.loadPathData(this.pathData);
8907
+ this.loadPathData(this.pathData, this.mowPartitionData);
8368
8908
  }
8909
+ // 刷新绘制图层
8910
+ this.svgMapView.refresh();
8369
8911
  // 获取生成的SVG并添加到叠加层div中
8370
8912
  const svgElement = this.svgMapView.getSVG();
8371
8913
  if (svgElement) {
@@ -8388,9 +8930,6 @@ class MowerMapOverlay {
8388
8930
  // 使用现有的MapDataProcessor处理地图数据
8389
8931
  const elements = MapDataProcessor.processMapData(this.mapData, this.mapConfig);
8390
8932
  // 分离充电桩和天线元素,其他元素添加到SVG图层
8391
- // const svgElements = elements.filter(element =>
8392
- // element.type !== 'charging_pile' && element.type !== 'antenna'
8393
- // );
8394
8933
  const svgElements = elements.filter((element) => element.type !== 'charging_pile' && element.type !== 'antenna');
8395
8934
  const chargingPileElements = elements.filter((element) => element.type === 'charging_pile');
8396
8935
  // 处理SVG图层元素
@@ -8399,14 +8938,16 @@ class MowerMapOverlay {
8399
8938
  // 处理天线数据
8400
8939
  let antennaElements = [];
8401
8940
  if (this.antennaConfig.length > 0) {
8402
- antennaElements = AntennaDataBuilder.fromAntennaData(this.antennaConfig);
8941
+ antennaElements = AntennaDataBuilder.fromAntennaData(this.antennaConfig, this.mapConfig);
8403
8942
  }
8404
8943
  // 添加图层到SvgMapView
8405
8944
  const layers = drawLayer.getLayers();
8406
8945
  this.svgMapView.clear();
8407
- layers.forEach((layer) => {
8408
- this.svgMapView.addLayer(layer);
8409
- });
8946
+ this.svgMapView.addLayers(layers);
8947
+ const boundaryBorderLayer = this.svgMapView.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
8948
+ if (boundaryBorderLayer) {
8949
+ boundaryBorderLayer.setMowingBoundarys(this.mowPartitionData?.partitionIds || []);
8950
+ }
8410
8951
  this.createChargingPileManager();
8411
8952
  // 使用管理器处理充电桩和天线
8412
8953
  if (this.chargingPileManager && chargingPileElements.length > 0) {
@@ -8432,40 +8973,94 @@ class MowerMapOverlay {
8432
8973
  console.error('加载地图数据时出错:', error);
8433
8974
  }
8434
8975
  }
8435
- loadPathData(pathData) {
8976
+ loadPathData(pathData, mowPartitionData) {
8436
8977
  try {
8437
8978
  // 使用现有的PathDataProcessor处理路径数据
8438
8979
  const pathElements = PathDataProcessor.processPathData(pathData, this.mapConfig);
8980
+ const newPathElements = pathElements.map((pathElement) => {
8981
+ const { id, elements } = pathElement;
8982
+ const isMowBoundary = mowPartitionData && mowPartitionData?.partitionIds?.includes(id);
8983
+ if (isMowBoundary) {
8984
+ return {
8985
+ id: id,
8986
+ elements: elements.map((element) => {
8987
+ const isTransPath = element.pathType === PathSegmentType.TRANS;
8988
+ if (isTransPath) {
8989
+ return element;
8990
+ }
8991
+ return {
8992
+ ...element,
8993
+ style: {
8994
+ ...element.style,
8995
+ lineColor: DEFAULT_STYLES.path.mowingLineColor,
8996
+ },
8997
+ };
8998
+ }),
8999
+ };
9000
+ }
9001
+ else {
9002
+ return pathElement;
9003
+ }
9004
+ });
8439
9005
  const pathLayer = new PathLayer();
8440
- pathLayer.addElements(pathElements);
9006
+ pathLayer.addElements(newPathElements);
8441
9007
  // 添加图层到SvgMapView
8442
9008
  this.svgMapView.removeLayerByType(LAYER_DEFAULT_TYPE.PATH);
8443
9009
  this.svgMapView.addLayer(pathLayer);
8444
- console.log('pathElements----->', this.svgMapView.getLayers());
9010
+ this.svgMapView.renderLayer(LAYER_DEFAULT_TYPE.PATH);
8445
9011
  // 调用回调
8446
- const elementCount = pathElements.length;
9012
+ const elementCount = newPathElements.length;
8447
9013
  this.onPathLoad?.(elementCount);
8448
9014
  }
8449
9015
  catch (error) {
8450
9016
  console.error('加载路径数据时出错:', error);
8451
9017
  }
8452
9018
  }
9019
+ /**
9020
+ * 根据割草机位置,更新路径数据
9021
+ * @param position 割草机位置
9022
+ */
9023
+ updatePathDataByMowingPosition(position) {
9024
+ // 找到当前position所在的分区id,将该点更新到pathData中
9025
+ const currentPartitionId = getPartitionId(this.partitionBoundary, position.x, position.y);
9026
+ const processStateIsMowing = useProcessMowingState.getState().processStateIsMowing;
9027
+ if (currentPartitionId && this.pathData?.[currentPartitionId]) {
9028
+ const currentPathData = this.pathData[currentPartitionId];
9029
+ this.pathData[currentPartitionId] = {
9030
+ ...currentPathData,
9031
+ points: [
9032
+ ...(currentPathData?.points || []),
9033
+ {
9034
+ postureX: Number(position.x),
9035
+ postureY: Number(position.y),
9036
+ knifeRotation: processStateIsMowing && position.vehicleState === RobotStatus.MOWING ? '01' : '00', // "knifeRotation": "01",//刀盘是否转动 00-否 01-是
9037
+ pathType: '', //这里由于从实时路径是获取不到pathType的,所以这里暂时不传。让路径是否绘制取决于knifeRotation
9038
+ partitionId: currentPartitionId.toString(),
9039
+ },
9040
+ ],
9041
+ };
9042
+ this.updatePathData(this.pathData, this.mowPartitionData);
9043
+ }
9044
+ }
9045
+ updateMowPartitionData(mowPartitionData) {
9046
+ this.mowPartitionData = mowPartitionData;
9047
+ }
8453
9048
  /** 更新历史路径数据 */
8454
- updatePathData(pathData) {
9049
+ updatePathData(pathData, mowPartitionData) {
8455
9050
  if (!this.svgMapView || !pathData)
8456
9051
  return;
8457
9052
  // 找到pathLayer,将其删除,然后重新添加
8458
- this.loadPathData(pathData);
9053
+ this.loadPathData(pathData, mowPartitionData);
8459
9054
  }
9055
+ /** 更新边界标签信息 */
8460
9056
  updateBoundaryLabelInfo(pathJson) {
8461
9057
  if (!pathJson)
8462
9058
  return;
8463
9059
  const boundaryData = generateBoundaryData(this.mapData, pathJson);
8464
- console.log('boundaryData==', boundaryData);
9060
+ // console.log('boundaryData==', boundaryData);
8465
9061
  this.boundaryLabelsManager?.updateBoundaryData(boundaryData);
8466
9062
  }
8467
9063
  draw() {
8468
- console.log('draw方法被调用');
8469
9064
  // 防御性检查:如果this.div为null,说明onAdd还没被调用,直接返回
8470
9065
  if (!this.div) {
8471
9066
  return;
@@ -8523,13 +9118,17 @@ class MowerMapOverlay {
8523
9118
  this.div.style.transform = transform;
8524
9119
  }
8525
9120
  else {
8526
- // 非拖拽时:只应用旋转(如果有的话)
8527
- if (this.currentRotation !== 0) {
8528
- this.div.style.transform = `rotate(${this.currentRotation}deg)`;
9121
+ // 非拖拽时:应用当前偏移和旋转(包括默认值)
9122
+ const transforms = [];
9123
+ // 应用像素偏移
9124
+ if (this.tempPixelOffset.x !== 0 || this.tempPixelOffset.y !== 0) {
9125
+ transforms.push(`translate(${this.tempPixelOffset.x}px, ${this.tempPixelOffset.y}px)`);
8529
9126
  }
8530
- else {
8531
- this.div.style.transform = '';
9127
+ // 应用旋转
9128
+ if (this.currentRotation !== 0) {
9129
+ transforms.push(`rotate(${this.currentRotation}deg)`);
8532
9130
  }
9131
+ this.div.style.transform = transforms.join(' ');
8533
9132
  }
8534
9133
  // 更新SVG视图框以适应新的尺寸
8535
9134
  if (this.svgMapView) {
@@ -8549,11 +9148,11 @@ class MowerMapOverlay {
8549
9148
  this.updateManagerPositions(width, height);
8550
9149
  // 重绘的时候可能在动画中,这时候需要强制更新一次数据,不然会出现抖动的效果
8551
9150
  // 非动画的情况下,根据最新的数据实时同步就可以了
8552
- if (!this.mowerPostionManager?.animationFlag) {
8553
- this.mowerPostionManager?.updatePostionByLastPosition(this.mowerPositonConfig, 0);
9151
+ if (!this.mowerPositionManager?.animationFlag) {
9152
+ this.mowerPositionManager?.updatePositionByLastPosition(this.mowerPositionConfig);
8554
9153
  }
8555
9154
  else {
8556
- this.mowerPostionManager?.forceUpdatePosition();
9155
+ this.mowerPositionManager?.forceUpdatePosition();
8557
9156
  }
8558
9157
  if (this.antennaManager) {
8559
9158
  this.antennaManager.updateAntennaPosition();
@@ -8589,9 +9188,9 @@ class MowerMapOverlay {
8589
9188
  this.antennaManager = null;
8590
9189
  }
8591
9190
  // 清理割草机位置管理器
8592
- if (this.mowerPostionManager) {
8593
- this.mowerPostionManager.destroy();
8594
- this.mowerPostionManager = null;
9191
+ if (this.mowerPositionManager) {
9192
+ this.mowerPositionManager.destroy();
9193
+ this.mowerPositionManager = null;
8595
9194
  }
8596
9195
  // 清理编辑界面
8597
9196
  this.removeEditInterface();
@@ -8608,12 +9207,45 @@ class MowerMapOverlay {
8608
9207
  }
8609
9208
  // 显示/隐藏割草机位置
8610
9209
  setMowerPositionVisible(visible) {
8611
- if (this.mowerPostionManager) {
8612
- this.mowerPostionManager.setVisible(visible);
8613
- }
9210
+ if (this.mowerPositionManager) {
9211
+ this.mowerPositionManager.setVisible(visible);
9212
+ }
9213
+ }
9214
+ // 获取SvgMapView实例(用于debug)
9215
+ getSvgMapView() {
9216
+ return this.svgMapView;
9217
+ }
9218
+ }
9219
+
9220
+ // 获取车辆状态的中文文案
9221
+ const getVehicleStateText = (vehicleState) => {
9222
+ switch (vehicleState) {
9223
+ case RobotStatus.PARKED:
9224
+ return 'PARKED';
9225
+ case RobotStatus.CHARGING:
9226
+ return 'CHARGING';
9227
+ case RobotStatus.STANDBY:
9228
+ return 'STANDBY';
9229
+ case RobotStatus.MOWING:
9230
+ return 'MOWING';
9231
+ case RobotStatus.WORKING:
9232
+ return 'WORKING';
9233
+ case RobotStatus.MAPPING:
9234
+ return 'MAPPING';
9235
+ case RobotStatus.ERROR:
9236
+ return 'ERROR';
9237
+ case RobotStatus.UPGRADING:
9238
+ return 'UPGRADING';
9239
+ case RobotStatus.DISCONNECTED:
9240
+ return 'DISCONNECTED';
9241
+ case RobotStatus.TASK_DELAY:
9242
+ return 'TASK_DELAY';
9243
+ case RobotStatus.UNKNOWN:
9244
+ return '未知';
9245
+ default:
9246
+ return `未知状态(${vehicleState})`;
8614
9247
  }
8615
- }
8616
-
9248
+ };
8617
9249
  // 验证GPS坐标是否有效
8618
9250
  const isValidGpsCoordinate = (coordinate) => {
8619
9251
  if (!coordinate || coordinate.length < 2)
@@ -8627,47 +9259,89 @@ const isValidGpsCoordinate = (coordinate) => {
8627
9259
  !(Math.abs(lng) < 0.001 && Math.abs(lat) < 0.001) // 排除接近(0,0)的坐标
8628
9260
  );
8629
9261
  };
9262
+ // 旋转坐标点
9263
+ const rotateCoordinate = (point, center, angleRadians) => {
9264
+ const [x, y] = point;
9265
+ const [cx, cy] = center;
9266
+ // 将点移动到原点
9267
+ const dx = x - cx;
9268
+ const dy = y - cy;
9269
+ // 应用旋转矩阵
9270
+ const cos = Math.cos(angleRadians);
9271
+ const sin = Math.sin(angleRadians);
9272
+ const rotatedX = dx * cos - dy * sin;
9273
+ const rotatedY = dx * sin + dy * cos;
9274
+ // 移回原位置
9275
+ return [rotatedX + cx, rotatedY + cy];
9276
+ };
8630
9277
  // 获取有效的GPS边界
8631
- const getValidGpsBounds = (mapData) => {
9278
+ const getValidGpsBounds = (mapData, rotation = 0) => {
9279
+ let bounds;
8632
9280
  // 首先尝试使用地图数据中的GPS坐标
8633
9281
  if (isValidGpsCoordinate(mapData.sw_gps) && isValidGpsCoordinate(mapData.ne_gps)) {
8634
- return {
9282
+ bounds = {
8635
9283
  sw: mapData.sw_gps,
8636
9284
  ne: mapData.ne_gps,
8637
9285
  };
8638
9286
  }
8639
- // 如果GPS坐标无效,尝试从地图几何数据估算
8640
- const { sw, ne } = estimateGpsFromMapBounds(mapData);
8641
- console.log('sw, ne==', sw, ne);
8642
- if (sw && ne) {
8643
- console.warn('GPS坐标无效,使用地图几何数据估算边界:', sw, ne);
8644
- return {
8645
- sw: [sw[0], sw[1]],
8646
- ne: [ne[0], ne[1]],
9287
+ else {
9288
+ // 如果GPS坐标无效,尝试从地图几何数据估算
9289
+ const { sw, ne } = estimateGpsFromMapBounds(mapData);
9290
+ if (sw && ne) {
9291
+ console.warn('GPS坐标无效,使用地图几何数据估算边界:', sw, ne);
9292
+ bounds = {
9293
+ sw: [sw[0], sw[1]],
9294
+ ne: [ne[0], ne[1]],
9295
+ };
9296
+ }
9297
+ else {
9298
+ // 最后的fallback:使用默认坐标
9299
+ console.warn('无法获取有效的GPS边界,使用默认坐标');
9300
+ bounds = {
9301
+ sw: [-9.1562, -37.7503],
9302
+ ne: [31.247, 5.797],
9303
+ };
9304
+ }
9305
+ }
9306
+ // 如果有旋转角度,计算旋转后的边界
9307
+ if (rotation !== 0) {
9308
+ const angleRadians = (rotation * Math.PI) / 180; // 转换为弧度
9309
+ // 计算边界中心点
9310
+ const centerLng = (bounds.sw[0] + bounds.ne[0]) / 2;
9311
+ const centerLat = (bounds.sw[1] + bounds.ne[1]) / 2;
9312
+ const center = [centerLng, centerLat];
9313
+ // 旋转四个角点
9314
+ const sw = rotateCoordinate(bounds.sw, center, angleRadians);
9315
+ const ne = rotateCoordinate(bounds.ne, center, angleRadians);
9316
+ const se = rotateCoordinate([bounds.ne[0], bounds.sw[1]], center, angleRadians);
9317
+ const nw = rotateCoordinate([bounds.sw[0], bounds.ne[1]], center, angleRadians);
9318
+ // 计算旋转后的边界框(包含所有旋转后的点)
9319
+ const lngs = [sw[0], ne[0], se[0], nw[0]];
9320
+ const lats = [sw[1], ne[1], se[1], nw[1]];
9321
+ bounds = {
9322
+ sw: [Math.min(...lngs), Math.min(...lats)],
9323
+ ne: [Math.max(...lngs), Math.max(...lats)],
8647
9324
  };
8648
9325
  }
8649
- // 最后的fallback:使用默认坐标
8650
- console.warn('无法获取有效的GPS边界,使用默认坐标');
8651
- return {
8652
- sw: [-9.1562, -37.7503],
8653
- ne: [31.247, 5.797],
8654
- };
9326
+ return bounds;
8655
9327
  };
8656
9328
  // 默认配置
8657
9329
  const defaultMapConfig = DEFAULT_STYLES;
8658
9330
  // 地图渲染器组件
8659
- const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pathJson, realTimeData, currentPositionData, antennaConfig, onMapLoad, onPathLoad, onZoomChange, onError, className, style, googleMapInstance, isEditMode = false, dragCallbacks, }, ref) => {
8660
- const svgMapViewRef = useRef(null);
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) => {
8661
9332
  const [elementCount, setElementCount] = useState(0);
8662
9333
  const [pathCount, setPathCount] = useState(0);
8663
- const [zoom, setZoom] = useState(1);
8664
9334
  const [currentError, setCurrentError] = useState(null);
8665
9335
  const overlayRef = useRef(null);
8666
9336
  // const mapRef = useMap();
8667
9337
  const [isGoogleMapsReady, setIsGoogleMapsReady] = useState(false);
8668
9338
  const [hasInitializedBounds, setHasInitializedBounds] = useState(false);
8669
- const { clearSubBoundaryBorder } = useSubBoundaryBorderStore();
9339
+ const { clearSubBoundaryBorder, clearObstacles, clearSvgElements } = useSubBoundaryBorderStore();
8670
9340
  const currentProcessMowingStatusRef = useRef(false);
9341
+ const { updateProcessStateIsMowing, processStateIsMowing } = useProcessMowingState();
9342
+ const [mowPartitionData, setMowPartitionData] = useState(null);
9343
+ // Debug相关状态
9344
+ const [debugInfo, setDebugInfo] = useState({});
8671
9345
  // 处理地图分区边界
8672
9346
  const partitionBoundary = useMemo(() => {
8673
9347
  const allBoundaryElements = [];
@@ -8690,22 +9364,67 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8690
9364
  }, [mapConfig]);
8691
9365
  const mergedAntennaConfig = useMemo(() => antennaConfig, [antennaConfig]);
8692
9366
  const mowerPositionData = useMemo(() => {
9367
+ // realTimeData 中包含三个种类的数据,之需要实时坐标的数据即可。
9368
+ if (!realTimeData || realTimeData.length === 0)
9369
+ return {
9370
+ postureX: 0,
9371
+ postureY: 0,
9372
+ postureTheta: 0,
9373
+ vehicleState: RobotStatus.DISCONNECTED,
9374
+ };
9375
+ let currentPositionData;
9376
+ if (realTimeData.length === 1 && realTimeData[0].type === RealTimeDataType.LOCATION) {
9377
+ currentPositionData = realTimeData[0];
9378
+ }
9379
+ else {
9380
+ currentPositionData = realTimeData?.find((item) => item.type === RealTimeDataType.LOCATION);
9381
+ }
9382
+ if (!currentPositionData)
9383
+ return undefined;
8693
9384
  return {
8694
9385
  postureTheta: currentPositionData?.postureTheta
8695
9386
  ? Number(currentPositionData.postureTheta)
8696
9387
  : 0,
8697
9388
  postureX: currentPositionData?.postureX ? Number(currentPositionData.postureX) : 0,
8698
9389
  postureY: currentPositionData?.postureY ? Number(currentPositionData.postureY) : 0,
8699
- vehicleState: currentPositionData?.vehicleState || RobotStatus.STANDBY,
8700
- vehicleModel: modelType?.toLowerCase() || '',
9390
+ lastPostureTheta: currentPositionData?.lastPostureTheta
9391
+ ? Number(currentPositionData.lastPostureTheta)
9392
+ : 0,
9393
+ lastPostureX: currentPositionData?.lastPostureX
9394
+ ? Number(currentPositionData.lastPostureX)
9395
+ : 0,
9396
+ lastPostureY: currentPositionData?.lastPostureY
9397
+ ? Number(currentPositionData.lastPostureY)
9398
+ : 0,
9399
+ vehicleState: currentPositionData?.vehicleState || RobotStatus.DISCONNECTED,
8701
9400
  };
8702
- }, [currentPositionData, modelType]);
8703
- console.log('mowerPositionData==', currentPositionData, mowerPositionData);
9401
+ }, [realTimeData, modelType]);
8704
9402
  // 处理错误
8705
9403
  const handleError = (error) => {
8706
9404
  setCurrentError(error);
8707
9405
  onError?.(error);
8708
9406
  };
9407
+ const fitBounds = useCallback(() => {
9408
+ if (!mapJson || !mapRef)
9409
+ return null;
9410
+ // 计算边界
9411
+ const bounds = calculateMapBounds(mapJson);
9412
+ if (!bounds) {
9413
+ handleError('无法计算地图边界');
9414
+ return;
9415
+ }
9416
+ // 将自定义边界转换为Google Maps LatLngBounds(使用有效的GPS坐标)
9417
+ const validBounds = getValidGpsBounds(mapJson, defaultTransform?.rotation);
9418
+ // 地图数据中的坐标格式是 [longitude, latitude]
9419
+ const swLat = validBounds.sw[1] + defaultTransform.y;
9420
+ const swLng = validBounds.sw[0] + defaultTransform.x;
9421
+ const neLat = validBounds.ne[1] + defaultTransform.y;
9422
+ const neLng = validBounds.ne[0] + defaultTransform.x;
9423
+ const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
9424
+ new window.google.maps.LatLng(neLat, neLng) // 东北角
9425
+ );
9426
+ mapRef.fitBounds(googleBounds);
9427
+ }, [mapJson, mapRef, defaultTransform]);
8709
9428
  // 初始化Google Maps叠加层
8710
9429
  const initializeGoogleMapsOverlay = async () => {
8711
9430
  if (!mapJson)
@@ -8729,7 +9448,8 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8729
9448
  return;
8730
9449
  }
8731
9450
  // 将自定义边界转换为Google Maps LatLngBounds(使用有效的GPS坐标)
8732
- const validBounds = getValidGpsBounds(mapJson);
9451
+ // 这里需要使用0度,需要原始的坐标点去计算元素的实际位置,只有在fitbounds的时候才需要使用旋转后的坐标点
9452
+ const validBounds = getValidGpsBounds(mapJson, 0);
8733
9453
  // 地图数据中的坐标格式是 [longitude, latitude]
8734
9454
  const swLat = validBounds.sw[1];
8735
9455
  const swLng = validBounds.sw[0];
@@ -8738,17 +9458,13 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8738
9458
  const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
8739
9459
  new window.google.maps.LatLng(neLat, neLng) // 东北角
8740
9460
  );
8741
- console.log('使用有效GPS坐标创建边界:', {
8742
- sw: { lat: swLat, lng: swLng },
8743
- ne: { lat: neLat, lng: neLng },
8744
- });
8745
9461
  // 如果已经存在叠加层,先移除它
8746
9462
  if (overlayRef.current) {
8747
9463
  overlayRef.current.setMap(null);
8748
9464
  overlayRef.current = null;
8749
9465
  }
8750
9466
  // 创建叠加层
8751
- const overlay = new MowerMapOverlay(googleBounds, mapJson, mowerPositionData, pathJson || {}, isEditMode, mergedMapConfig, mergedAntennaConfig, (count) => {
9467
+ const overlay = new MowerMapOverlay(googleBounds, mapJson, partitionBoundary, mowerPositionData, modelType, pathJson || {}, isEditMode, unitType, language, mergedMapConfig, mergedAntennaConfig, null, defaultTransform, (count) => {
8752
9468
  setElementCount(count);
8753
9469
  onMapLoad?.(count);
8754
9470
  }, (count) => {
@@ -8758,6 +9474,7 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8758
9474
  // 设置地图
8759
9475
  overlay.setMap(mapInstance);
8760
9476
  overlayRef.current = overlay;
9477
+ overlay.setEdger(edger);
8761
9478
  // 只在首次初始化时自适应视图
8762
9479
  if (!hasInitializedBounds) {
8763
9480
  mapInstance.fitBounds(googleBounds);
@@ -8769,39 +9486,33 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8769
9486
  handleError(`初始化Google Maps叠加层失败: ${error instanceof Error ? error.message : String(error)}`);
8770
9487
  }
8771
9488
  };
8772
- const formatRealTimeData = async (realTimeData) => {
8773
- // await sleep(1000);
8774
- const newRealTimeData = handleRealTimeData({
8775
- realTimeData,
8776
- isMowing: currentProcessMowingStatusRef.current,
8777
- pathData: pathJson,
8778
- partitionBoundary,
8779
- });
8780
- console.log('newRealTimeData==', newRealTimeData);
8781
- // currentProcessMowingStatusRef.current = newRealTimeData.isMowing;
8782
- currentProcessMowingStatusRef.current = true;
8783
- // 调用overlay的updatePathData方法,更新历史路径数据
8784
- if (overlayRef.current) {
8785
- overlayRef.current.updatePathData(newRealTimeData.pathData);
8786
- }
8787
- overlayRef.current.updateBoundaryLabelInfo(newRealTimeData.pathData);
8788
- };
9489
+ const resetInCharginPie = useCallback(() => {
9490
+ const elements = MapDataProcessor.processMapData(mapJson, mergedMapConfig);
9491
+ const chargingPiles = elements.find((element) => element.type === 'charging_pile');
9492
+ if (!overlayRef.current)
9493
+ return;
9494
+ // 如果在充电桩上,则直接更新位置到充电桩的位置
9495
+ overlayRef.current.updatePosition({
9496
+ ...mowerPositionData,
9497
+ postureX: chargingPiles?.originalData.position[0],
9498
+ postureY: chargingPiles?.originalData.position[1],
9499
+ postureTheta: chargingPiles?.originalData.direction - Math.PI || 0,
9500
+ }, 0);
9501
+ }, [mapJson, mowerPositionData]);
8789
9502
  // 初始化效果
8790
9503
  useEffect(() => {
8791
9504
  initializeGoogleMapsOverlay();
8792
- console.log('init mow map');
8793
9505
  // 清理函数
8794
9506
  return () => {
8795
9507
  clearSubBoundaryBorder();
9508
+ clearObstacles();
9509
+ clearSvgElements();
9510
+ updateProcessStateIsMowing(false);
8796
9511
  currentProcessMowingStatusRef.current = false;
8797
9512
  if (overlayRef.current) {
8798
9513
  overlayRef.current.setMap(null);
8799
9514
  overlayRef.current = null;
8800
9515
  }
8801
- if (svgMapViewRef.current) {
8802
- svgMapViewRef.current.destroy();
8803
- svgMapViewRef.current = null;
8804
- }
8805
9516
  };
8806
9517
  }, [mapJson, pathJson, mergedMapConfig, mergedAntennaConfig]);
8807
9518
  // 监听编辑模式变化
@@ -8821,13 +9532,12 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8821
9532
  return;
8822
9533
  const elements = MapDataProcessor.processMapData(mapJson, mergedMapConfig);
8823
9534
  const chargingPiles = elements.find((element) => element.type === 'charging_pile');
8824
- console.log('chargingPiles==', chargingPiles, currentPositionData);
8825
9535
  if (!mowerPositionData || !overlayRef.current)
8826
9536
  return;
8827
9537
  const inChargingPiles = [RobotStatus.CHARGING, RobotStatus.PARKED];
8828
9538
  const isOffLine = mowerPositionData.vehicleState === RobotStatus.DISCONNECTED;
8829
9539
  const isInChargingPile = inChargingPiles.includes(mowerPositionData.vehicleState);
8830
- // 如果在充电桩上,则更新位置到充电桩的位置
9540
+ // 如果在充电桩上,则直接更新位置到充电桩的位置
8831
9541
  if (isInChargingPile) {
8832
9542
  overlayRef.current.updatePosition({
8833
9543
  ...mowerPositionData,
@@ -8837,68 +9547,247 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8837
9547
  }, 0);
8838
9548
  }
8839
9549
  else {
9550
+ // 如果车辆是disabled状态或者超出边界(默认超过1000m),则更新位置到上一次的位置
8840
9551
  const positonOutOfRange = isOutOfRange(mowerPositionData);
8841
9552
  const positionValid = isInvalidPosition(mowerPositionData);
8842
- const isStandyBy = mowerPositionData.vehicleState === RobotStatus.STANDBY;
9553
+ const isStandby = mowerPositionData.vehicleState === RobotStatus.STANDBY;
8843
9554
  if (positonOutOfRange || positionValid || isOffLine) {
8844
- overlayRef.current.updatePostionByLastPosition({
8845
- ...mowerPositionData,
8846
- postureX: chargingPiles?.originalData.position[0],
8847
- postureY: chargingPiles?.originalData.position[1],
8848
- postureTheta: chargingPiles?.originalData.direction - Math.PI || 0,
8849
- }, isStandyBy ? 0 : 2200);
9555
+ // 初始信息是通过后端接口获取的,此时当前位置数据不可用的时候,可以取上一次的位置数据
9556
+ // mowerPositionData 中可能会包含上一次的位置数据,
9557
+ const lastPostureX = mowerPositionData.lastPostureX;
9558
+ const lastPostureY = mowerPositionData.lastPostureY;
9559
+ const lastPostureTheta = mowerPositionData.lastPostureTheta;
9560
+ if (lastPostureX && lastPostureY && lastPostureTheta) {
9561
+ overlayRef.current.updatePositionByLastPosition(mowerPositionData);
9562
+ }
9563
+ else {
9564
+ overlayRef.current.updatePositionByLastPosition({
9565
+ ...mowerPositionData,
9566
+ postureX: chargingPiles?.originalData.position[0],
9567
+ postureY: chargingPiles?.originalData.position[1],
9568
+ postureTheta: chargingPiles?.originalData.direction - Math.PI || 0,
9569
+ });
9570
+ }
8850
9571
  }
8851
9572
  else {
8852
- overlayRef.current.updatePosition(mowerPositionData, isStandyBy ? 0 : 2200);
9573
+ overlayRef.current.updatePosition(mowerPositionData, isStandby ? 0 : 2000);
8853
9574
  }
8854
9575
  }
8855
- }, [currentPositionData]);
9576
+ }, [mowerPositionData]);
9577
+ // 更新debug信息
8856
9578
  useEffect(() => {
8857
- if (!mapJson ||
8858
- !pathJson ||
8859
- !realTimeData ||
8860
- realTimeData.length === 0 ||
8861
- !Array.isArray(realTimeData))
9579
+ if (!debug)
8862
9580
  return;
8863
- // 根据后端推送的实时数据,进行不同处理
8864
- // TODO:需要根据返回的数据,处理车辆的移动位置
8865
- // 这里是根据返回的数据,处理实时轨迹
8866
- formatRealTimeData(realTimeData);
8867
- }, [realTimeData, mapJson, pathJson]);
8868
- // 提供ref方法
8869
- useImperativeHandle(ref, () => ({
8870
- setZoom: (newZoom) => {
8871
- setZoom(newZoom);
8872
- if (svgMapViewRef.current) {
8873
- svgMapViewRef.current.setZoom(newZoom);
9581
+ const updateDebugInfo = () => {
9582
+ const newDebugInfo = {};
9583
+ // 获取地图GPS边界
9584
+ if (mapJson) {
9585
+ newDebugInfo.mapBounds = getValidGpsBounds(mapJson, defaultTransform?.rotation);
8874
9586
  }
8875
- onZoomChange?.(newZoom);
8876
- },
8877
- getZoom: () => zoom,
8878
- resetView: () => {
8879
- if (svgMapViewRef.current) {
8880
- svgMapViewRef.current.resetTransform();
9587
+ // 获取SVG viewBox信息
9588
+ if (overlayRef.current) {
9589
+ const overlay = overlayRef.current;
9590
+ const svgMapView = overlay.getSvgMapView?.();
9591
+ if (svgMapView) {
9592
+ const viewBoxInfo = svgMapView.getViewBoxInfo();
9593
+ // 计算实际的米单位数据(除以缩放比例)
9594
+ const SCALE_FACTOR = 50; // 根据项目中的缩放因子
9595
+ newDebugInfo.viewBox = {
9596
+ x: viewBoxInfo.x / SCALE_FACTOR,
9597
+ y: viewBoxInfo.y / SCALE_FACTOR,
9598
+ width: viewBoxInfo.width / SCALE_FACTOR,
9599
+ height: viewBoxInfo.height / SCALE_FACTOR,
9600
+ scale: SCALE_FACTOR,
9601
+ // 计算左下角和右上角坐标
9602
+ sw: {
9603
+ x: viewBoxInfo.x / SCALE_FACTOR,
9604
+ y: viewBoxInfo.y / SCALE_FACTOR,
9605
+ },
9606
+ ne: {
9607
+ x: (viewBoxInfo.x + viewBoxInfo.width) / SCALE_FACTOR,
9608
+ y: (viewBoxInfo.y + viewBoxInfo.height) / SCALE_FACTOR,
9609
+ },
9610
+ };
9611
+ }
8881
9612
  }
8882
- setZoom(1);
8883
- onZoomChange?.(1);
8884
- },
8885
- clear: () => {
8886
- if (svgMapViewRef.current) {
8887
- svgMapViewRef.current.clear();
9613
+ // 获取当前割草机位置
9614
+ if (mowerPositionData) {
9615
+ newDebugInfo.mowerPosition = {
9616
+ x: mowerPositionData.postureX || 0,
9617
+ y: mowerPositionData.postureY || 0,
9618
+ theta: mowerPositionData.postureTheta || 0,
9619
+ lastX: mowerPositionData.lastPostureX || 0,
9620
+ lastY: mowerPositionData.lastPostureY || 0,
9621
+ lastTheta: mowerPositionData.lastPostureTheta || 0,
9622
+ vehicleState: mowerPositionData.vehicleState || RobotStatus.UNKNOWN,
9623
+ vehicleStateText: getVehicleStateText(mowerPositionData.vehicleState || RobotStatus.UNKNOWN),
9624
+ };
8888
9625
  }
8889
- setElementCount(0);
8890
- setPathCount(0);
8891
- },
8892
- getSvgElement: () => {
8893
- return svgMapViewRef.current ? svgMapViewRef.current.getSVG() : null;
8894
- },
8895
- fitToView: () => {
8896
- if (svgMapViewRef.current && mapJson) {
8897
- const bounds = calculateMapBounds(mapJson);
8898
- if (bounds) {
8899
- svgMapViewRef.current.fitToView(bounds);
9626
+ // 获取当前割草地块数据
9627
+ newDebugInfo.partitionData = mowPartitionData;
9628
+ setDebugInfo(newDebugInfo);
9629
+ };
9630
+ updateDebugInfo();
9631
+ }, [debug, mapJson, mowerPositionData, mowPartitionData, defaultTransform]);
9632
+ // 当关键数据变化时立即更新debug信息
9633
+ useEffect(() => {
9634
+ if (!debug)
9635
+ return;
9636
+ const updateDebugInfo = () => {
9637
+ const newDebugInfo = {};
9638
+ // 获取地图GPS边界
9639
+ if (mapJson) {
9640
+ newDebugInfo.mapBounds = getValidGpsBounds(mapJson, defaultTransform?.rotation);
9641
+ }
9642
+ // 获取SVG viewBox信息
9643
+ if (overlayRef.current) {
9644
+ const overlay = overlayRef.current;
9645
+ const svgMapView = overlay.getSvgMapView?.();
9646
+ if (svgMapView) {
9647
+ const viewBoxInfo = svgMapView.getViewBoxInfo();
9648
+ // 计算实际的米单位数据(除以缩放比例)
9649
+ const SCALE_FACTOR = 50; // 根据项目中的缩放因子
9650
+ newDebugInfo.viewBox = {
9651
+ x: viewBoxInfo.x / SCALE_FACTOR,
9652
+ y: viewBoxInfo.y / SCALE_FACTOR,
9653
+ width: viewBoxInfo.width / SCALE_FACTOR,
9654
+ height: viewBoxInfo.height / SCALE_FACTOR,
9655
+ scale: SCALE_FACTOR,
9656
+ // 计算左下角和右上角坐标
9657
+ sw: {
9658
+ x: viewBoxInfo.x / SCALE_FACTOR,
9659
+ y: viewBoxInfo.y / SCALE_FACTOR,
9660
+ },
9661
+ ne: {
9662
+ x: (viewBoxInfo.x + viewBoxInfo.width) / SCALE_FACTOR,
9663
+ y: (viewBoxInfo.y + viewBoxInfo.height) / SCALE_FACTOR,
9664
+ },
9665
+ };
8900
9666
  }
8901
9667
  }
9668
+ // 获取当前割草机位置
9669
+ if (mowerPositionData) {
9670
+ newDebugInfo.mowerPosition = {
9671
+ x: mowerPositionData.postureX || 0,
9672
+ y: mowerPositionData.postureY || 0,
9673
+ theta: mowerPositionData.postureTheta || 0,
9674
+ lastX: mowerPositionData.lastPostureX || 0,
9675
+ lastY: mowerPositionData.lastPostureY || 0,
9676
+ lastTheta: mowerPositionData.lastPostureTheta || 0,
9677
+ vehicleState: mowerPositionData.vehicleState || RobotStatus.UNKNOWN,
9678
+ vehicleStateText: getVehicleStateText(mowerPositionData.vehicleState || RobotStatus.UNKNOWN),
9679
+ };
9680
+ }
9681
+ // 获取当前割草地块数据
9682
+ newDebugInfo.partitionData = mowPartitionData;
9683
+ setDebugInfo(newDebugInfo);
9684
+ };
9685
+ updateDebugInfo();
9686
+ }, [debug, mowerPositionData, mowPartitionData]);
9687
+ useEffect(() => {
9688
+ if (!realTimeData || realTimeData.length === 0 || !Array.isArray(realTimeData)) {
9689
+ return;
9690
+ }
9691
+ let curMowPartitionData = mowPartitionData;
9692
+ // realtime中包含当前割草任务的数据,根据数据进行path路径和边界的高亮操作,
9693
+ const mowingPartition = realTimeData.find((item) => item.type === RealTimeDataType.PARTITION);
9694
+ if (mowingPartition) {
9695
+ setMowPartitionData(mowingPartition);
9696
+ curMowPartitionData = mowingPartition;
9697
+ }
9698
+ const positionData = realTimeData?.find((item) => item?.type === RealTimeDataType.LOCATION);
9699
+ const statusData = realTimeData?.find((item) => item?.type === RealTimeDataType.STATUS);
9700
+ if (statusData || positionData) {
9701
+ const currentStatus = statusData?.vehicleState || positionData?.vehicleState;
9702
+ // 车辆回桩不会回传最后的park的位置,所以根据实时数据的状态数据判断车辆回到桩上
9703
+ if ([RobotStatus.CHARGING, RobotStatus.PARKED].includes(currentStatus || RobotStatus.UNKNOWN)) {
9704
+ resetInCharginPie();
9705
+ }
9706
+ else if (currentStatus === RobotStatus.WORKING) {
9707
+ // 兜底收不到割草地块的实时数据,使用状态来兜底
9708
+ overlayRef.current.resetBorderLayerHighlight();
9709
+ setMowPartitionData({});
9710
+ curMowPartitionData = {};
9711
+ }
9712
+ else if (currentStatus === RobotStatus.MOWING &&
9713
+ curMowPartitionData &&
9714
+ !curMowPartitionData?.partitionIds) {
9715
+ // 如果当前是割草状态,但是地块数据初始化过且不存在则认为是全局割草,则把所有地块都高亮
9716
+ const allPartitionIds = mapJson?.sub_maps?.map((item) => item?.id);
9717
+ setMowPartitionData({
9718
+ partitionIds: allPartitionIds,
9719
+ });
9720
+ curMowPartitionData = {
9721
+ partitionIds: allPartitionIds,
9722
+ };
9723
+ }
9724
+ }
9725
+ if (!mapJson || !pathJson || !overlayRef.current)
9726
+ return;
9727
+ // 根据后端推送的实时数据,进行不同处理
9728
+ if (curMowPartitionData) {
9729
+ const isMowing = curMowPartitionData?.partitionIds && curMowPartitionData.partitionIds.length > 0;
9730
+ overlayRef.current.updateMowPartitionData(curMowPartitionData);
9731
+ if (!isMowing) {
9732
+ overlayRef.current.resetBorderLayerHighlight();
9733
+ }
9734
+ else {
9735
+ overlayRef.current.setBorderLayerHighlight(curMowPartitionData);
9736
+ }
9737
+ }
9738
+ // 如果一次性推送的是多条数据,则把多条数据处理后存入pathData,然后更新路径数据和边界标签信息
9739
+ // 如果一次只推送一条数据,则只解析里面的进度数据,然后更新边界标签信息,剩下的实时轨迹数据由车辆运动产生时存入pathData
9740
+ if (realTimeData.length > 1) {
9741
+ const { pathData, isMowing } = handleMultipleRealTimeData({
9742
+ realTimeData,
9743
+ isMowing: processStateIsMowing,
9744
+ pathData: pathJson,
9745
+ partitionBoundary,
9746
+ });
9747
+ updateProcessStateIsMowing(isMowing);
9748
+ if (pathData) {
9749
+ overlayRef.current.updatePathData(pathData, curMowPartitionData);
9750
+ overlayRef.current.updateBoundaryLabelInfo(pathData);
9751
+ }
9752
+ }
9753
+ else {
9754
+ const { isMowing, pathData } = getProcessMowingDataFromRealTimeData({
9755
+ realTimeData,
9756
+ isMowing: processStateIsMowing,
9757
+ pathData: pathJson,
9758
+ });
9759
+ updateProcessStateIsMowing(isMowing);
9760
+ overlayRef.current.updatePathData(pathData, curMowPartitionData);
9761
+ // 更新进度数据
9762
+ if (pathData) {
9763
+ overlayRef.current.updateBoundaryLabelInfo(pathData);
9764
+ }
9765
+ }
9766
+ }, [realTimeData, mapJson, pathJson]);
9767
+ useEffect(() => {
9768
+ if (!overlayRef.current || !defaultTransform)
9769
+ return;
9770
+ overlayRef.current?.setTransform(defaultTransform);
9771
+ const validBounds = getValidGpsBounds(mapJson, defaultTransform?.rotation);
9772
+ // 地图数据中的坐标格式是 [longitude, latitude]
9773
+ const swLat = validBounds.sw[1] + defaultTransform.y;
9774
+ const swLng = validBounds.sw[0] + defaultTransform.x;
9775
+ const neLat = validBounds.ne[1] + defaultTransform.y;
9776
+ const neLng = validBounds.ne[0] + defaultTransform.x;
9777
+ const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
9778
+ new window.google.maps.LatLng(neLat, neLng) // 东北角
9779
+ );
9780
+ mapRef.fitBounds(googleBounds);
9781
+ }, [defaultTransform]);
9782
+ useEffect(() => {
9783
+ if (!overlayRef || !overlayRef.current)
9784
+ return;
9785
+ overlayRef.current.setEdger(edger);
9786
+ }, [edger]);
9787
+ // 提供ref方法
9788
+ useImperativeHandle(ref, () => ({
9789
+ fitToView: () => {
9790
+ fitBounds();
8902
9791
  },
8903
9792
  getOverlay: () => {
8904
9793
  return overlayRef.current;
@@ -8927,13 +9816,35 @@ const MowerMapRenderer = forwardRef(({ mapConfig, modelType, mapRef, mapJson, pa
8927
9816
  getElementCount: () => elementCount,
8928
9817
  getPathCount: () => pathCount,
8929
9818
  isGoogleMapsReady: () => isGoogleMapsReady,
9819
+ setTransform: (t) => overlayRef.current?.setTransform(t),
9820
+ resetToDefaultTransform: () => overlayRef.current?.resetToDefaultTransform(),
8930
9821
  }));
9822
+ // Debug信息组件
9823
+ const DebugInfo = () => {
9824
+ if (!debug)
9825
+ return null;
9826
+ return (jsxs("div", { style: {
9827
+ position: 'fixed',
9828
+ bottom: '10px',
9829
+ left: '10px',
9830
+ backgroundColor: 'rgba(0, 0, 0, 0.8)',
9831
+ color: 'white',
9832
+ padding: '10px',
9833
+ borderRadius: '5px',
9834
+ fontSize: '12px',
9835
+ fontFamily: 'monospace',
9836
+ zIndex: 10000,
9837
+ maxWidth: '300px',
9838
+ lineHeight: '1.4',
9839
+ }, children: [jsx("div", { style: { fontWeight: 'bold', marginBottom: '8px' }, children: "\uD83D\uDC1B Debug Info" }), debugInfo.mapBounds && (jsxs("div", { style: { marginBottom: '6px' }, children: [jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDCCD Map GPS Bounds:" }), jsxs("div", { children: ["SW: [", debugInfo.mapBounds.sw[0].toFixed(6), ", ", debugInfo.mapBounds.sw[1].toFixed(6), "]"] }), jsxs("div", { children: ["NE: [", debugInfo.mapBounds.ne[0].toFixed(6), ", ", debugInfo.mapBounds.ne[1].toFixed(6), "]"] })] })), debugInfo.viewBox && (jsxs("div", { style: { marginBottom: '6px' }, children: [jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDCD0 SVG ViewBox (meters):" }), jsxs("div", { children: ["SW: [", debugInfo.viewBox.sw.x.toFixed(2), ", ", debugInfo.viewBox.sw.y.toFixed(2), "]"] }), jsxs("div", { children: ["NE: [", debugInfo.viewBox.ne.x.toFixed(2), ", ", debugInfo.viewBox.ne.y.toFixed(2), "]"] }), jsxs("div", { children: ["Size: ", debugInfo.viewBox.width.toFixed(2), "m \u00D7 ", debugInfo.viewBox.height.toFixed(2), "m"] }), jsxs("div", { children: ["Scale: 1:", debugInfo.viewBox.scale] })] })), debugInfo.mowerPosition && (jsxs("div", { style: { marginBottom: '6px' }, children: [jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDE9C Mower Position:" }), jsxs("div", { children: ["Current: X=", debugInfo.mowerPosition.x.toFixed(2), ", Y=", debugInfo.mowerPosition.y.toFixed(2)] }), jsxs("div", { children: ["Theta: ", ((debugInfo.mowerPosition.theta * 180) / Math.PI).toFixed(1), "\u00B0"] }), jsxs("div", { children: ["Last: X=", debugInfo.mowerPosition.lastX.toFixed(2), ", Y=", debugInfo.mowerPosition.lastY.toFixed(2)] }), jsxs("div", { children: ["Last Theta: ", ((debugInfo.mowerPosition.lastTheta * 180) / Math.PI).toFixed(1), "\u00B0"] }), jsxs("div", { children: ["Status: ", debugInfo.mowerPosition.vehicleStateText, " (", debugInfo.mowerPosition.vehicleState, ")"] })] })), debugInfo.partitionData && (jsxs("div", { style: { marginBottom: '6px' }, children: [jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDD32 Mow Partition Data:" }), jsxs("div", { children: ["Type: ", debugInfo.partitionData.type || 'N/A'] }), debugInfo.partitionData.partitionIds &&
9840
+ debugInfo.partitionData.partitionIds.length > 0 ? (jsxs("div", { children: ["Active IDs: [", debugInfo.partitionData.partitionIds.join(', '), "]"] })) : (jsx("div", { children: "No active partitions" })), debugInfo.partitionData.time && (jsxs("div", { children: ["Updated: ", new Date(debugInfo.partitionData.time).toLocaleTimeString()] }))] })), !debugInfo.partitionData && (jsxs("div", { style: { marginBottom: '6px' }, children: [jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDD32 Mow Partition Data:" }), jsx("div", { style: { color: '#888' }, children: "No partition data available" })] }))] }));
9841
+ };
8931
9842
  // 错误显示
8932
9843
  if (currentError) {
8933
- return (jsx("div", { className: className, style: style, children: jsxs("div", { style: { color: 'red', padding: '10px' }, children: ["\u9519\u8BEF: ", currentError] }) }));
9844
+ return (jsxs("div", { className: className, style: style, children: [jsxs("div", { style: { color: 'red', padding: '10px' }, children: ["\u9519\u8BEF: ", currentError] }), jsx(DebugInfo, {})] }));
8934
9845
  }
8935
- // 使用goole maps自定义叠加层,不需要实际返回组件内容
8936
- return null;
9846
+ // 使用goole maps自定义叠加层,返回debug信息(如果启用)
9847
+ return debug ? jsx(DebugInfo, {}) : null;
8937
9848
  });
8938
9849
  MowerMapRenderer.displayName = 'MowerMapRenderer';
8939
9850