@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.
- package/dist/config/constants.d.ts +3 -25
- package/dist/config/constants.d.ts.map +1 -1
- package/dist/config/styles.d.ts +3 -2
- package/dist/config/styles.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +1995 -1084
- package/dist/index.js +1993 -1082
- package/dist/processor/MapDataProcessor.d.ts +1 -1
- package/dist/processor/MapDataProcessor.d.ts.map +1 -1
- package/dist/processor/PathDataProcessor.d.ts +3 -18
- package/dist/processor/PathDataProcessor.d.ts.map +1 -1
- package/dist/processor/builder/AntennaDataBuilder.d.ts +2 -1
- package/dist/processor/builder/AntennaDataBuilder.d.ts.map +1 -1
- package/dist/processor/builder/BoundaryDataBuilder.d.ts +1 -1
- package/dist/processor/builder/BoundaryDataBuilder.d.ts.map +1 -1
- package/dist/processor/builder/ChannelDataBuilder.d.ts +1 -1
- package/dist/processor/builder/ChannelDataBuilder.d.ts.map +1 -1
- package/dist/processor/builder/ChargingPileDataBuilder.d.ts +1 -1
- package/dist/processor/builder/ChargingPileDataBuilder.d.ts.map +1 -1
- package/dist/processor/builder/ObstacleDataBuilder.d.ts +2 -1
- package/dist/processor/builder/ObstacleDataBuilder.d.ts.map +1 -1
- package/dist/processor/builder/PathDataBuilder.d.ts +6 -5
- package/dist/processor/builder/PathDataBuilder.d.ts.map +1 -1
- package/dist/processor/builder/PointDataBuilder.d.ts +2 -1
- package/dist/processor/builder/PointDataBuilder.d.ts.map +1 -1
- package/dist/processor/builder/SvgElementDataBuilder.d.ts +1 -1
- package/dist/processor/builder/SvgElementDataBuilder.d.ts.map +1 -1
- package/dist/processor/builder/VisionOffDataBuilder.d.ts +2 -1
- package/dist/processor/builder/VisionOffDataBuilder.d.ts.map +1 -1
- package/dist/processor/index.d.ts +2 -1
- package/dist/processor/index.d.ts.map +1 -1
- package/dist/render/AntennaManager.d.ts +7 -24
- package/dist/render/AntennaManager.d.ts.map +1 -1
- package/dist/render/BoundaryLabelsManager.d.ts +12 -20
- package/dist/render/BoundaryLabelsManager.d.ts.map +1 -1
- package/dist/render/ChargingPileManager.d.ts +10 -20
- package/dist/render/ChargingPileManager.d.ts.map +1 -1
- package/dist/render/MowerMapOverlay.d.ts +53 -8
- package/dist/render/MowerMapOverlay.d.ts.map +1 -1
- package/dist/render/MowerMapRenderer.d.ts.map +1 -1
- package/dist/render/{MowerPostionManager.d.ts → MowerPositionManager.d.ts} +16 -11
- package/dist/render/MowerPositionManager.d.ts.map +1 -0
- package/dist/render/SvgMapView.d.ts +15 -26
- package/dist/render/SvgMapView.d.ts.map +1 -1
- package/dist/render/layers/BoundaryBorderLayer.d.ts +11 -0
- package/dist/render/layers/BoundaryBorderLayer.d.ts.map +1 -1
- package/dist/render/layers/ChannelLayer.d.ts.map +1 -1
- package/dist/render/layers/DrawLayer.d.ts +1 -1
- package/dist/render/layers/DrawLayer.d.ts.map +1 -1
- package/dist/render/layers/PathLayer.d.ts +11 -2
- package/dist/render/layers/PathLayer.d.ts.map +1 -1
- package/dist/render/layers/SvgElementLayer.d.ts.map +1 -1
- package/dist/render/layers/index.d.ts +0 -1
- package/dist/render/layers/index.d.ts.map +1 -1
- package/dist/render/layers/types.d.ts +1 -36
- package/dist/render/layers/types.d.ts.map +1 -1
- package/dist/store/processMowingState.d.ts +4 -0
- package/dist/store/processMowingState.d.ts.map +1 -0
- package/dist/store/useSubBoundaryBorderStore.d.ts +9 -5
- package/dist/store/useSubBoundaryBorderStore.d.ts.map +1 -1
- package/dist/types/constants.d.ts +38 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/layers.d.ts +50 -0
- package/dist/types/layers.d.ts.map +1 -0
- package/dist/types/processor.d.ts +26 -0
- package/dist/types/processor.d.ts.map +1 -0
- package/dist/types/realTime.d.ts +27 -2
- package/dist/types/realTime.d.ts.map +1 -1
- package/dist/types/renderer.d.ts +28 -10
- package/dist/types/renderer.d.ts.map +1 -1
- package/dist/types/store.d.ts +22 -0
- package/dist/types/store.d.ts.map +1 -0
- package/dist/types/utils.d.ts +102 -0
- package/dist/types/utils.d.ts.map +1 -0
- package/dist/utils/boundaryUtils.d.ts +1 -29
- package/dist/utils/boundaryUtils.d.ts.map +1 -1
- package/dist/utils/common.d.ts +20 -0
- package/dist/utils/common.d.ts.map +1 -0
- package/dist/utils/coordinates.d.ts +28 -7
- package/dist/utils/coordinates.d.ts.map +1 -1
- package/dist/utils/handleRealTime.d.ts +20 -10
- package/dist/utils/handleRealTime.d.ts.map +1 -1
- package/dist/utils/mapBounds.d.ts +1 -10
- package/dist/utils/mapBounds.d.ts.map +1 -1
- package/dist/utils/math.d.ts +3 -10
- package/dist/utils/math.d.ts.map +1 -1
- package/dist/utils/mower.d.ts +5 -5
- package/dist/utils/mower.d.ts.map +1 -1
- package/dist/utils/pathSegments.d.ts +1 -15
- package/dist/utils/pathSegments.d.ts.map +1 -1
- package/dist/utils/unionFind.d.ts +49 -0
- package/dist/utils/unionFind.d.ts.map +1 -0
- package/package.json +1 -1
- package/dist/render/MowerPostionManager.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,136 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var React = require('react');
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* 常量和枚举类型定义
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 机器人状态枚举
|
|
11
|
+
*/
|
|
12
|
+
var RobotStatus;
|
|
13
|
+
(function (RobotStatus) {
|
|
14
|
+
RobotStatus[RobotStatus["PARKED"] = 1] = "PARKED";
|
|
15
|
+
RobotStatus[RobotStatus["CHARGING"] = 2] = "CHARGING";
|
|
16
|
+
RobotStatus[RobotStatus["STANDBY"] = 3] = "STANDBY";
|
|
17
|
+
RobotStatus[RobotStatus["MOWING"] = 4] = "MOWING";
|
|
18
|
+
RobotStatus[RobotStatus["WORKING"] = 5] = "WORKING";
|
|
19
|
+
RobotStatus[RobotStatus["MAPPING"] = 6] = "MAPPING";
|
|
20
|
+
RobotStatus[RobotStatus["ERROR"] = 7] = "ERROR";
|
|
21
|
+
RobotStatus[RobotStatus["UPGRADING"] = 8] = "UPGRADING";
|
|
22
|
+
RobotStatus[RobotStatus["DISCONNECTED"] = 9] = "DISCONNECTED";
|
|
23
|
+
RobotStatus[RobotStatus["UNKNOWN"] = -1] = "UNKNOWN";
|
|
24
|
+
RobotStatus[RobotStatus["TASK_DELAY"] = 10] = "TASK_DELAY";
|
|
25
|
+
})(RobotStatus || (RobotStatus = {}));
|
|
26
|
+
/**
|
|
27
|
+
* RTK状态枚举
|
|
28
|
+
*/
|
|
29
|
+
var RTK_STATE;
|
|
30
|
+
(function (RTK_STATE) {
|
|
31
|
+
RTK_STATE[RTK_STATE["LOW_RTK"] = 1] = "LOW_RTK";
|
|
32
|
+
RTK_STATE[RTK_STATE["MIDDLE_RTK"] = 2] = "MIDDLE_RTK";
|
|
33
|
+
RTK_STATE[RTK_STATE["HIGH_RTK"] = 3] = "HIGH_RTK";
|
|
34
|
+
RTK_STATE[RTK_STATE["NO_POSTURE"] = 10] = "NO_POSTURE";
|
|
35
|
+
RTK_STATE[RTK_STATE["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
|
|
36
|
+
RTK_STATE[RTK_STATE["OFF_LINE"] = 19] = "OFF_LINE";
|
|
37
|
+
})(RTK_STATE || (RTK_STATE = {}));
|
|
38
|
+
/**
|
|
39
|
+
* 实时数据类型枚举
|
|
40
|
+
*/
|
|
41
|
+
var REAL_TIME_DATA_TYPE;
|
|
42
|
+
(function (REAL_TIME_DATA_TYPE) {
|
|
43
|
+
REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["LOCATION"] = 1] = "LOCATION";
|
|
44
|
+
REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["PROCESS"] = 2] = "PROCESS";
|
|
45
|
+
})(REAL_TIME_DATA_TYPE || (REAL_TIME_DATA_TYPE = {}));
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 地图渲染相关常量配置
|
|
49
|
+
*/
|
|
50
|
+
/**
|
|
51
|
+
* 缩放因子 - 将米转换为像素
|
|
52
|
+
* 与Python代码中的SVG比例一致
|
|
53
|
+
*/
|
|
54
|
+
const SCALE_FACTOR = 50; // 50像素/米
|
|
55
|
+
/**
|
|
56
|
+
* 默认线宽设置
|
|
57
|
+
*/
|
|
58
|
+
const DEFAULT_LINE_WIDTHS = {
|
|
59
|
+
OBSTACLE: 2,
|
|
60
|
+
CHARGING_PILE: 2,
|
|
61
|
+
CHANNEL: 2,
|
|
62
|
+
PATH: 20,
|
|
63
|
+
VISION_OFF_AREA: 2,
|
|
64
|
+
TIME_LIMIT_OBSTACLE: 1,
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* 默认透明度设置
|
|
68
|
+
*/
|
|
69
|
+
const DEFAULT_OPACITIES = {
|
|
70
|
+
FULL: 1.0,
|
|
71
|
+
HIGH: 0.7,
|
|
72
|
+
MEDIUM: 0.6,
|
|
73
|
+
DOODLE: 0.8};
|
|
74
|
+
/**
|
|
75
|
+
* 默认半径设置
|
|
76
|
+
*/
|
|
77
|
+
const DEFAULT_RADII = {
|
|
78
|
+
CHARGING_PILE: 12};
|
|
79
|
+
/**
|
|
80
|
+
* 图层等级
|
|
81
|
+
*/
|
|
82
|
+
const LAYER_LEVELS = {
|
|
83
|
+
BOUNDARY: 2,
|
|
84
|
+
BOUNDARY_BORDER: 4};
|
|
85
|
+
/**
|
|
86
|
+
* 图层默认id
|
|
87
|
+
*/
|
|
88
|
+
const LAYER_DEFAULT_TYPE = {
|
|
89
|
+
CHANNEL: 'channel',
|
|
90
|
+
BOUNDARY: 'boundary',
|
|
91
|
+
PATH: 'path',
|
|
92
|
+
BOUNDARY_BORDER: 'boundary_border',
|
|
93
|
+
OBSTACLE: 'obstacle',
|
|
94
|
+
CHARGING_PILE: 'charging_pile',
|
|
95
|
+
POINT: 'point',
|
|
96
|
+
SVG: 'svg',
|
|
97
|
+
VISION_OFF_AREA: 'vision_off_area',
|
|
98
|
+
ANTENNA: 'antenna',
|
|
99
|
+
};
|
|
100
|
+
const ISOLATED_BOUNDARY_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
101
|
+
<g opacity="0.6">
|
|
102
|
+
<rect width="24" height="24" rx="12" fill="#1E1E1F" fill-opacity="0.5"/>
|
|
103
|
+
<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"/>
|
|
104
|
+
<mask id="path-3-outside-1_9822_43516" maskUnits="userSpaceOnUse" x="13.2344" y="7.20545" width="12.0208" height="12.0208" fill="black">
|
|
105
|
+
<rect fill="white" x="13.2344" y="7.20545" width="12.0208" height="12.0208"/>
|
|
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"/>
|
|
107
|
+
</mask>
|
|
108
|
+
<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"/>
|
|
109
|
+
<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)"/>
|
|
110
|
+
<mask id="path-5-outside-2_9822_43516" maskUnits="userSpaceOnUse" x="9.22265" y="10.388" width="12.7279" height="12.7279" fill="black">
|
|
111
|
+
<rect fill="white" x="9.22265" y="10.388" width="12.7279" height="12.7279"/>
|
|
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"/>
|
|
113
|
+
</mask>
|
|
114
|
+
<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"/>
|
|
115
|
+
<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)"/>
|
|
116
|
+
<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"/>
|
|
117
|
+
<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"/>
|
|
118
|
+
<rect width="1.91578" height="4.24403" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 19.6992 13.8066)" fill="#8E8E8F"/>
|
|
119
|
+
<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"/>
|
|
120
|
+
</g>
|
|
121
|
+
</svg>`;
|
|
122
|
+
/**
|
|
123
|
+
* 遍历割草任务,下述四个字段可以在路径中唯一确定遍历的位置(当前区域、当前块、当前行、在当前行上的路程)
|
|
124
|
+
*/
|
|
125
|
+
const ACTION_BLOCK_COVER = 5;
|
|
126
|
+
/**
|
|
127
|
+
* 遍历割草块转移任务(在同一区域中的一个块转移到下一个块),下述四个字段可以在路径中唯一确定转移的位置(当前区域、前置块、当前转移路径线序号、在当前线上的路程)
|
|
128
|
+
*/
|
|
129
|
+
const ACTION_BLOCK_TRANSFER = 6;
|
|
130
|
+
/**
|
|
131
|
+
* 边界割草任务(割草任务内部的巡边任务)
|
|
132
|
+
*/
|
|
133
|
+
const ACTION_BOUNDARY_TASK = 8;
|
|
134
|
+
const SVG_MAP_VIEW_ID = 'fleet-maps-svg-map-view';
|
|
135
|
+
|
|
6
136
|
/**
|
|
7
137
|
* SVG基础MapView
|
|
8
138
|
* 使用真正的矢量SVG渲染替代Canvas位图渲染
|
|
@@ -16,7 +146,6 @@ class SvgMapView {
|
|
|
16
146
|
this.lineScale = 1; // 线条缩放系数
|
|
17
147
|
// 状态标志
|
|
18
148
|
this.destroyed = false;
|
|
19
|
-
this.showScale = false;
|
|
20
149
|
// 渲染系统 - 移除节流以确保用户操作的实时响应
|
|
21
150
|
// 拖动功能
|
|
22
151
|
this.isDragging = false;
|
|
@@ -59,6 +188,7 @@ class SvgMapView {
|
|
|
59
188
|
this.svg.setAttribute('shape-rendering', 'geometricPrecision');
|
|
60
189
|
this.svg.setAttribute('text-rendering', 'geometricPrecision');
|
|
61
190
|
this.svg.setAttribute('image-rendering', 'optimizeQuality');
|
|
191
|
+
this.svg.setAttribute('id', SVG_MAP_VIEW_ID);
|
|
62
192
|
}
|
|
63
193
|
/**
|
|
64
194
|
* 创建SVG组元素
|
|
@@ -81,7 +211,9 @@ class SvgMapView {
|
|
|
81
211
|
return;
|
|
82
212
|
this.layers.push(...layers);
|
|
83
213
|
this.layers.sort((a, b) => a.getLevel() - b.getLevel());
|
|
84
|
-
|
|
214
|
+
}
|
|
215
|
+
getLayer(type) {
|
|
216
|
+
return this.layers.find((layer) => layer.getType() === type) || null;
|
|
85
217
|
}
|
|
86
218
|
/**
|
|
87
219
|
* 添加图层
|
|
@@ -91,14 +223,12 @@ class SvgMapView {
|
|
|
91
223
|
return;
|
|
92
224
|
this.layers.push(layer);
|
|
93
225
|
this.layers.sort((a, b) => a.getLevel() - b.getLevel());
|
|
94
|
-
this.refresh();
|
|
95
226
|
}
|
|
96
227
|
/**
|
|
97
228
|
* 移除图层
|
|
98
229
|
*/
|
|
99
230
|
removeLayer(layer) {
|
|
100
231
|
const index = this.layers.indexOf(layer);
|
|
101
|
-
console.log('removeLayer----->', index);
|
|
102
232
|
if (index !== -1) {
|
|
103
233
|
this.layers.splice(index, 1);
|
|
104
234
|
this.refresh();
|
|
@@ -109,15 +239,18 @@ class SvgMapView {
|
|
|
109
239
|
* @param type 图层类型
|
|
110
240
|
*/
|
|
111
241
|
removeLayerByType(type) {
|
|
112
|
-
|
|
113
|
-
|
|
242
|
+
const layer = this.layers.find((layer) => layer.getType() === type);
|
|
243
|
+
if (layer) {
|
|
244
|
+
this.clearLayersGroup(layer);
|
|
245
|
+
this.layers = this.layers.filter((cLayer) => cLayer.getType() !== type);
|
|
246
|
+
}
|
|
114
247
|
}
|
|
115
248
|
// ==================== 变换系统 ====================
|
|
116
249
|
/**
|
|
117
250
|
* 设置自适应视图变换 - 让SVG刚好包裹住图形
|
|
118
251
|
*/
|
|
119
252
|
fitToView(bounds) {
|
|
120
|
-
const padding =
|
|
253
|
+
const padding = 20; // 添加一些边距以避免内容贴边
|
|
121
254
|
const boundWidth = bounds.maxX - bounds.minX;
|
|
122
255
|
const boundHeight = bounds.maxY - bounds.minY;
|
|
123
256
|
// 防止宽高为0的情况
|
|
@@ -140,8 +273,8 @@ class SvgMapView {
|
|
|
140
273
|
this.viewBox = {
|
|
141
274
|
x: bounds.minX - padding,
|
|
142
275
|
y: bounds.minY - padding,
|
|
143
|
-
width: boundWidth + padding,
|
|
144
|
-
height: boundHeight + padding,
|
|
276
|
+
width: boundWidth + padding * 2,
|
|
277
|
+
height: boundHeight + padding * 2,
|
|
145
278
|
};
|
|
146
279
|
// 根据宽高比选择合适的preserveAspectRatio设置
|
|
147
280
|
if (Math.abs(contentAspectRatio - containerAspectRatio) < 0.01) {
|
|
@@ -153,42 +286,6 @@ class SvgMapView {
|
|
|
153
286
|
this.svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
|
|
154
287
|
}
|
|
155
288
|
this.updateViewBox();
|
|
156
|
-
this.refresh();
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* 重置变换
|
|
160
|
-
*/
|
|
161
|
-
resetTransform() {
|
|
162
|
-
this.scale = 1;
|
|
163
|
-
// 重置viewBox到默认状态
|
|
164
|
-
const containerRect = this.container.getBoundingClientRect();
|
|
165
|
-
this.viewBox = {
|
|
166
|
-
x: 0,
|
|
167
|
-
y: 0,
|
|
168
|
-
width: containerRect.width,
|
|
169
|
-
height: containerRect.height,
|
|
170
|
-
};
|
|
171
|
-
this.updateViewBox();
|
|
172
|
-
this.refresh();
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* 设置缩放级别
|
|
176
|
-
*/
|
|
177
|
-
setZoom(zoomLevel) {
|
|
178
|
-
if (zoomLevel <= 0)
|
|
179
|
-
return;
|
|
180
|
-
const oldScale = this.scale;
|
|
181
|
-
this.scale = zoomLevel;
|
|
182
|
-
// 调整viewBox以实现缩放
|
|
183
|
-
const scaleFactor = oldScale / this.scale;
|
|
184
|
-
const centerX = this.viewBox.x + this.viewBox.width / 2;
|
|
185
|
-
const centerY = this.viewBox.y + this.viewBox.height / 2;
|
|
186
|
-
this.viewBox.width *= scaleFactor;
|
|
187
|
-
this.viewBox.height *= scaleFactor;
|
|
188
|
-
this.viewBox.x = centerX - this.viewBox.width / 2;
|
|
189
|
-
this.viewBox.y = centerY - this.viewBox.height / 2;
|
|
190
|
-
this.updateViewBox();
|
|
191
|
-
this.refresh();
|
|
192
289
|
}
|
|
193
290
|
/**
|
|
194
291
|
* 获取当前缩放级别
|
|
@@ -196,21 +293,21 @@ class SvgMapView {
|
|
|
196
293
|
getZoom() {
|
|
197
294
|
return this.scale;
|
|
198
295
|
}
|
|
199
|
-
/**
|
|
200
|
-
* 设置线条缩放系数
|
|
201
|
-
*/
|
|
202
|
-
setLineScale(lineScale) {
|
|
203
|
-
if (lineScale <= 0)
|
|
204
|
-
return;
|
|
205
|
-
this.lineScale = lineScale;
|
|
206
|
-
this.refresh();
|
|
207
|
-
}
|
|
208
296
|
/**
|
|
209
297
|
* 获取当前线条缩放系数
|
|
210
298
|
*/
|
|
211
299
|
getLineScale() {
|
|
212
300
|
return this.lineScale;
|
|
213
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* 绘制特定的图层
|
|
304
|
+
*/
|
|
305
|
+
renderLayer(type) {
|
|
306
|
+
const layer = this.layers.find((layer) => layer.getType() === type);
|
|
307
|
+
// 清空图层组
|
|
308
|
+
this.clearLayersGroup(layer);
|
|
309
|
+
this.onDrawLayers(type);
|
|
310
|
+
}
|
|
214
311
|
// ==================== 渲染系统 ====================
|
|
215
312
|
/**
|
|
216
313
|
* 主渲染方法
|
|
@@ -221,25 +318,112 @@ class SvgMapView {
|
|
|
221
318
|
// 绘制所有图层
|
|
222
319
|
this.onDrawLayers();
|
|
223
320
|
}
|
|
321
|
+
/**
|
|
322
|
+
* 获取图层id
|
|
323
|
+
*/
|
|
324
|
+
getLayerId(layer) {
|
|
325
|
+
return `layer-${layer.getType()}-${layer.getLevel()}`;
|
|
326
|
+
}
|
|
224
327
|
/**
|
|
225
328
|
* 清空图层组
|
|
226
329
|
*/
|
|
227
|
-
clearLayersGroup() {
|
|
228
|
-
|
|
229
|
-
this.
|
|
330
|
+
clearLayersGroup(layer) {
|
|
331
|
+
if (layer) {
|
|
332
|
+
const layerId = this.getLayerId(layer);
|
|
333
|
+
const layerGroup = this.layersGroup.querySelector(`#${layerId}`);
|
|
334
|
+
if (layerGroup) {
|
|
335
|
+
this.layersGroup.removeChild(layerGroup);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
while (this.layersGroup.firstChild) {
|
|
340
|
+
this.layersGroup.removeChild(this.layersGroup.firstChild);
|
|
341
|
+
}
|
|
230
342
|
}
|
|
231
343
|
}
|
|
232
344
|
/**
|
|
233
|
-
*
|
|
345
|
+
* 获取图层的下一个兄弟元素
|
|
346
|
+
* 根据图层的level来获取
|
|
234
347
|
*/
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
348
|
+
getNextSibling(layer) {
|
|
349
|
+
const nextLayer = this.layers.find((cLayer) => cLayer.getLevel() > layer.getLevel());
|
|
350
|
+
const id = `layer-${nextLayer?.getType()}-${nextLayer?.getLevel()}`;
|
|
351
|
+
const nextSibling = this.layersGroup.querySelector(`#${id}`);
|
|
352
|
+
return nextSibling;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* 绘制图层,不传参数则默认绘制所有图层
|
|
356
|
+
*/
|
|
357
|
+
onDrawLayers(type) {
|
|
358
|
+
if (type) {
|
|
359
|
+
const layer = this.layers.find((layer) => layer.getType() === type);
|
|
360
|
+
if (layer) {
|
|
361
|
+
const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
|
|
239
362
|
layer.drawSVG(layerGroup, this.scale, this.lineScale);
|
|
240
|
-
this.
|
|
363
|
+
const nextSibling = this.getNextSibling(layer);
|
|
364
|
+
if (nextSibling) {
|
|
365
|
+
this.layersGroup.insertBefore(layerGroup, nextSibling);
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
this.layersGroup.appendChild(layerGroup);
|
|
369
|
+
}
|
|
241
370
|
}
|
|
242
371
|
}
|
|
372
|
+
else {
|
|
373
|
+
for (const layer of this.layers) {
|
|
374
|
+
if (layer.isVisible()) {
|
|
375
|
+
const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
|
|
376
|
+
layer.drawSVG(layerGroup, this.scale, this.lineScale);
|
|
377
|
+
this.layersGroup.appendChild(layerGroup);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
// if (type) {
|
|
382
|
+
// const layer = this.layers.find((layer) => layer.getType() === type);
|
|
383
|
+
// const layerId = this.getLayerId(layer);
|
|
384
|
+
// // 记录原始位置信息
|
|
385
|
+
// const layerGroup = this.layersGroup.querySelector(`#${layerId}`);
|
|
386
|
+
// let nextSibling: Node | null = null;
|
|
387
|
+
// if (layerGroup) {
|
|
388
|
+
// // 记录被删除元素的下一个兄弟元素,用于确定插入位置
|
|
389
|
+
// nextSibling = layerGroup.nextSibling;
|
|
390
|
+
// // 删除旧的图层组
|
|
391
|
+
// this.layersGroup.removeChild(layerGroup);
|
|
392
|
+
// // 从layerId解析出图层类型和层级
|
|
393
|
+
// // layerId格式: layer-${type}-${level}
|
|
394
|
+
// const layerIdParts = layerId.split('-');
|
|
395
|
+
// if (layerIdParts.length >= 3) {
|
|
396
|
+
// const layerType = layerIdParts.slice(1, -1).join('-'); // 处理类型名中可能包含连字符的情况
|
|
397
|
+
// const layerLevel = parseInt(layerIdParts[layerIdParts.length - 1]);
|
|
398
|
+
// // 查找对应的图层对象
|
|
399
|
+
// const targetLayer = this.layers.find(layer =>
|
|
400
|
+
// layer.getType() === layerType && layer.getLevel() === layerLevel
|
|
401
|
+
// );
|
|
402
|
+
// if (targetLayer && targetLayer.isVisible()) {
|
|
403
|
+
// // 创建新的图层组
|
|
404
|
+
// const newLayerGroup = this.createSVGGroup(layerId);
|
|
405
|
+
// // 重新绘制图层
|
|
406
|
+
// targetLayer.drawSVG(newLayerGroup, this.scale, this.lineScale);
|
|
407
|
+
// // 在原始位置插入新元素
|
|
408
|
+
// if (nextSibling) {
|
|
409
|
+
// this.layersGroup.insertBefore(newLayerGroup, nextSibling);
|
|
410
|
+
// } else {
|
|
411
|
+
// // 如果没有下一个兄弟元素,说明原来是最后一个,直接appendChild
|
|
412
|
+
// this.layersGroup.appendChild(newLayerGroup);
|
|
413
|
+
// }
|
|
414
|
+
// }
|
|
415
|
+
// }
|
|
416
|
+
// }
|
|
417
|
+
// } else {
|
|
418
|
+
// // 重绘所有图层
|
|
419
|
+
// for (const layer of this.layers) {
|
|
420
|
+
// if (layer.isVisible()) {
|
|
421
|
+
// const layerGroup = this.createSVGGroup(`layer-${layer.getType()}-${layer.getLevel()}`);
|
|
422
|
+
// layer.drawSVG(layerGroup, this.scale, this.lineScale);
|
|
423
|
+
// this.layersGroup.appendChild(layerGroup);
|
|
424
|
+
// }
|
|
425
|
+
// }
|
|
426
|
+
// }
|
|
243
427
|
}
|
|
244
428
|
/**
|
|
245
429
|
* 刷新渲染
|
|
@@ -249,12 +433,6 @@ class SvgMapView {
|
|
|
249
433
|
return;
|
|
250
434
|
this.render();
|
|
251
435
|
}
|
|
252
|
-
/**
|
|
253
|
-
* 重新初始化SVG(用于容器大小变化)
|
|
254
|
-
*/
|
|
255
|
-
reinitializeSVG() {
|
|
256
|
-
this.refresh();
|
|
257
|
-
}
|
|
258
436
|
// ==================== 拖拽功能 ====================
|
|
259
437
|
/**
|
|
260
438
|
* 设置拖拽事件处理器
|
|
@@ -379,22 +557,6 @@ class SvgMapView {
|
|
|
379
557
|
return null;
|
|
380
558
|
}
|
|
381
559
|
}
|
|
382
|
-
/**
|
|
383
|
-
* 自动适配viewBox到实际内容
|
|
384
|
-
*/
|
|
385
|
-
autoFitToContent() {
|
|
386
|
-
if (this.destroyed || this.layers.length === 0)
|
|
387
|
-
return;
|
|
388
|
-
const bounds = this.getLayersGroupBounds();
|
|
389
|
-
if (bounds) {
|
|
390
|
-
this.fitToView({
|
|
391
|
-
minX: bounds.x,
|
|
392
|
-
minY: bounds.y,
|
|
393
|
-
maxX: bounds.x + bounds.width,
|
|
394
|
-
maxY: bounds.y + bounds.height,
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
560
|
/**
|
|
399
561
|
* 获取ViewBox信息
|
|
400
562
|
*/
|
|
@@ -415,13 +577,6 @@ class SvgMapView {
|
|
|
415
577
|
* 诊断SVG尺寸信息
|
|
416
578
|
*/
|
|
417
579
|
diagnosticSizeInfo() { }
|
|
418
|
-
/**
|
|
419
|
-
* 设置是否显示比例尺
|
|
420
|
-
*/
|
|
421
|
-
setShowScale(show) {
|
|
422
|
-
this.showScale = show;
|
|
423
|
-
this.refresh();
|
|
424
|
-
}
|
|
425
580
|
/**
|
|
426
581
|
* 获取SVG元素
|
|
427
582
|
*/
|
|
@@ -623,8 +778,6 @@ const create = (createState) => createState ? createImpl(createState) : createIm
|
|
|
623
778
|
|
|
624
779
|
const useSubBoundaryBorderStore = create((set, get) => ({
|
|
625
780
|
subBoundaryBorder: {},
|
|
626
|
-
// 覆盖所有数据
|
|
627
|
-
setSubBoundaryBorder: (subBoundaryBorder) => set({ subBoundaryBorder }),
|
|
628
781
|
// 追加单个数据
|
|
629
782
|
addSubBoundaryBorder: (key, element) => set((state) => ({
|
|
630
783
|
subBoundaryBorder: {
|
|
@@ -632,134 +785,28 @@ const useSubBoundaryBorderStore = create((set, get) => ({
|
|
|
632
785
|
[key]: element,
|
|
633
786
|
},
|
|
634
787
|
})),
|
|
635
|
-
// 追加多个数据
|
|
636
|
-
addMultipleSubBoundaryBorders: (borders) => set((state) => ({
|
|
637
|
-
subBoundaryBorder: {
|
|
638
|
-
...state.subBoundaryBorder,
|
|
639
|
-
...borders,
|
|
640
|
-
},
|
|
641
|
-
})),
|
|
642
788
|
// 清空所有数据
|
|
643
789
|
clearSubBoundaryBorder: () => set({ subBoundaryBorder: {} }),
|
|
790
|
+
// 障碍物
|
|
791
|
+
obstacles: {},
|
|
792
|
+
addObstacles: (key, element) => set((state) => ({
|
|
793
|
+
obstacles: {
|
|
794
|
+
...state.obstacles,
|
|
795
|
+
[key]: element,
|
|
796
|
+
},
|
|
797
|
+
})),
|
|
798
|
+
clearObstacles: () => set({ obstacles: {} }),
|
|
799
|
+
// svg数据
|
|
800
|
+
svgElements: {},
|
|
801
|
+
addSvgElements: (key, element) => set((state) => ({
|
|
802
|
+
svgElements: {
|
|
803
|
+
...state.svgElements,
|
|
804
|
+
[key]: element,
|
|
805
|
+
},
|
|
806
|
+
})),
|
|
807
|
+
clearSvgElements: () => set({ svgElements: {} }),
|
|
644
808
|
}));
|
|
645
809
|
|
|
646
|
-
/**
|
|
647
|
-
* 地图渲染相关常量配置
|
|
648
|
-
*/
|
|
649
|
-
/**
|
|
650
|
-
* 缩放因子 - 将米转换为像素
|
|
651
|
-
* 与Python代码中的SVG比例一致
|
|
652
|
-
*/
|
|
653
|
-
const SCALE_FACTOR = 50; // 50像素/米
|
|
654
|
-
/**
|
|
655
|
-
* 默认线宽设置
|
|
656
|
-
*/
|
|
657
|
-
const DEFAULT_LINE_WIDTHS = {
|
|
658
|
-
OBSTACLE: 2,
|
|
659
|
-
CHARGING_PILE: 2,
|
|
660
|
-
CHANNEL: 2,
|
|
661
|
-
PATH: 20,
|
|
662
|
-
VISION_OFF_AREA: 2,
|
|
663
|
-
TIME_LIMIT_OBSTACLE: 1,
|
|
664
|
-
};
|
|
665
|
-
/**
|
|
666
|
-
* 默认透明度设置
|
|
667
|
-
*/
|
|
668
|
-
const DEFAULT_OPACITIES = {
|
|
669
|
-
FULL: 1.0,
|
|
670
|
-
HIGH: 0.7,
|
|
671
|
-
LOW: 0.4};
|
|
672
|
-
/**
|
|
673
|
-
* 默认半径设置
|
|
674
|
-
*/
|
|
675
|
-
const DEFAULT_RADII = {
|
|
676
|
-
CHARGING_PILE: 12};
|
|
677
|
-
/**
|
|
678
|
-
* 图层等级
|
|
679
|
-
*/
|
|
680
|
-
const LAYER_LEVELS = {
|
|
681
|
-
BOUNDARY: 2,
|
|
682
|
-
BOUNDARY_BORDER: 4};
|
|
683
|
-
/**
|
|
684
|
-
* 图层默认id
|
|
685
|
-
*/
|
|
686
|
-
const LAYER_DEFAULT_TYPE = {
|
|
687
|
-
CHANNEL: 'channel',
|
|
688
|
-
BOUNDARY: 'boundary',
|
|
689
|
-
PATH: 'path',
|
|
690
|
-
BOUNDARY_BORDER: 'boundary_border',
|
|
691
|
-
OBSTACLE: 'obstacle',
|
|
692
|
-
CHARGING_PILE: 'charging_pile',
|
|
693
|
-
POINT: 'point',
|
|
694
|
-
SVG: 'svg',
|
|
695
|
-
VISION_OFF_AREA: 'vision_off_area',
|
|
696
|
-
ANTENNA: 'antenna',
|
|
697
|
-
};
|
|
698
|
-
var RobotStatus;
|
|
699
|
-
(function (RobotStatus) {
|
|
700
|
-
RobotStatus[RobotStatus["PARKED"] = 1] = "PARKED";
|
|
701
|
-
RobotStatus[RobotStatus["CHARGING"] = 2] = "CHARGING";
|
|
702
|
-
RobotStatus[RobotStatus["STANDBY"] = 3] = "STANDBY";
|
|
703
|
-
RobotStatus[RobotStatus["MOWING"] = 4] = "MOWING";
|
|
704
|
-
RobotStatus[RobotStatus["WORKING"] = 5] = "WORKING";
|
|
705
|
-
RobotStatus[RobotStatus["MAPPING"] = 6] = "MAPPING";
|
|
706
|
-
RobotStatus[RobotStatus["ERROR"] = 7] = "ERROR";
|
|
707
|
-
RobotStatus[RobotStatus["UPGRADING"] = 8] = "UPGRADING";
|
|
708
|
-
RobotStatus[RobotStatus["DISCONNECTED"] = 9] = "DISCONNECTED";
|
|
709
|
-
RobotStatus[RobotStatus["UNKNOWN"] = -1] = "UNKNOWN";
|
|
710
|
-
RobotStatus[RobotStatus["TASK_DELAY"] = 10] = "TASK_DELAY";
|
|
711
|
-
// WAITING = 'Waiting',
|
|
712
|
-
})(RobotStatus || (RobotStatus = {}));
|
|
713
|
-
// RTK状态
|
|
714
|
-
var RTK_STATE;
|
|
715
|
-
(function (RTK_STATE) {
|
|
716
|
-
RTK_STATE[RTK_STATE["LOW_RTK"] = 1] = "LOW_RTK";
|
|
717
|
-
RTK_STATE[RTK_STATE["MIDDLE_RTK"] = 2] = "MIDDLE_RTK";
|
|
718
|
-
RTK_STATE[RTK_STATE["HIGH_RTK"] = 3] = "HIGH_RTK";
|
|
719
|
-
RTK_STATE[RTK_STATE["NO_POSTURE"] = 10] = "NO_POSTURE";
|
|
720
|
-
RTK_STATE[RTK_STATE["OUT_OF_RANGE"] = 11] = "OUT_OF_RANGE";
|
|
721
|
-
RTK_STATE[RTK_STATE["OFF_LINE"] = 19] = "OFF_LINE";
|
|
722
|
-
})(RTK_STATE || (RTK_STATE = {}));
|
|
723
|
-
var REAL_TIME_DATA_TYPE;
|
|
724
|
-
(function (REAL_TIME_DATA_TYPE) {
|
|
725
|
-
REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["LOCATION"] = 1] = "LOCATION";
|
|
726
|
-
REAL_TIME_DATA_TYPE[REAL_TIME_DATA_TYPE["PROCESS"] = 2] = "PROCESS";
|
|
727
|
-
})(REAL_TIME_DATA_TYPE || (REAL_TIME_DATA_TYPE = {}));
|
|
728
|
-
const ISOLATED_BOUNDARY_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
729
|
-
<g opacity="0.6">
|
|
730
|
-
<rect width="24" height="24" rx="12" fill="#1E1E1F" fill-opacity="0.5"/>
|
|
731
|
-
<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"/>
|
|
732
|
-
<mask id="path-3-outside-1_9822_43516" maskUnits="userSpaceOnUse" x="13.2344" y="7.20545" width="12.0208" height="12.0208" fill="black">
|
|
733
|
-
<rect fill="white" x="13.2344" y="7.20545" width="12.0208" height="12.0208"/>
|
|
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"/>
|
|
735
|
-
</mask>
|
|
736
|
-
<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"/>
|
|
737
|
-
<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)"/>
|
|
738
|
-
<mask id="path-5-outside-2_9822_43516" maskUnits="userSpaceOnUse" x="9.22265" y="10.388" width="12.7279" height="12.7279" fill="black">
|
|
739
|
-
<rect fill="white" x="9.22265" y="10.388" width="12.7279" height="12.7279"/>
|
|
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"/>
|
|
741
|
-
</mask>
|
|
742
|
-
<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"/>
|
|
743
|
-
<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)"/>
|
|
744
|
-
<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"/>
|
|
745
|
-
<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"/>
|
|
746
|
-
<rect width="1.91578" height="4.24403" transform="matrix(-0.707107 -0.707107 -0.707107 0.707107 19.6992 13.8066)" fill="#8E8E8F"/>
|
|
747
|
-
<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"/>
|
|
748
|
-
</g>
|
|
749
|
-
</svg>`;
|
|
750
|
-
/**
|
|
751
|
-
* 遍历割草任务,下述四个字段可以在路径中唯一确定遍历的位置(当前区域、当前块、当前行、在当前行上的路程)
|
|
752
|
-
*/
|
|
753
|
-
const ACTION_BLOCK_COVER = 5;
|
|
754
|
-
/**
|
|
755
|
-
* 遍历割草块转移任务(在同一区域中的一个块转移到下一个块),下述四个字段可以在路径中唯一确定转移的位置(当前区域、前置块、当前转移路径线序号、在当前线上的路程)
|
|
756
|
-
*/
|
|
757
|
-
const ACTION_BLOCK_TRANSFER = 6;
|
|
758
|
-
/**
|
|
759
|
-
* 边界割草任务(割草任务内部的巡边任务)
|
|
760
|
-
*/
|
|
761
|
-
const ACTION_BOUNDARY_TASK = 8;
|
|
762
|
-
|
|
763
810
|
/**
|
|
764
811
|
* 路径图层
|
|
765
812
|
* 专门处理路径元素的渲染
|
|
@@ -810,51 +857,90 @@ class ChannelLayer extends BaseLayer {
|
|
|
810
857
|
}
|
|
811
858
|
// �� 修改:计算包含所有分区和通道的边界框
|
|
812
859
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
813
|
-
// 1.
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
860
|
+
// 1. 先计算所有分区的边界,如果能拿到边界的svg的大小,就使用这个如果拿不到,就根据分区去计算
|
|
861
|
+
const svg = document.getElementById(SVG_MAP_VIEW_ID);
|
|
862
|
+
if (svg && svg instanceof SVGSVGElement && svg.viewBox) {
|
|
863
|
+
const viewBox = svg.viewBox.baseVal;
|
|
864
|
+
minX = viewBox.x;
|
|
865
|
+
minY = viewBox.y;
|
|
866
|
+
maxX = viewBox.x + viewBox.width;
|
|
867
|
+
maxY = viewBox.y + viewBox.height;
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
for (const partitionId in subBoundaryBorder) {
|
|
871
|
+
const boundaryData = subBoundaryBorder[partitionId];
|
|
872
|
+
if (boundaryData && boundaryData.coordinates && boundaryData.coordinates.length > 0) {
|
|
873
|
+
for (const coord of boundaryData.coordinates) {
|
|
874
|
+
minX = Math.min(minX, coord[0]);
|
|
875
|
+
minY = Math.min(minY, coord[1]);
|
|
876
|
+
maxX = Math.max(maxX, coord[0]);
|
|
877
|
+
maxY = Math.max(maxY, coord[1]);
|
|
878
|
+
}
|
|
822
879
|
}
|
|
823
880
|
}
|
|
824
881
|
}
|
|
825
882
|
// 2. 再计算所有通道的边界
|
|
826
883
|
for (const element of this.elements) {
|
|
827
|
-
const tunnelConnection = element.originalData?.connection;
|
|
828
|
-
if (tunnelConnection && Array.isArray(tunnelConnection)) {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
884
|
+
// const tunnelConnection = element.originalData?.connection;
|
|
885
|
+
// if (tunnelConnection && Array.isArray(tunnelConnection)) {
|
|
886
|
+
// const clipPathId = `channel-exclude-${
|
|
887
|
+
// element.originalData?.id || Math.random().toString(36).substr(2, 9)
|
|
888
|
+
// }`;
|
|
889
|
+
// // 检查是否已存在该 clipPath
|
|
890
|
+
// const existingClipPath = defs.querySelector(`#${clipPathId}`);
|
|
891
|
+
// if (existingClipPath) continue;
|
|
892
|
+
// // 创建 clipPath
|
|
893
|
+
// const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
|
|
894
|
+
// clipPath.setAttribute('id', clipPathId);
|
|
895
|
+
// clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
|
|
896
|
+
// // === 合成一个 path ===
|
|
897
|
+
// let d = `M ${minX} ${minY} L ${maxX} ${minY} L ${maxX} ${maxY} L ${minX} ${maxY} Z`;
|
|
898
|
+
// for (const partitionId of tunnelConnection) {
|
|
899
|
+
// const boundaryData = subBoundaryBorder[partitionId];
|
|
900
|
+
// if (boundaryData && boundaryData.coordinates.length >= 3) {
|
|
901
|
+
// d += ` M ${boundaryData.coordinates[0][0]} ${boundaryData.coordinates[0][1]}`;
|
|
902
|
+
// for (let i = 1; i < boundaryData.coordinates.length; i++) {
|
|
903
|
+
// d += ` L ${boundaryData.coordinates[i][0]} ${boundaryData.coordinates[i][1]}`;
|
|
904
|
+
// }
|
|
905
|
+
// d += ' Z';
|
|
906
|
+
// }
|
|
907
|
+
// }
|
|
908
|
+
// const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
909
|
+
// path.setAttribute('d', d);
|
|
910
|
+
// path.setAttribute('clip-rule', 'evenodd'); // 关键
|
|
911
|
+
// clipPath.appendChild(path);
|
|
912
|
+
// defs.appendChild(clipPath);
|
|
913
|
+
// clipPathIdsMap[element.originalData?.id.toString()] = clipPathId;
|
|
914
|
+
// } else {
|
|
915
|
+
const clipPathId = `channel-exclude-all-${element.originalData?.id || Math.random().toString(36).substr(2, 9)}`;
|
|
916
|
+
// 检查是否已存在该 clipPath
|
|
917
|
+
const existingClipPath = defs.querySelector(`#${clipPathId}`);
|
|
918
|
+
if (existingClipPath)
|
|
919
|
+
continue;
|
|
920
|
+
// 创建 clipPath
|
|
921
|
+
const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
|
|
922
|
+
clipPath.setAttribute('id', clipPathId);
|
|
923
|
+
clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
|
|
924
|
+
// === 合成一个 path ===
|
|
925
|
+
let d = `M ${minX} ${minY} L ${maxX} ${minY} L ${maxX} ${maxY} L ${minX} ${maxY} Z`;
|
|
926
|
+
for (const partitionId in subBoundaryBorder) {
|
|
927
|
+
const boundaryData = subBoundaryBorder[partitionId];
|
|
928
|
+
if (boundaryData && boundaryData.coordinates.length >= 3) {
|
|
929
|
+
d += ` M ${boundaryData.coordinates[0][0]} ${boundaryData.coordinates[0][1]}`;
|
|
930
|
+
for (let i = 1; i < boundaryData.coordinates.length; i++) {
|
|
931
|
+
d += ` L ${boundaryData.coordinates[i][0]} ${boundaryData.coordinates[i][1]}`;
|
|
848
932
|
}
|
|
933
|
+
d += ' Z';
|
|
849
934
|
}
|
|
850
|
-
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
851
|
-
path.setAttribute('d', d);
|
|
852
|
-
path.setAttribute('clip-rule', 'evenodd'); // 关键
|
|
853
|
-
clipPath.appendChild(path);
|
|
854
|
-
defs.appendChild(clipPath);
|
|
855
|
-
clipPathIdsMap[element.originalData?.id.toString()] = clipPathId;
|
|
856
935
|
}
|
|
936
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
937
|
+
path.setAttribute('d', d);
|
|
938
|
+
path.setAttribute('clip-rule', 'evenodd'); // 关键
|
|
939
|
+
clipPath.appendChild(path);
|
|
940
|
+
defs.appendChild(clipPath);
|
|
941
|
+
clipPathIdsMap[element.originalData?.id.toString()] = clipPathId;
|
|
857
942
|
}
|
|
943
|
+
// }
|
|
858
944
|
return clipPathIdsMap;
|
|
859
945
|
}
|
|
860
946
|
/**
|
|
@@ -918,13 +1004,14 @@ class PathLayer extends BaseLayer {
|
|
|
918
1004
|
this.level = 3;
|
|
919
1005
|
this.scale = 1;
|
|
920
1006
|
this.lineScale = 1;
|
|
1007
|
+
this.boundaryPaths = {};
|
|
921
1008
|
this.type = LAYER_DEFAULT_TYPE.PATH;
|
|
922
1009
|
}
|
|
923
1010
|
/**
|
|
924
1011
|
* 创建所有分区并集的 clipPath
|
|
925
1012
|
*/
|
|
926
1013
|
createUnionClipPath(svgGroup) {
|
|
927
|
-
const { subBoundaryBorder } = useSubBoundaryBorderStore.getState();
|
|
1014
|
+
const { subBoundaryBorder, obstacles, svgElements } = useSubBoundaryBorderStore.getState();
|
|
928
1015
|
// 确保 defs 元素存在
|
|
929
1016
|
let defs = svgGroup.querySelector('defs');
|
|
930
1017
|
if (!defs) {
|
|
@@ -938,7 +1025,7 @@ class PathLayer extends BaseLayer {
|
|
|
938
1025
|
defs.removeChild(existing);
|
|
939
1026
|
// 合成所有分区的 path
|
|
940
1027
|
let d = '';
|
|
941
|
-
//
|
|
1028
|
+
// 1. 外圈(主边界,顺时针)
|
|
942
1029
|
Object.values(subBoundaryBorder).forEach((item) => {
|
|
943
1030
|
const bCoords = item.coordinates;
|
|
944
1031
|
if (bCoords.length >= 3) {
|
|
@@ -949,6 +1036,48 @@ class PathLayer extends BaseLayer {
|
|
|
949
1036
|
d += ' Z ';
|
|
950
1037
|
}
|
|
951
1038
|
});
|
|
1039
|
+
// 2. 内圈(禁区,逆时针)
|
|
1040
|
+
Object.values(obstacles).forEach((item) => {
|
|
1041
|
+
const bCoords = item.coordinates;
|
|
1042
|
+
if (bCoords.length >= 3) {
|
|
1043
|
+
d += `M ${bCoords[bCoords.length - 1][0]} ${bCoords[bCoords.length - 1][1]}`;
|
|
1044
|
+
for (let i = bCoords.length - 2; i >= 0; i--) {
|
|
1045
|
+
d += ` L ${bCoords[i][0]} ${bCoords[i][1]}`;
|
|
1046
|
+
}
|
|
1047
|
+
d += ' Z ';
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
// 3. svgElements(解析 SVG 字符串并提取 path 数据)
|
|
1051
|
+
Object.values(svgElements).forEach((svgPath) => {
|
|
1052
|
+
const svgPathString = svgPath?.metadata?.svg;
|
|
1053
|
+
if (svgPathString && typeof svgPathString === 'string' && svgPathString.trim()) {
|
|
1054
|
+
// 处理转义字符
|
|
1055
|
+
const processedSvgString = svgPathString.replace(/\\n/g, '\n').replace(/\\"/g, '"');
|
|
1056
|
+
// 解析 SVG 字符串
|
|
1057
|
+
const parser = new DOMParser();
|
|
1058
|
+
const svgDoc = parser.parseFromString(processedSvgString, 'image/svg+xml');
|
|
1059
|
+
const svgElement = svgDoc.documentElement;
|
|
1060
|
+
if (svgElement.tagName === 'svg') {
|
|
1061
|
+
// 查找 path 元素
|
|
1062
|
+
const pathElement = svgElement.querySelector('path');
|
|
1063
|
+
if (pathElement) {
|
|
1064
|
+
const pathData = pathElement.getAttribute('d');
|
|
1065
|
+
if (pathData) {
|
|
1066
|
+
// 获取 SVG 元素的变换参数
|
|
1067
|
+
const centerCoords = svgPath.coordinates?.[0] || [0, 0];
|
|
1068
|
+
const center = [centerCoords[0], centerCoords[1]];
|
|
1069
|
+
const userScale = svgPath.metadata.scale || 1;
|
|
1070
|
+
const direction = svgPath.metadata?.direction || 0;
|
|
1071
|
+
const originalWidth = parseFloat(svgElement.getAttribute('width') || '76');
|
|
1072
|
+
const originalHeight = parseFloat(svgElement.getAttribute('height') || '68');
|
|
1073
|
+
// 应用变换到路径数据
|
|
1074
|
+
const transformedPathData = this.transformSvgPath(pathData, center, userScale, direction, originalWidth, originalHeight);
|
|
1075
|
+
d += transformedPathData + ' ';
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
952
1081
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
953
1082
|
path.setAttribute('d', d);
|
|
954
1083
|
const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
|
|
@@ -973,48 +1102,132 @@ class PathLayer extends BaseLayer {
|
|
|
973
1102
|
// 2. 创建一个组,应用 clipPath
|
|
974
1103
|
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
975
1104
|
group.setAttribute('clip-path', `url(#${clipPathId})`);
|
|
976
|
-
group.setAttribute('opacity', '0.
|
|
977
|
-
// 3.
|
|
978
|
-
|
|
979
|
-
this.renderPathToGroup(group, element);
|
|
980
|
-
}
|
|
1105
|
+
group.setAttribute('opacity', '0.5'); // 统一透明度,防止叠加脏乱
|
|
1106
|
+
// 3. 优化渲染:按样式分组并合并路径
|
|
1107
|
+
this.renderOptimizedPaths(group);
|
|
981
1108
|
svgGroup.appendChild(group);
|
|
982
1109
|
}
|
|
983
1110
|
/**
|
|
984
|
-
*
|
|
1111
|
+
* 优化渲染:按样式分组并合并路径,减少 DOM 节点数量
|
|
985
1112
|
*/
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
const
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1113
|
+
renderOptimizedPaths(group) {
|
|
1114
|
+
// 按样式分组存储路径数据
|
|
1115
|
+
const styleGroups = new Map();
|
|
1116
|
+
// 收集所有路径数据并按样式分组
|
|
1117
|
+
for (const element of this.elements) {
|
|
1118
|
+
// 类型断言:PathLayer 中的 elements 实际上是 PathElements 结构
|
|
1119
|
+
const pathElement = element;
|
|
1120
|
+
const { id, elements } = pathElement;
|
|
1121
|
+
this.boundaryPaths[id] = [];
|
|
1122
|
+
elements.forEach((pathElement) => {
|
|
1123
|
+
const { coordinates, style } = pathElement;
|
|
1124
|
+
if (coordinates.length < 2)
|
|
1125
|
+
return;
|
|
1126
|
+
// 生成样式键(用于分组)
|
|
1127
|
+
const styleKey = this.generateStyleKey(style);
|
|
1128
|
+
// 构建路径数据
|
|
1129
|
+
let pathData = `M ${coordinates[0][0]} ${coordinates[0][1]}`;
|
|
1130
|
+
for (let i = 1; i < coordinates.length; i++) {
|
|
1131
|
+
pathData += ` L ${coordinates[i][0]} ${coordinates[i][1]}`;
|
|
1132
|
+
}
|
|
1133
|
+
// 按样式分组存储
|
|
1134
|
+
if (!styleGroups.has(styleKey)) {
|
|
1135
|
+
styleGroups.set(styleKey, { pathData: [], elements: [] });
|
|
1136
|
+
}
|
|
1137
|
+
styleGroups.get(styleKey).pathData.push(pathData);
|
|
1138
|
+
styleGroups.get(styleKey).elements.push(pathElement);
|
|
1139
|
+
});
|
|
1012
1140
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1141
|
+
// 为每种样式创建一个合并的 path 元素
|
|
1142
|
+
styleGroups.forEach((groupData) => {
|
|
1143
|
+
const { pathData, elements } = groupData;
|
|
1144
|
+
if (pathData.length === 0)
|
|
1145
|
+
return;
|
|
1146
|
+
// 使用第一个元素的样式作为该组的样式
|
|
1147
|
+
const firstElement = elements[0];
|
|
1148
|
+
const style = firstElement.style;
|
|
1149
|
+
// 创建合并的 path 元素
|
|
1150
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
1151
|
+
// 合并所有路径数据
|
|
1152
|
+
const mergedPathData = pathData.join(' ');
|
|
1153
|
+
path.setAttribute('d', mergedPathData);
|
|
1154
|
+
// 设置样式属性
|
|
1155
|
+
path.setAttribute('fill', 'none');
|
|
1156
|
+
path.setAttribute('stroke', style.lineColor || '#000000');
|
|
1157
|
+
path.setAttribute('mix-blend-mode', 'normal');
|
|
1158
|
+
const lineWidth = Math.max(style.lineWidth || 1, 0.5);
|
|
1159
|
+
path.setAttribute('stroke-width', lineWidth.toString());
|
|
1160
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
1161
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
1162
|
+
path.classList.add('vector-path');
|
|
1163
|
+
// 将合并的 path 添加到组中
|
|
1164
|
+
group.appendChild(path);
|
|
1165
|
+
// 保存引用到 boundaryPaths 中(保持兼容性)
|
|
1166
|
+
elements.forEach((element) => {
|
|
1167
|
+
const { id } = element;
|
|
1168
|
+
if (!this.boundaryPaths[id]) {
|
|
1169
|
+
this.boundaryPaths[id] = [];
|
|
1170
|
+
}
|
|
1171
|
+
this.boundaryPaths[id].push(path);
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* 变换 SVG 路径数据
|
|
1177
|
+
*/
|
|
1178
|
+
transformSvgPath(pathData, center, scale, direction, originalWidth, originalHeight) {
|
|
1179
|
+
// 解析路径数据并应用变换
|
|
1180
|
+
const commands = pathData.match(/[MmLlHhVvCcSsQqTtAaZz][^MmLlHhVvCcSsQqTtAaZz]*/g) || [];
|
|
1181
|
+
let transformedCommands = [];
|
|
1182
|
+
for (const command of commands) {
|
|
1183
|
+
const type = command[0];
|
|
1184
|
+
const params = command
|
|
1185
|
+
.slice(1)
|
|
1186
|
+
.trim()
|
|
1187
|
+
.split(/[\s,]+/)
|
|
1188
|
+
.filter(Boolean)
|
|
1189
|
+
.map(Number);
|
|
1190
|
+
if (type === 'Z' || type === 'z') {
|
|
1191
|
+
// 闭合路径,不需要变换
|
|
1192
|
+
transformedCommands.push(command);
|
|
1193
|
+
continue;
|
|
1194
|
+
}
|
|
1195
|
+
// 处理坐标参数
|
|
1196
|
+
let transformedParams = [];
|
|
1197
|
+
for (let i = 0; i < params.length; i += 2) {
|
|
1198
|
+
if (i + 1 < params.length) {
|
|
1199
|
+
let x = params[i];
|
|
1200
|
+
let y = params[i + 1];
|
|
1201
|
+
// 应用变换:先平移到中心,然后缩放、旋转,最后平移到目标位置
|
|
1202
|
+
// 1. 平移到原点(相对于原始尺寸的中心)
|
|
1203
|
+
x -= originalWidth / 2;
|
|
1204
|
+
y -= originalHeight / 2;
|
|
1205
|
+
// 2. 应用缩放
|
|
1206
|
+
x *= scale;
|
|
1207
|
+
y *= scale;
|
|
1208
|
+
// 3. 应用旋转
|
|
1209
|
+
const cos = Math.cos(-direction);
|
|
1210
|
+
const sin = Math.sin(-direction);
|
|
1211
|
+
const newX = x * cos - y * sin;
|
|
1212
|
+
const newY = x * sin + y * cos;
|
|
1213
|
+
// 4. 平移到目标位置
|
|
1214
|
+
x = newX + center[0];
|
|
1215
|
+
y = newY + center[1];
|
|
1216
|
+
transformedParams.push(x, y);
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
// 重建命令
|
|
1220
|
+
if (transformedParams.length > 0) {
|
|
1221
|
+
transformedCommands.push(type + transformedParams.join(' '));
|
|
1222
|
+
}
|
|
1015
1223
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1224
|
+
return transformedCommands.join(' ');
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* 生成样式键,用于路径分组
|
|
1228
|
+
*/
|
|
1229
|
+
generateStyleKey(style) {
|
|
1230
|
+
return `${style.lineColor || '#000000'}-${style.lineWidth || 1}-${style.opacity || 1}`;
|
|
1018
1231
|
}
|
|
1019
1232
|
}
|
|
1020
1233
|
|
|
@@ -1122,7 +1335,7 @@ class ObstacleLayer extends BaseLayer {
|
|
|
1122
1335
|
}
|
|
1123
1336
|
}
|
|
1124
1337
|
|
|
1125
|
-
var chargingPileImage = "data:image/png;base64,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";
|
|
1338
|
+
var chargingPileImage = "data:image/png;base64,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";
|
|
1126
1339
|
|
|
1127
1340
|
/**
|
|
1128
1341
|
* 充电桩图层
|
|
@@ -1312,7 +1525,7 @@ class SvgElementLayer extends BaseLayer {
|
|
|
1312
1525
|
// 在transformGroup上应用变换:平移到中心,旋转,缩放,然后居中SVG
|
|
1313
1526
|
const transform = [
|
|
1314
1527
|
`translate(${center[0]}, ${center[1]})`,
|
|
1315
|
-
`rotate(${(direction * 180) / Math.PI})`,
|
|
1528
|
+
`rotate(${-(direction * 180) / Math.PI})`,
|
|
1316
1529
|
`scale(${userScale})`,
|
|
1317
1530
|
`translate(${-originalWidth / 2}, ${-originalHeight / 2})`,
|
|
1318
1531
|
].join(' ');
|
|
@@ -1341,7 +1554,6 @@ class SvgElementLayer extends BaseLayer {
|
|
|
1341
1554
|
renderSvgPlaceholder(svgGroup, center, metadata, style) {
|
|
1342
1555
|
const size = (metadata?.scale || 1) * 20;
|
|
1343
1556
|
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
1344
|
-
console.log('style==', style);
|
|
1345
1557
|
rect.setAttribute('x', ((center[0] - size / 2) / 50).toString());
|
|
1346
1558
|
rect.setAttribute('y', ((center[1] - size / 2) / 50).toString());
|
|
1347
1559
|
rect.setAttribute('width', size.toString());
|
|
@@ -1411,9 +1623,10 @@ class VisionOffLayer extends BaseLayer {
|
|
|
1411
1623
|
*/
|
|
1412
1624
|
const BOUNDARY_STYLES = {
|
|
1413
1625
|
lineColor: '#ffffff',
|
|
1414
|
-
fillColor: 'rgba(239, 255, 237, 0.
|
|
1626
|
+
fillColor: 'rgba(239, 255, 237, 0.1)', // 更鲜艳的绿色半透明填充,增强可见性
|
|
1415
1627
|
lineWidth: 2,
|
|
1416
1628
|
opacity: DEFAULT_OPACITIES.FULL,
|
|
1629
|
+
mowingLineColor: 'rgba(99, 216, 174, 1)',
|
|
1417
1630
|
};
|
|
1418
1631
|
const VISION_OFF_AREA_STYLES = {
|
|
1419
1632
|
lineColor: 'rgba(108, 167, 255, 1)',
|
|
@@ -1438,14 +1651,15 @@ const DOODLE_STYLES = {
|
|
|
1438
1651
|
lineColor: '#ff5722',
|
|
1439
1652
|
fillColor: '#ff9800', // 粉色半透明填充
|
|
1440
1653
|
lineWidth: DEFAULT_LINE_WIDTHS.TIME_LIMIT_OBSTACLE,
|
|
1441
|
-
opacity: DEFAULT_OPACITIES.
|
|
1654
|
+
opacity: DEFAULT_OPACITIES.DOODLE,
|
|
1442
1655
|
};
|
|
1443
1656
|
const PATH_EDGE_STYLES = {
|
|
1444
1657
|
lineWidth: DEFAULT_LINE_WIDTHS.PATH,
|
|
1445
|
-
opacity: DEFAULT_OPACITIES.
|
|
1446
|
-
edgeLineColor: 'rgba(
|
|
1658
|
+
opacity: DEFAULT_OPACITIES.MEDIUM,
|
|
1659
|
+
edgeLineColor: 'rgba(231, 238, 246)',
|
|
1447
1660
|
transLineColor: 'transparent',
|
|
1448
|
-
|
|
1661
|
+
mowedLineColor: 'rgba(231, 238, 246)',
|
|
1662
|
+
mowingLineColor: 'rgba(123, 200, 187)',
|
|
1449
1663
|
};
|
|
1450
1664
|
const CHANNEL_STYLES = {
|
|
1451
1665
|
lineColor: 'purple',
|
|
@@ -1479,13 +1693,13 @@ const DEFAULT_STYLES = {
|
|
|
1479
1693
|
function convertPointsFormat(points) {
|
|
1480
1694
|
if (!points || points.length === 0)
|
|
1481
1695
|
return null;
|
|
1482
|
-
return points.map(point => {
|
|
1696
|
+
return points.map((point) => {
|
|
1483
1697
|
if (point.length >= 2) {
|
|
1484
1698
|
// 对前两个元素应用缩放因子,保留其他元素
|
|
1485
1699
|
return [
|
|
1486
1700
|
point[0] * SCALE_FACTOR,
|
|
1487
1701
|
-point[1] * SCALE_FACTOR, // Y轴翻转,与Python代码一致
|
|
1488
|
-
...point.slice(2) // 保留第三个及以后的元素
|
|
1702
|
+
...point.slice(2), // 保留第三个及以后的元素
|
|
1489
1703
|
];
|
|
1490
1704
|
}
|
|
1491
1705
|
return point;
|
|
@@ -1500,7 +1714,7 @@ function convertPositionFormat(position) {
|
|
|
1500
1714
|
return null;
|
|
1501
1715
|
return {
|
|
1502
1716
|
x: position[0] * SCALE_FACTOR,
|
|
1503
|
-
y: -position[1] * SCALE_FACTOR // Y轴翻转
|
|
1717
|
+
y: -position[1] * SCALE_FACTOR, // Y轴翻转
|
|
1504
1718
|
};
|
|
1505
1719
|
}
|
|
1506
1720
|
/**
|
|
@@ -1509,9 +1723,146 @@ function convertPositionFormat(position) {
|
|
|
1509
1723
|
function convertCoordinate(x, y) {
|
|
1510
1724
|
return {
|
|
1511
1725
|
x: x * SCALE_FACTOR,
|
|
1512
|
-
y: -y * SCALE_FACTOR // Y轴翻转
|
|
1726
|
+
y: -y * SCALE_FACTOR, // Y轴翻转
|
|
1513
1727
|
};
|
|
1514
1728
|
}
|
|
1729
|
+
/**
|
|
1730
|
+
* @param x x坐标
|
|
1731
|
+
* @param y y坐标
|
|
1732
|
+
* @param isAllowInBoundary 是否允许点在边界上的判断
|
|
1733
|
+
* @return ture-点在边界上即可视为在边界内,false-严格判断点在边界内
|
|
1734
|
+
*/
|
|
1735
|
+
function isPointIn$1(x, y, pointList, isAllowInBoundary) {
|
|
1736
|
+
let count = 0;
|
|
1737
|
+
let size = pointList.length;
|
|
1738
|
+
let p1, p2, p3;
|
|
1739
|
+
for (let i = 0; i < size; i++) {
|
|
1740
|
+
p1 = pointList[i];
|
|
1741
|
+
p2 = pointList[(i + 1) % size];
|
|
1742
|
+
if (p1.y == null || p2.y == null || p1.x == null || p2.x == null) {
|
|
1743
|
+
continue;
|
|
1744
|
+
}
|
|
1745
|
+
if (p1.y === p2.y) {
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
if (y > Math.min(p1.y, p2.y) && y < Math.max(p1.y, p2.y)) {
|
|
1749
|
+
const interX = ((y - p1.y) * (p2.x - p1.x)) / (p2.y - p1.y) + p1.x;
|
|
1750
|
+
if (interX >= x) {
|
|
1751
|
+
count++;
|
|
1752
|
+
}
|
|
1753
|
+
else if (interX == x) {
|
|
1754
|
+
return isAllowInBoundary;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
else {
|
|
1758
|
+
if (y == p2.y && x <= p2.x) {
|
|
1759
|
+
p3 = pointList[(i + 2) % size];
|
|
1760
|
+
if (y >= Math.min(p1.y, p3.y) && y <= Math.max(p1.y, p3.y)) {
|
|
1761
|
+
// 若当前点的y坐标位于 p1和p3组成的线段关于y轴的投影中,则记为该点的射线只穿过端点一次。
|
|
1762
|
+
++count;
|
|
1763
|
+
}
|
|
1764
|
+
else {
|
|
1765
|
+
// 若当前点的y坐标不能包含在p1和p3组成的线段关于y轴的投影中,则点射线通过的两条线段组成了一个弯折的部分,
|
|
1766
|
+
// 此时我们记射线穿过该端点两次
|
|
1767
|
+
count += 2;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return count % 2 == 1;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* 用于判断三个点的方向的辅助方法
|
|
1776
|
+
*/
|
|
1777
|
+
function orientation(p, q, r) {
|
|
1778
|
+
const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
1779
|
+
if (val == 0)
|
|
1780
|
+
return 0; // colinear
|
|
1781
|
+
return val > 0 ? 1 : 2; // clock or counterclock wise
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* 检查点q是否在线段pr上的辅助方法
|
|
1785
|
+
*/
|
|
1786
|
+
function onSegment(p, q, r) {
|
|
1787
|
+
if (q.x <= Math.max(p.x, r.x) &&
|
|
1788
|
+
q.x >= Math.min(p.x, r.x) &&
|
|
1789
|
+
q.y <= Math.max(p.y, r.y) &&
|
|
1790
|
+
q.y >= Math.min(p.y, r.y)) {
|
|
1791
|
+
return true;
|
|
1792
|
+
}
|
|
1793
|
+
return false;
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* 判断两条线段是否相交的方法
|
|
1797
|
+
*/
|
|
1798
|
+
function doTwoLinesIntersect(p1, q1, p2, q2) {
|
|
1799
|
+
//处理p1和q1两个点相同的情况
|
|
1800
|
+
if (p1.x - q1.x == 0 && p1.y - q1.y == 0) {
|
|
1801
|
+
return false;
|
|
1802
|
+
}
|
|
1803
|
+
if (p2.x - q2.x == 0 && p2.y - q2.y == 0) {
|
|
1804
|
+
return false;
|
|
1805
|
+
}
|
|
1806
|
+
// 计算四个点的方向
|
|
1807
|
+
const o1 = orientation(p1, q1, p2);
|
|
1808
|
+
const o2 = orientation(p1, q1, q2);
|
|
1809
|
+
const o3 = orientation(p2, q2, p1);
|
|
1810
|
+
const o4 = orientation(p2, q2, q1);
|
|
1811
|
+
// 一般情况,如果四个方向两两不同,则线段相交
|
|
1812
|
+
if (o1 != o2 && o3 != o4) {
|
|
1813
|
+
return true;
|
|
1814
|
+
}
|
|
1815
|
+
// 特殊情况,当线段的端点在另一条线段上时
|
|
1816
|
+
if (o1 == 0 && onSegment(p1, q1, p2))
|
|
1817
|
+
return true;
|
|
1818
|
+
if (o2 == 0 && onSegment(p1, q1, q2))
|
|
1819
|
+
return true;
|
|
1820
|
+
if (o3 == 0 && onSegment(p2, q2, p1))
|
|
1821
|
+
return true;
|
|
1822
|
+
if (o4 == 0 && onSegment(p2, q2, q1))
|
|
1823
|
+
return true;
|
|
1824
|
+
// 如果以上情况都不满足,则线段不相交
|
|
1825
|
+
return false;
|
|
1826
|
+
}
|
|
1827
|
+
/**
|
|
1828
|
+
* 判断多点折线是否相交
|
|
1829
|
+
*/
|
|
1830
|
+
function doIntersect(points1, points2) {
|
|
1831
|
+
if (points1 == null || points2 == null || points1.length < 3 || points2.length < 3) {
|
|
1832
|
+
return false;
|
|
1833
|
+
}
|
|
1834
|
+
for (let i = 0; i < points1.length - 1; i++) {
|
|
1835
|
+
for (let j = 0; j < points2.length - 1; j++) {
|
|
1836
|
+
if (doTwoLinesIntersect(points1[i], points1[i + 1], points2[j], points2[j + 1])) {
|
|
1837
|
+
return true;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
return false;
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* 两个图形是否完全分离,互相不包含
|
|
1845
|
+
*/
|
|
1846
|
+
function isOutsideToEachOther(points1, points2) {
|
|
1847
|
+
// 相交关系
|
|
1848
|
+
if (doIntersect(points1, points2)) {
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
// 点关系,判断每个图形的点都在另一个图形外部
|
|
1852
|
+
for (let point of points1) {
|
|
1853
|
+
if (isPointIn$1(point.x, point.y, points2, true)) {
|
|
1854
|
+
// Log.i("ycf", "isOutsideToEachOther: mapPoint1=" + mapPoint);
|
|
1855
|
+
return false;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
for (let point of points2) {
|
|
1859
|
+
if (isPointIn$1(point.x, point.y, points1, true)) {
|
|
1860
|
+
// Log.i("ycf", "isOutsideToEachOther: mapPoint2=" + mapPoint);
|
|
1861
|
+
return false;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return true;
|
|
1865
|
+
}
|
|
1515
1866
|
|
|
1516
1867
|
/**
|
|
1517
1868
|
* 按Python逻辑创建路径段:根据连续的两点之间的关系确定线段类型
|
|
@@ -1529,11 +1880,11 @@ function createPathSegmentsByType(list) {
|
|
|
1529
1880
|
let currentSegmentType = null;
|
|
1530
1881
|
for (const currentPoint of list) {
|
|
1531
1882
|
const currentCoord = {
|
|
1532
|
-
x: currentPoint.postureX
|
|
1533
|
-
y:
|
|
1883
|
+
x: currentPoint.postureX,
|
|
1884
|
+
y: currentPoint.postureY
|
|
1534
1885
|
};
|
|
1535
1886
|
if (lastPoint !== null) {
|
|
1536
|
-
// 判断上一个点和当前点是否需要绘制 (
|
|
1887
|
+
// 判断上一个点和当前点是否需要绘制 (iso端逻辑)
|
|
1537
1888
|
const lastShouldDraw = lastPoint.pathType === '00' || lastPoint.pathType === '01' || lastPoint.knifeRotation === '01';
|
|
1538
1889
|
const currentShouldDraw = currentPoint.pathType === '00' || currentPoint.pathType === '01' || currentPoint.knifeRotation === '01';
|
|
1539
1890
|
let segmentType;
|
|
@@ -1553,8 +1904,8 @@ function createPathSegmentsByType(list) {
|
|
|
1553
1904
|
// 开始新段
|
|
1554
1905
|
currentSegment = [
|
|
1555
1906
|
{
|
|
1556
|
-
x: lastPoint.postureX
|
|
1557
|
-
y:
|
|
1907
|
+
x: lastPoint.postureX,
|
|
1908
|
+
y: lastPoint.postureY
|
|
1558
1909
|
},
|
|
1559
1910
|
currentCoord
|
|
1560
1911
|
];
|
|
@@ -1774,6 +2125,136 @@ function calculateMapGpsCenter(mapData) {
|
|
|
1774
2125
|
};
|
|
1775
2126
|
}
|
|
1776
2127
|
|
|
2128
|
+
/**
|
|
2129
|
+
* 并查集(Union-Find)是一种非常高效的数据结构,用于处理动态连通性问题。
|
|
2130
|
+
* 它可以快速判断网络中任意两点是否连通,并能将不连通的集合合并。
|
|
2131
|
+
*/
|
|
2132
|
+
class UnionFind {
|
|
2133
|
+
/**
|
|
2134
|
+
* 构造函数,n为图的节点总数
|
|
2135
|
+
* @param {number} n - 节点总数
|
|
2136
|
+
*/
|
|
2137
|
+
constructor(n) {
|
|
2138
|
+
this.count = n; // 连通分量的数量
|
|
2139
|
+
this.parent = new Array(n); // parent[i]表示第i个元素所指向的父节点
|
|
2140
|
+
// 初始时,每个节点的父节点是自己
|
|
2141
|
+
for (let i = 0; i < n; i++) {
|
|
2142
|
+
this.parent[i] = i;
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* 查找元素p所对应的集合编号(根节点)
|
|
2147
|
+
* @param {number} p - 要查找的元素
|
|
2148
|
+
* @returns {number} 根节点的编号
|
|
2149
|
+
*/
|
|
2150
|
+
find(p) {
|
|
2151
|
+
while (p !== this.parent[p]) {
|
|
2152
|
+
this.parent[p] = this.parent[this.parent[p]]; // 路径压缩
|
|
2153
|
+
p = this.parent[p];
|
|
2154
|
+
}
|
|
2155
|
+
return p;
|
|
2156
|
+
}
|
|
2157
|
+
/**
|
|
2158
|
+
* 判断元素p和元素q是否属于同一集合
|
|
2159
|
+
* @param {number} p - 第一个元素
|
|
2160
|
+
* @param {number} q - 第二个元素
|
|
2161
|
+
* @returns {boolean} 是否连通
|
|
2162
|
+
*/
|
|
2163
|
+
isConnected(p, q) {
|
|
2164
|
+
return this.find(p) === this.find(q);
|
|
2165
|
+
}
|
|
2166
|
+
/**
|
|
2167
|
+
* 合并元素p和元素q所属的集合
|
|
2168
|
+
* @param {number} p - 第一个元素
|
|
2169
|
+
* @param {number} q - 第二个元素
|
|
2170
|
+
*/
|
|
2171
|
+
union(p, q) {
|
|
2172
|
+
const rootP = this.find(p);
|
|
2173
|
+
const rootQ = this.find(q);
|
|
2174
|
+
if (rootP === rootQ) {
|
|
2175
|
+
return; // 已经在同一个集合中
|
|
2176
|
+
}
|
|
2177
|
+
// 将较小的根节点作为父节点(按秩合并的简化版本)
|
|
2178
|
+
if (rootP < rootQ) {
|
|
2179
|
+
this.parent[rootQ] = rootP;
|
|
2180
|
+
}
|
|
2181
|
+
else {
|
|
2182
|
+
this.parent[rootP] = rootQ;
|
|
2183
|
+
}
|
|
2184
|
+
// 两个集合合并成一个集合,连通分量减1
|
|
2185
|
+
this.count--;
|
|
2186
|
+
}
|
|
2187
|
+
/**
|
|
2188
|
+
* 获取当前的连通分量个数
|
|
2189
|
+
* @returns {number} 连通分量数量
|
|
2190
|
+
*/
|
|
2191
|
+
getCount() {
|
|
2192
|
+
return this.count;
|
|
2193
|
+
}
|
|
2194
|
+
/**
|
|
2195
|
+
* 获取联通的组
|
|
2196
|
+
* @param {Array} list - 原始元素列表
|
|
2197
|
+
* @returns {Array<Set>} 联通组列表
|
|
2198
|
+
*/
|
|
2199
|
+
getConnectedGroup(list) {
|
|
2200
|
+
if (!list || list.length === 0 || !this.parent || this.parent.length === 0) {
|
|
2201
|
+
return null;
|
|
2202
|
+
}
|
|
2203
|
+
if (list.length !== this.parent.length) {
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
const map = new Map();
|
|
2207
|
+
// 遍历所有元素,按根节点分组
|
|
2208
|
+
for (let i = 0; i < this.parent.length; i++) {
|
|
2209
|
+
const root = this.parent[i];
|
|
2210
|
+
if (!map.has(root)) {
|
|
2211
|
+
map.set(root, new Set());
|
|
2212
|
+
}
|
|
2213
|
+
map.get(root).add(list[i]);
|
|
2214
|
+
}
|
|
2215
|
+
return Array.from(map.values());
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* 重置并查集
|
|
2219
|
+
* @param {number} n - 新的节点总数
|
|
2220
|
+
*/
|
|
2221
|
+
reset(n) {
|
|
2222
|
+
this.count = n;
|
|
2223
|
+
this.parent = new Array(n);
|
|
2224
|
+
for (let i = 0; i < n; i++) {
|
|
2225
|
+
this.parent[i] = i;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
function isTunnelConnected(a, b, connectIds) {
|
|
2231
|
+
if (!a || !b)
|
|
2232
|
+
return false;
|
|
2233
|
+
if (!connectIds || connectIds?.length === 0)
|
|
2234
|
+
return false;
|
|
2235
|
+
const temp = [a?.id, b?.id];
|
|
2236
|
+
temp.sort();
|
|
2237
|
+
return connectIds?.includes(temp?.join('-'));
|
|
2238
|
+
}
|
|
2239
|
+
function isOverlayConnected(a, b) {
|
|
2240
|
+
if (!a || !b) {
|
|
2241
|
+
return false;
|
|
2242
|
+
}
|
|
2243
|
+
if (!a?.points?.length || !b?.points?.length) {
|
|
2244
|
+
return false;
|
|
2245
|
+
}
|
|
2246
|
+
const aPoints = a?.points?.map(item => ({ x: item[0], y: item[1] }));
|
|
2247
|
+
const bPoints = b?.points?.map(item => ({ x: item[0], y: item[1] }));
|
|
2248
|
+
try {
|
|
2249
|
+
if (isOutsideToEachOther(aPoints, bPoints)) {
|
|
2250
|
+
return false;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
catch (error) {
|
|
2254
|
+
console.log('error->', error);
|
|
2255
|
+
}
|
|
2256
|
+
return true;
|
|
2257
|
+
}
|
|
1777
2258
|
/**
|
|
1778
2259
|
* 通过 mapData 和 pathData 生成所有 boundary 的数据
|
|
1779
2260
|
* @param mapData 地图数据
|
|
@@ -1782,41 +2263,21 @@ function calculateMapGpsCenter(mapData) {
|
|
|
1782
2263
|
*/
|
|
1783
2264
|
function generateBoundaryData(mapData, pathData) {
|
|
1784
2265
|
const boundaryData = [];
|
|
2266
|
+
let chargingPileBoundary = undefined;
|
|
1785
2267
|
if (!mapData || !mapData.sub_maps) {
|
|
1786
2268
|
return boundaryData;
|
|
1787
2269
|
}
|
|
1788
2270
|
// 第一步:收集所有TUNNEL数据的connection信息
|
|
1789
|
-
const
|
|
1790
|
-
//
|
|
1791
|
-
for (const subMap of mapData.sub_maps) {
|
|
1792
|
-
if (!subMap.elements)
|
|
1793
|
-
continue;
|
|
1794
|
-
// 找到该子地图中所有 type 为 TUNNEL 的元素
|
|
1795
|
-
const tunnelElements = subMap.elements.filter(element => element.type === 'TUNNEL');
|
|
1796
|
-
for (const tunnelElement of tunnelElements) {
|
|
1797
|
-
const connection = tunnelElement.connection;
|
|
1798
|
-
if (connection) {
|
|
1799
|
-
// connection可能是单个数字或数组
|
|
1800
|
-
if (Array.isArray(connection)) {
|
|
1801
|
-
connection.forEach(id => connectedBoundaryIds.add(id));
|
|
1802
|
-
}
|
|
1803
|
-
else if (typeof connection === 'number') {
|
|
1804
|
-
connectedBoundaryIds.add(connection);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
}
|
|
1809
|
-
// 1.2 遍历mapData中的tunnels字段
|
|
2271
|
+
const connectIds = [];
|
|
2272
|
+
// 遍历mapData中的tunnels字段
|
|
1810
2273
|
if (mapData.tunnels && Array.isArray(mapData.tunnels)) {
|
|
1811
2274
|
for (const tunnel of mapData.tunnels) {
|
|
1812
2275
|
const connection = tunnel.connection;
|
|
1813
2276
|
if (connection) {
|
|
1814
2277
|
// connection可能是单个数字或数组
|
|
1815
2278
|
if (Array.isArray(connection)) {
|
|
1816
|
-
connection.
|
|
1817
|
-
|
|
1818
|
-
else if (typeof connection === 'number') {
|
|
1819
|
-
connectedBoundaryIds.add(connection);
|
|
2279
|
+
connection.sort();
|
|
2280
|
+
connectIds.push(connection.join('-'));
|
|
1820
2281
|
}
|
|
1821
2282
|
}
|
|
1822
2283
|
}
|
|
@@ -1827,7 +2288,9 @@ function generateBoundaryData(mapData, pathData) {
|
|
|
1827
2288
|
if (!subMap.elements)
|
|
1828
2289
|
continue;
|
|
1829
2290
|
// 每个sub_map的elements是边界坐标,没有sub_map只有一个boundary数据
|
|
1830
|
-
const boundaryElement = subMap.elements.find(element => element.type === 'BOUNDARY');
|
|
2291
|
+
const boundaryElement = subMap.elements.find((element) => element.type === 'BOUNDARY');
|
|
2292
|
+
// 如果当前subMap存在充电桩且充电桩存在tunnel,说明当前subMap中的boundary是初始boundary,这个boundary不为孤立区域
|
|
2293
|
+
const hasTunnelToChargingPile = subMap.elements.some((element) => element.type === 'CHARGING_PILE' && element.tunnel);
|
|
1831
2294
|
// 创建基础的 boundary 数据(来自 mapData)
|
|
1832
2295
|
const boundary = {
|
|
1833
2296
|
// 从 BOUNDARY 元素复制属性
|
|
@@ -1836,8 +2299,6 @@ function generateBoundaryData(mapData, pathData) {
|
|
|
1836
2299
|
area: subMap?.area,
|
|
1837
2300
|
points: convertPointsFormat(boundaryElement?.points) || [],
|
|
1838
2301
|
type: boundaryElement.type,
|
|
1839
|
-
// 判断是否为孤立子区域
|
|
1840
|
-
isIsolated: !connectedBoundaryIds.has(boundaryElement.id)
|
|
1841
2302
|
};
|
|
1842
2303
|
// 如果有 pathData,尝试匹配对应的分区数据
|
|
1843
2304
|
if (pathData) {
|
|
@@ -1853,185 +2314,43 @@ function generateBoundaryData(mapData, pathData) {
|
|
|
1853
2314
|
boundary.endTime = partitionData.endTime;
|
|
1854
2315
|
}
|
|
1855
2316
|
}
|
|
2317
|
+
if (hasTunnelToChargingPile) {
|
|
2318
|
+
chargingPileBoundary = boundary;
|
|
2319
|
+
}
|
|
1856
2320
|
boundaryData.push(boundary);
|
|
1857
2321
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
* 从data数组的第0位开始取4个字节组成一个int型数
|
|
1868
|
-
* 对应Java: public static int bytes2Int(byte[] data)
|
|
1869
|
-
*
|
|
1870
|
-
* @param data 源字节数组
|
|
1871
|
-
* @returns 返回int型数
|
|
1872
|
-
*/
|
|
1873
|
-
// public static bytes2Int(data: Uint8Array): number {
|
|
1874
|
-
// return FormatUtils.bytes2IntWithOffset(data, 0, false);
|
|
1875
|
-
// }
|
|
1876
|
-
/**
|
|
1877
|
-
* 从data数组的第position位置开始取4个字节组成int型数
|
|
1878
|
-
* 对应Java: public static int bytes2Int(byte[] data, int offset, boolean bigEndian)
|
|
1879
|
-
*
|
|
1880
|
-
* @param data 源字节数组
|
|
1881
|
-
* @param offset 要组成int型数据的起始位置
|
|
1882
|
-
* @param bigEndian 是否为大端
|
|
1883
|
-
* @returns 返回int型数
|
|
1884
|
-
*/
|
|
1885
|
-
static bytes2Int(data, offset, bigEndian) {
|
|
1886
|
-
return FormatUtils.bytes2IntWidthLength(data, offset, bigEndian, 4);
|
|
1887
|
-
}
|
|
1888
|
-
/**
|
|
1889
|
-
* 从data数组的第position位置开始取几个字节组成int型数
|
|
1890
|
-
* 对应Java: public static int bytes2Int(byte[] data, int offset, boolean bigEndian, int length)
|
|
1891
|
-
*
|
|
1892
|
-
* @param data 源字节数组
|
|
1893
|
-
* @param offset 要组成int型数据的起始位置
|
|
1894
|
-
* @param bigEndian 是否为大端
|
|
1895
|
-
* @param length 取的字节长度
|
|
1896
|
-
* @returns 返回int型数
|
|
1897
|
-
*/
|
|
1898
|
-
static bytes2IntWidthLength(data, offset, bigEndian, length) {
|
|
1899
|
-
if (!data || offset < 0 || offset > data.length) {
|
|
1900
|
-
return 0;
|
|
1901
|
-
}
|
|
1902
|
-
let result = 0;
|
|
1903
|
-
try {
|
|
1904
|
-
// 创建DataView来处理字节序
|
|
1905
|
-
const buffer = new ArrayBuffer(length);
|
|
1906
|
-
const view = new DataView(buffer);
|
|
1907
|
-
// 将指定长度的数据复制到buffer中
|
|
1908
|
-
const tempArray = new Uint8Array(buffer);
|
|
1909
|
-
for (let i = 0; i < length && offset + i < data.length; i++) {
|
|
1910
|
-
tempArray[i] = data[offset + i];
|
|
1911
|
-
}
|
|
1912
|
-
// 根据字节序读取数据(使用有符号整数,与Java保持一致)
|
|
1913
|
-
if (length === 1) {
|
|
1914
|
-
result = view.getInt8(0);
|
|
1915
|
-
}
|
|
1916
|
-
else if (length === 2) {
|
|
1917
|
-
result = view.getInt16(0, !bigEndian); // DataView的littleEndian参数与bigEndian相反
|
|
1918
|
-
}
|
|
1919
|
-
else if (length === 4) {
|
|
1920
|
-
result = view.getInt32(0, !bigEndian); // 改为 getInt32,返回有符号整数
|
|
1921
|
-
}
|
|
1922
|
-
else {
|
|
1923
|
-
// 对于其他长度,手动处理
|
|
1924
|
-
result = FormatUtils.manualBytes2Int(data, offset, bigEndian, length);
|
|
2322
|
+
const unionFind = new UnionFind(boundaryData?.length);
|
|
2323
|
+
for (let i = 0; i < boundaryData?.length - 1; i++) {
|
|
2324
|
+
for (let j = i + 1; j < boundaryData?.length; j++) {
|
|
2325
|
+
const boundary1 = boundaryData[i];
|
|
2326
|
+
const boundary2 = boundaryData[j];
|
|
2327
|
+
const isChannelConnect = isTunnelConnected(boundary1, boundary2, connectIds);
|
|
2328
|
+
const isOverlayConnect = isOverlayConnected(boundary1, boundary2);
|
|
2329
|
+
if (isChannelConnect || isOverlayConnect) {
|
|
2330
|
+
unionFind.union(i, j);
|
|
1925
2331
|
}
|
|
1926
2332
|
}
|
|
1927
|
-
catch (e) {
|
|
1928
|
-
// console.error(
|
|
1929
|
-
// `${FormatUtils.TAG}: bytes2Int: Exception : data = ${data}, offset = ${offset}`,
|
|
1930
|
-
// e
|
|
1931
|
-
// );
|
|
1932
|
-
// console.log(
|
|
1933
|
-
// `${FormatUtils.TAG}: bytes2Int: Exception = ${e instanceof Error ? e.message : e}`
|
|
1934
|
-
// );
|
|
1935
|
-
}
|
|
1936
|
-
return result;
|
|
1937
2333
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
* @param bigEndian 是否为大端
|
|
1944
|
-
* @param length 字节长度
|
|
1945
|
-
* @returns 转换后的整数
|
|
1946
|
-
*/
|
|
1947
|
-
static manualBytes2Int(data, offset, bigEndian, length) {
|
|
1948
|
-
let result = 0;
|
|
1949
|
-
if (bigEndian) {
|
|
1950
|
-
// 大端序:最高位字节在最低地址
|
|
1951
|
-
for (let i = 0; i < length && offset + i < data.length; i++) {
|
|
1952
|
-
result = (result << 8) | (data[offset + i] & 0xff);
|
|
1953
|
-
}
|
|
2334
|
+
const tunnelAndOverlayList = unionFind.getConnectedGroup(boundaryData);
|
|
2335
|
+
const chargingPileConnectBoundarys = tunnelAndOverlayList?.find(item => item?.has(chargingPileBoundary));
|
|
2336
|
+
for (let boundary of boundaryData) {
|
|
2337
|
+
if (chargingPileConnectBoundarys?.has(boundary)) {
|
|
2338
|
+
boundary.isIsolated = false;
|
|
1954
2339
|
}
|
|
1955
2340
|
else {
|
|
1956
|
-
|
|
1957
|
-
for (let i = length - 1; i >= 0; i--) {
|
|
1958
|
-
if (offset + i < data.length) {
|
|
1959
|
-
result = (result << 8) | (data[offset + i] & 0xff);
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
return result;
|
|
1964
|
-
}
|
|
1965
|
-
/**
|
|
1966
|
-
* 将字节数组转换为十六进制字符串
|
|
1967
|
-
* 对应Java: public static String bytesToHex(byte[] bytes)
|
|
1968
|
-
*
|
|
1969
|
-
* @param bytes 字节数组
|
|
1970
|
-
* @returns 十六进制字符串
|
|
1971
|
-
*/
|
|
1972
|
-
static bytesToHex(bytes) {
|
|
1973
|
-
if (!bytes) {
|
|
1974
|
-
return 'null';
|
|
1975
|
-
}
|
|
1976
|
-
const hexArray = '0123456789ABCDEF'.split('');
|
|
1977
|
-
const hexChars = [];
|
|
1978
|
-
for (let j = 0; j < bytes.length; j++) {
|
|
1979
|
-
const v = bytes[j] & 0xff;
|
|
1980
|
-
hexChars.push(hexArray[v >>> 4]);
|
|
1981
|
-
hexChars.push(hexArray[v & 0x0f]);
|
|
1982
|
-
}
|
|
1983
|
-
return hexChars.join('');
|
|
1984
|
-
}
|
|
1985
|
-
/**
|
|
1986
|
-
* 将十六进制字符串转换为字节数组
|
|
1987
|
-
* 对应Java: public static byte[] hexStringToByteArray(String s)
|
|
1988
|
-
*
|
|
1989
|
-
* @param s 十六进制字符串
|
|
1990
|
-
* @returns 字节数组
|
|
1991
|
-
*/
|
|
1992
|
-
static hexStringToByteArray(s) {
|
|
1993
|
-
if (!s || s.length <= 0) {
|
|
1994
|
-
return null;
|
|
1995
|
-
}
|
|
1996
|
-
s = s.trim();
|
|
1997
|
-
const len = s.length;
|
|
1998
|
-
const data = new Uint8Array(len / 2);
|
|
1999
|
-
for (let i = 0; i < len - 1; i += 2) {
|
|
2000
|
-
try {
|
|
2001
|
-
const high = parseInt(s.charAt(i), 16);
|
|
2002
|
-
const low = parseInt(s.charAt(i + 1), 16);
|
|
2003
|
-
data[i / 2] = (high << 4) + low;
|
|
2004
|
-
}
|
|
2005
|
-
catch (e) {
|
|
2006
|
-
console.log('hexStringToByteArray: ' + e);
|
|
2007
|
-
}
|
|
2341
|
+
boundary.isIsolated = true;
|
|
2008
2342
|
}
|
|
2009
|
-
return data;
|
|
2010
2343
|
}
|
|
2344
|
+
return boundaryData;
|
|
2011
2345
|
}
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
// const result2 = FormatUtils.bytes2Int(data2, 2, true); // 应该返回 0x12345678
|
|
2021
|
-
// // 示例3:读取2字节整数
|
|
2022
|
-
// const data3 = new Uint8Array([0x12, 0x34]);
|
|
2023
|
-
// const result3 = FormatUtils.bytes2Int(data3, 0, false, 2); // 应该返回 0x3412
|
|
2024
|
-
// console.log(`Result1: 0x${result1.toString(16)}`);
|
|
2025
|
-
// console.log(`Result2: 0x${result2.toString(16)}`);
|
|
2026
|
-
// console.log(`Result3: 0x${result3.toString(16)}`);
|
|
2027
|
-
// // 示例4:十六进制字符串转换
|
|
2028
|
-
// const hexString = "12345678";
|
|
2029
|
-
// const byteArray = FormatUtils.hexStringToByteArray(hexString);
|
|
2030
|
-
// const backToHex = FormatUtils.bytesToHex(byteArray);
|
|
2031
|
-
// console.log(`Original: ${hexString}`);
|
|
2032
|
-
// console.log(`To bytes: ${byteArray}`);
|
|
2033
|
-
// console.log(`Back to hex: ${backToHex}`);
|
|
2034
|
-
// }
|
|
2346
|
+
|
|
2347
|
+
var RealTimeDataType;
|
|
2348
|
+
(function (RealTimeDataType) {
|
|
2349
|
+
RealTimeDataType[RealTimeDataType["LOCATION"] = 1] = "LOCATION";
|
|
2350
|
+
RealTimeDataType[RealTimeDataType["PROCESS"] = 2] = "PROCESS";
|
|
2351
|
+
RealTimeDataType[RealTimeDataType["PARTITION"] = 3] = "PARTITION";
|
|
2352
|
+
RealTimeDataType[RealTimeDataType["STATUS"] = 4] = "STATUS";
|
|
2353
|
+
})(RealTimeDataType || (RealTimeDataType = {}));
|
|
2035
2354
|
|
|
2036
2355
|
/**
|
|
2037
2356
|
* 射线法判断点是否在多边形内部
|
|
@@ -2127,50 +2446,6 @@ const getPartitionId = (partitionBoundary, postureX, postureY) => {
|
|
|
2127
2446
|
})?.id;
|
|
2128
2447
|
return partitionId;
|
|
2129
2448
|
};
|
|
2130
|
-
/**
|
|
2131
|
-
*
|
|
2132
|
-
* 支持分区割草后的解析,加入了分区数量、分区列表
|
|
2133
|
-
* 8 割草路径类型
|
|
2134
|
-
* 4 割草启动类型 0:自动 1:手动
|
|
2135
|
-
* 4 当前割草边界id
|
|
2136
|
-
* 4 当前边界割草百分比 比如9800----98%
|
|
2137
|
-
* @returns
|
|
2138
|
-
*/
|
|
2139
|
-
const parseMapWorkPosition = (mapWorkPosition) => {
|
|
2140
|
-
let isMowing = false;
|
|
2141
|
-
let mowStartType = null;
|
|
2142
|
-
let currentMowBoundaryId = null;
|
|
2143
|
-
let currentMowProgress = null;
|
|
2144
|
-
const bytes = new Uint8Array(mapWorkPosition.match(/.{1,2}/g)?.map((byte) => parseInt(byte, 16)) || []);
|
|
2145
|
-
if (mapWorkPosition.length >= 8) {
|
|
2146
|
-
// 以下两种状态认为是割草中
|
|
2147
|
-
// 1. action为8,且subAction为6
|
|
2148
|
-
// 2. action为5
|
|
2149
|
-
const action = FormatUtils.bytes2Int(bytes, 0, true);
|
|
2150
|
-
if (action === ACTION_BOUNDARY_TASK) {
|
|
2151
|
-
const subAction = FormatUtils.bytes2Int(bytes, 4, true);
|
|
2152
|
-
if (subAction === ACTION_BLOCK_TRANSFER) {
|
|
2153
|
-
// action=8且subAction=6:边界任务中的块转移
|
|
2154
|
-
isMowing = true;
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
else if (action === ACTION_BLOCK_COVER) {
|
|
2158
|
-
// action=5:块覆盖割草
|
|
2159
|
-
isMowing = true;
|
|
2160
|
-
}
|
|
2161
|
-
if (mapWorkPosition.length >= 16) {
|
|
2162
|
-
mowStartType = FormatUtils.bytes2Int(bytes, 8, true);
|
|
2163
|
-
currentMowBoundaryId = FormatUtils.bytes2Int(bytes, 12, true);
|
|
2164
|
-
currentMowProgress = FormatUtils.bytes2Int(bytes, 16, true);
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
return {
|
|
2168
|
-
isMowing,
|
|
2169
|
-
mowStartType,
|
|
2170
|
-
currentMowBoundaryId,
|
|
2171
|
-
currentMowProgress,
|
|
2172
|
-
};
|
|
2173
|
-
};
|
|
2174
2449
|
/**
|
|
2175
2450
|
* 处理实时数据的消息,这里的实时数据消息有两种,一种是实时轨迹,一种是割草进度,其中这两种下发的时间频次不一样
|
|
2176
2451
|
* 实时轨迹的路径需要依靠割草进度时候的割草状态判断,目前只能根据上一次获取到的割草进度的状态来处理,如果一开始没有割草的状态,则默认为不割草,后续会根据割草进度来更新
|
|
@@ -2178,7 +2453,7 @@ const parseMapWorkPosition = (mapWorkPosition) => {
|
|
|
2178
2453
|
* @param param0
|
|
2179
2454
|
* @returns
|
|
2180
2455
|
*/
|
|
2181
|
-
const
|
|
2456
|
+
const handleMultipleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundary, }) => {
|
|
2182
2457
|
// 先将数据进行倒排,这样好插入数据
|
|
2183
2458
|
if (realTimeData.length > 0) {
|
|
2184
2459
|
realTimeData.reverse();
|
|
@@ -2194,7 +2469,6 @@ const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundar
|
|
|
2194
2469
|
// 割草轨迹
|
|
2195
2470
|
const { postureX, postureY, vehicleState } = item;
|
|
2196
2471
|
const currentPartitionId = getPartitionId(partitionBoundary, Number(postureX), Number(postureY));
|
|
2197
|
-
console.log('currentPartitionId===', currentPartitionId);
|
|
2198
2472
|
if (currentPartitionId && newPathData?.[currentPartitionId]) {
|
|
2199
2473
|
const currentPathData = newPathData[currentPartitionId];
|
|
2200
2474
|
newPathData[currentPartitionId] = {
|
|
@@ -2206,7 +2480,7 @@ const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundar
|
|
|
2206
2480
|
postureY: Number(postureY),
|
|
2207
2481
|
knifeRotation: mowingStatus && vehicleState === RobotStatus.MOWING ? '01' : '00', // "knifeRotation": "01",//刀盘是否转动 00-否 01-是
|
|
2208
2482
|
// knifeRotation: '01', // "knifeRotation": "01",//刀盘是否转动 00-否 01-是
|
|
2209
|
-
pathType: '
|
|
2483
|
+
pathType: '', //"pathType": "01",//路径类型 : 00-巡边 01-弓字型割草 02-地图测试 03-转移路径 04-避障路径 05-恢复/脱困路径
|
|
2210
2484
|
partitionId: currentPartitionId.toString(), // TODO:不知道为什么这里的id需要是字符串类型?
|
|
2211
2485
|
},
|
|
2212
2486
|
],
|
|
@@ -2215,21 +2489,20 @@ const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundar
|
|
|
2215
2489
|
}
|
|
2216
2490
|
else if (item.type === REAL_TIME_DATA_TYPE.PROCESS) {
|
|
2217
2491
|
// 割草进度
|
|
2218
|
-
const {
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
}
|
|
2492
|
+
const { action, subAction, currentMowBoundary, currentMowProgress } = item;
|
|
2493
|
+
// 设置状态
|
|
2494
|
+
if ((action === ACTION_BOUNDARY_TASK && subAction && subAction === ACTION_BLOCK_TRANSFER) ||
|
|
2495
|
+
action === ACTION_BLOCK_COVER) {
|
|
2496
|
+
mowingStatus = true;
|
|
2497
|
+
}
|
|
2498
|
+
else {
|
|
2499
|
+
mowingStatus = false;
|
|
2500
|
+
}
|
|
2501
|
+
const currentPartitionId = currentMowBoundary ? currentMowBoundary.toString() : null;
|
|
2502
|
+
if (currentMowProgress && currentPartitionId && newPathData?.[currentPartitionId]) {
|
|
2503
|
+
newPathData[currentPartitionId].partitionPercentage = currentMowProgress / 100;
|
|
2504
|
+
newPathData[currentPartitionId].finishedArea =
|
|
2505
|
+
(newPathData[currentPartitionId].area * currentMowProgress) / 10000;
|
|
2233
2506
|
}
|
|
2234
2507
|
}
|
|
2235
2508
|
});
|
|
@@ -2238,6 +2511,39 @@ const handleRealTimeData = ({ realTimeData, isMowing, pathData, partitionBoundar
|
|
|
2238
2511
|
isMowing: mowingStatus,
|
|
2239
2512
|
};
|
|
2240
2513
|
};
|
|
2514
|
+
/**
|
|
2515
|
+
* 根据实时数据,获取到割草状态
|
|
2516
|
+
* @param realTimeData 实时数据
|
|
2517
|
+
* @param isMowing 上一次的割草状态
|
|
2518
|
+
* @returns 新的割草状态
|
|
2519
|
+
*/
|
|
2520
|
+
const getProcessMowingDataFromRealTimeData = ({ realTimeData, isMowing, pathData, }) => {
|
|
2521
|
+
let newMowingStatus = isMowing;
|
|
2522
|
+
let newPathData = pathData || {};
|
|
2523
|
+
// 找到返回的第一个实时进度的点
|
|
2524
|
+
const firstProcessData = realTimeData.find((item) => item.type === RealTimeDataType.PROCESS);
|
|
2525
|
+
if (firstProcessData) {
|
|
2526
|
+
// console.log('firstProcessData==', firstProcessData);
|
|
2527
|
+
const { action, subAction, currentMowBoundary, currentMowProgress } = firstProcessData;
|
|
2528
|
+
// 设置状态
|
|
2529
|
+
if ((action === ACTION_BOUNDARY_TASK && subAction && subAction === ACTION_BLOCK_TRANSFER) ||
|
|
2530
|
+
action === ACTION_BLOCK_COVER) {
|
|
2531
|
+
newMowingStatus = true;
|
|
2532
|
+
}
|
|
2533
|
+
else {
|
|
2534
|
+
newMowingStatus = false;
|
|
2535
|
+
}
|
|
2536
|
+
if (currentMowBoundary && newPathData?.[currentMowBoundary]) {
|
|
2537
|
+
newPathData[currentMowBoundary].partitionPercentage = currentMowProgress / 100;
|
|
2538
|
+
newPathData[currentMowBoundary].finishedArea =
|
|
2539
|
+
(newPathData[currentMowBoundary].area * currentMowProgress) / 10000;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
return {
|
|
2543
|
+
isMowing: newMowingStatus,
|
|
2544
|
+
pathData: newPathData,
|
|
2545
|
+
};
|
|
2546
|
+
};
|
|
2241
2547
|
|
|
2242
2548
|
var iMower = "data:image/png;base64,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";
|
|
2243
2549
|
|
|
@@ -2251,13 +2557,15 @@ var hNoPosition = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEoAAABKCAYAAAA
|
|
|
2251
2557
|
|
|
2252
2558
|
var hDisabled = "data:image/png;base64,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";
|
|
2253
2559
|
|
|
2560
|
+
var x3Edger = "data:image/png;base64,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";
|
|
2561
|
+
|
|
2254
2562
|
var x3Mower = "data:image/png;base64,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";
|
|
2255
2563
|
|
|
2256
2564
|
var x3NoPosition = "data:image/png;base64,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";
|
|
2257
2565
|
|
|
2258
2566
|
var x3Disabled = "data:image/png;base64,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";
|
|
2259
2567
|
|
|
2260
|
-
function getMowerImageByModal(mowerModal) {
|
|
2568
|
+
function getMowerImageByModal(mowerModal, hasEdger) {
|
|
2261
2569
|
if (mowerModal.includes('i')) {
|
|
2262
2570
|
return iMower;
|
|
2263
2571
|
}
|
|
@@ -2265,7 +2573,7 @@ function getMowerImageByModal(mowerModal) {
|
|
|
2265
2573
|
return hMower;
|
|
2266
2574
|
}
|
|
2267
2575
|
else if (mowerModal.includes('x3')) {
|
|
2268
|
-
return x3Mower;
|
|
2576
|
+
return hasEdger ? x3Edger : x3Mower;
|
|
2269
2577
|
}
|
|
2270
2578
|
return iMower;
|
|
2271
2579
|
}
|
|
@@ -2293,10 +2601,12 @@ function getNoPositionMowerImageByModal(mowerModal) {
|
|
|
2293
2601
|
}
|
|
2294
2602
|
return iNoPosition;
|
|
2295
2603
|
}
|
|
2296
|
-
function getMowerImage(positonConfig) {
|
|
2297
|
-
|
|
2604
|
+
function getMowerImage(positonConfig, modelType, hasEdger) {
|
|
2605
|
+
if (!positonConfig)
|
|
2606
|
+
return '';
|
|
2607
|
+
const model = modelType?.toLowerCase() || 'i';
|
|
2298
2608
|
const state = positonConfig.vehicleState;
|
|
2299
|
-
const mowerImage = getMowerImageByModal(model);
|
|
2609
|
+
const mowerImage = getMowerImageByModal(model, hasEdger);
|
|
2300
2610
|
const disabledImage = getDisabledMowerImageByModal(model);
|
|
2301
2611
|
const noPositionImage = getNoPositionMowerImageByModal(model);
|
|
2302
2612
|
const positonOutOfRange = isOutOfRange(positonConfig);
|
|
@@ -2331,9 +2641,9 @@ function isInvalidPosition(positonConfig) {
|
|
|
2331
2641
|
}
|
|
2332
2642
|
function isOutOfRange(positonConfig) {
|
|
2333
2643
|
return (positonConfig.postureX != null &&
|
|
2334
|
-
Math.abs(positonConfig.postureX) > 1000
|
|
2644
|
+
Math.abs(positonConfig.postureX) > 1000 &&
|
|
2335
2645
|
positonConfig.postureY != null &&
|
|
2336
|
-
Math.abs(positonConfig.postureY) > 1000
|
|
2646
|
+
Math.abs(positonConfig.postureY) > 1000);
|
|
2337
2647
|
}
|
|
2338
2648
|
|
|
2339
2649
|
/** Detect free variable `global` from Node.js. */
|
|
@@ -4399,7 +4709,7 @@ function cloneBuffer(buffer, isDeep) {
|
|
|
4399
4709
|
}
|
|
4400
4710
|
|
|
4401
4711
|
/** Built-in value references. */
|
|
4402
|
-
var Uint8Array
|
|
4712
|
+
var Uint8Array = root.Uint8Array;
|
|
4403
4713
|
|
|
4404
4714
|
/**
|
|
4405
4715
|
* Creates a clone of `arrayBuffer`.
|
|
@@ -4410,7 +4720,7 @@ var Uint8Array$1 = root.Uint8Array;
|
|
|
4410
4720
|
*/
|
|
4411
4721
|
function cloneArrayBuffer(arrayBuffer) {
|
|
4412
4722
|
var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
|
|
4413
|
-
new Uint8Array
|
|
4723
|
+
new Uint8Array(result).set(new Uint8Array(arrayBuffer));
|
|
4414
4724
|
return result;
|
|
4415
4725
|
}
|
|
4416
4726
|
|
|
@@ -4761,6 +5071,36 @@ var merge = createAssigner(function(object, source, srcIndex) {
|
|
|
4761
5071
|
*/
|
|
4762
5072
|
var round = createRound('round');
|
|
4763
5073
|
|
|
5074
|
+
/**
|
|
5075
|
+
* 工具模块类型定义
|
|
5076
|
+
*/
|
|
5077
|
+
/**
|
|
5078
|
+
* 路径段类型枚举
|
|
5079
|
+
*/
|
|
5080
|
+
var PathSegmentType;
|
|
5081
|
+
(function (PathSegmentType) {
|
|
5082
|
+
PathSegmentType["EDGE"] = "edge";
|
|
5083
|
+
PathSegmentType["MOWING"] = "mowing";
|
|
5084
|
+
PathSegmentType["TRANS"] = "trans";
|
|
5085
|
+
})(PathSegmentType || (PathSegmentType = {}));
|
|
5086
|
+
/**
|
|
5087
|
+
* 单位类型枚举
|
|
5088
|
+
*/
|
|
5089
|
+
var UnitsType;
|
|
5090
|
+
(function (UnitsType) {
|
|
5091
|
+
UnitsType["Metric"] = "Metric";
|
|
5092
|
+
UnitsType["Imperial"] = "Imperial";
|
|
5093
|
+
})(UnitsType || (UnitsType = {}));
|
|
5094
|
+
/**
|
|
5095
|
+
* 面积单位类型枚举
|
|
5096
|
+
*/
|
|
5097
|
+
var UnitsAreaType;
|
|
5098
|
+
(function (UnitsAreaType) {
|
|
5099
|
+
UnitsAreaType["SQUARE_METER"] = "m\u00B2";
|
|
5100
|
+
UnitsAreaType["SQUARE_FOOT"] = "ft\u00B2";
|
|
5101
|
+
UnitsAreaType["ACRE"] = "ac";
|
|
5102
|
+
})(UnitsAreaType || (UnitsAreaType = {}));
|
|
5103
|
+
|
|
4764
5104
|
/**
|
|
4765
5105
|
* 默认航向相对于canvas的偏移角度: 航向默认是东
|
|
4766
5106
|
*/
|
|
@@ -4788,10 +5128,6 @@ function radToDegree(radian) {
|
|
|
4788
5128
|
function distance(x1, y1, x2, y2) {
|
|
4789
5129
|
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
|
|
4790
5130
|
}
|
|
4791
|
-
// 计算前进方向和车头的夹角
|
|
4792
|
-
function calAngle(x1, y1, x2, y2) {
|
|
4793
|
-
return Math.atan2((y2 - y1), (x2 - x1));
|
|
4794
|
-
}
|
|
4795
5131
|
const mathRound = (value, decimals = 2) => {
|
|
4796
5132
|
return Number.isInteger(value) ? value : round(value, decimals);
|
|
4797
5133
|
};
|
|
@@ -4838,17 +5174,6 @@ function formatNumberWithMetricPrefix(value, round = true, decimals = 2) {
|
|
|
4838
5174
|
return `${mathFn(value / 1000000000, decimals)}B`;
|
|
4839
5175
|
}
|
|
4840
5176
|
}
|
|
4841
|
-
var UnitsType;
|
|
4842
|
-
(function (UnitsType) {
|
|
4843
|
-
UnitsType["Metric"] = "metric";
|
|
4844
|
-
UnitsType["Imperial"] = "imperial";
|
|
4845
|
-
})(UnitsType || (UnitsType = {}));
|
|
4846
|
-
var UnitsAreaType;
|
|
4847
|
-
(function (UnitsAreaType) {
|
|
4848
|
-
UnitsAreaType["SQUARE_METER"] = "m\u00B2";
|
|
4849
|
-
UnitsAreaType["SQUARE_FOOT"] = "ft\u00B2";
|
|
4850
|
-
UnitsAreaType["ACRE"] = "ac";
|
|
4851
|
-
})(UnitsAreaType || (UnitsAreaType = {}));
|
|
4852
5177
|
/**
|
|
4853
5178
|
* 转换割草面积的方法
|
|
4854
5179
|
* @param area 面积数值(单位:m²)
|
|
@@ -5037,6 +5362,25 @@ class BoundaryBorderLayer extends BaseLayer {
|
|
|
5037
5362
|
this.scale = 1;
|
|
5038
5363
|
this.level = LAYER_LEVELS.BOUNDARY_BORDER; // 中等层级
|
|
5039
5364
|
this.type = LAYER_DEFAULT_TYPE.BOUNDARY_BORDER;
|
|
5365
|
+
this.boudaryBorderPaths = {};
|
|
5366
|
+
this.mowingBoundarys = [];
|
|
5367
|
+
}
|
|
5368
|
+
/**
|
|
5369
|
+
* 设置当前割草任务的边界
|
|
5370
|
+
*/
|
|
5371
|
+
setMowingBoundarys(mowingBoundarys) {
|
|
5372
|
+
if (!mowingBoundarys) {
|
|
5373
|
+
this.mowingBoundarys = this.elements?.map(item => item?.originalData?.id);
|
|
5374
|
+
}
|
|
5375
|
+
else {
|
|
5376
|
+
this.mowingBoundarys = mowingBoundarys;
|
|
5377
|
+
}
|
|
5378
|
+
}
|
|
5379
|
+
/**
|
|
5380
|
+
* 获取当前割草任务的边界
|
|
5381
|
+
*/
|
|
5382
|
+
getMowingBoundarys() {
|
|
5383
|
+
return this.mowingBoundarys;
|
|
5040
5384
|
}
|
|
5041
5385
|
/**
|
|
5042
5386
|
* SVG渲染方法
|
|
@@ -5046,18 +5390,38 @@ class BoundaryBorderLayer extends BaseLayer {
|
|
|
5046
5390
|
return;
|
|
5047
5391
|
}
|
|
5048
5392
|
this.scale = scale;
|
|
5049
|
-
//
|
|
5393
|
+
// 将元素分为两组:非割草边界和割草边界
|
|
5394
|
+
const nonMowingElements = [];
|
|
5395
|
+
const mowingElements = [];
|
|
5396
|
+
// 只处理边界边框类型的元素
|
|
5050
5397
|
for (const element of this.elements) {
|
|
5051
5398
|
if (element.type === 'boundary_border') {
|
|
5052
|
-
|
|
5399
|
+
const { originalData } = element;
|
|
5400
|
+
const { id } = originalData || {};
|
|
5401
|
+
// 检查是否为割草边界
|
|
5402
|
+
if (this.mowingBoundarys.includes(Number(id))) {
|
|
5403
|
+
mowingElements.push(element);
|
|
5404
|
+
}
|
|
5405
|
+
else {
|
|
5406
|
+
nonMowingElements.push(element);
|
|
5407
|
+
}
|
|
5053
5408
|
}
|
|
5054
5409
|
}
|
|
5410
|
+
// 先渲染非割草边界
|
|
5411
|
+
for (const element of nonMowingElements) {
|
|
5412
|
+
this.renderBoundaryBorder(svgGroup, element);
|
|
5413
|
+
}
|
|
5414
|
+
// 再渲染割草边界(放在最后)
|
|
5415
|
+
for (const element of mowingElements) {
|
|
5416
|
+
this.renderBoundaryBorder(svgGroup, element);
|
|
5417
|
+
}
|
|
5055
5418
|
}
|
|
5056
5419
|
/**
|
|
5057
5420
|
* 渲染边界边框
|
|
5058
5421
|
*/
|
|
5059
5422
|
renderBoundaryBorder(svgGroup, element) {
|
|
5060
|
-
const { coordinates, style } = element;
|
|
5423
|
+
const { coordinates, style, originalData } = element;
|
|
5424
|
+
const { id } = originalData || {};
|
|
5061
5425
|
if (coordinates.length < 2)
|
|
5062
5426
|
return;
|
|
5063
5427
|
// 1. 先遍历所有的coordinates,把所有点分为若干段的path
|
|
@@ -5068,20 +5432,21 @@ class BoundaryBorderLayer extends BaseLayer {
|
|
|
5068
5432
|
return;
|
|
5069
5433
|
if (segment.type === 2) {
|
|
5070
5434
|
// type=2: 直接添加到svgGroup中
|
|
5071
|
-
this.createDirectPath(svgGroup, segment.points, style);
|
|
5435
|
+
this.createDirectPath(svgGroup, segment.points, style, id);
|
|
5072
5436
|
}
|
|
5073
5437
|
else if (segment.type === 1) {
|
|
5074
5438
|
// type=1: 使用PathMeasure逻辑生成平行路径
|
|
5075
5439
|
// this.createDirectPath(svgGroup, segment.points, style);
|
|
5076
|
-
this.createParallelPathsWithMeasure(svgGroup, segment.points, style);
|
|
5440
|
+
this.createParallelPathsWithMeasure(svgGroup, segment.points, style, id);
|
|
5077
5441
|
}
|
|
5078
5442
|
});
|
|
5079
5443
|
}
|
|
5080
5444
|
/**
|
|
5081
5445
|
* 创建直接路径(type=2)
|
|
5082
5446
|
*/
|
|
5083
|
-
createDirectPath(svgGroup, points, style) {
|
|
5084
|
-
const
|
|
5447
|
+
createDirectPath(svgGroup, points, style, id) {
|
|
5448
|
+
const isMowing = this.mowingBoundarys.includes(Number(id));
|
|
5449
|
+
const strokeColor = isMowing ? style.mowingLineColor : style.lineColor;
|
|
5085
5450
|
const lineWidth = dp2px(style.lineWidth || 3);
|
|
5086
5451
|
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
5087
5452
|
// 构建路径数据
|
|
@@ -5104,13 +5469,18 @@ class BoundaryBorderLayer extends BaseLayer {
|
|
|
5104
5469
|
path.setAttribute('opacity', (style.opacity || 1).toString());
|
|
5105
5470
|
path.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
5106
5471
|
path.classList.add('vector-boundary-solid');
|
|
5472
|
+
if (!this.boudaryBorderPaths[id]) {
|
|
5473
|
+
this.boudaryBorderPaths[id] = [];
|
|
5474
|
+
}
|
|
5475
|
+
this.boudaryBorderPaths[id].push(path);
|
|
5107
5476
|
svgGroup.appendChild(path);
|
|
5108
5477
|
}
|
|
5109
5478
|
/**
|
|
5110
5479
|
* 使用PathMeasure逻辑创建平行路径(type=1)
|
|
5111
5480
|
*/
|
|
5112
|
-
createParallelPathsWithMeasure(svgGroup, points, style) {
|
|
5113
|
-
const
|
|
5481
|
+
createParallelPathsWithMeasure(svgGroup, points, style, id) {
|
|
5482
|
+
const isMowing = this.mowingBoundarys.includes(Number(id));
|
|
5483
|
+
const strokeColor = isMowing ? style.mowingLineColor : style.lineColor;
|
|
5114
5484
|
const lineWidth = dp2px(style.lineWidth || 3);
|
|
5115
5485
|
// 获取当前SVG的缩放级别,计算固定屏幕像素间距
|
|
5116
5486
|
const fixedScreenDistance = lineWidth; // 固定的屏幕像素距离
|
|
@@ -5133,6 +5503,10 @@ class BoundaryBorderLayer extends BaseLayer {
|
|
|
5133
5503
|
// 或者可以根据当前缩放级别动态计算dash array
|
|
5134
5504
|
path.style.strokeDasharray = `${lineWidth}px ${lineWidth * 2}px`;
|
|
5135
5505
|
path.classList.add(`vector-boundary-parallel-${index + 1}`);
|
|
5506
|
+
if (!this.boudaryBorderPaths[id]) {
|
|
5507
|
+
this.boudaryBorderPaths[id] = [];
|
|
5508
|
+
}
|
|
5509
|
+
this.boudaryBorderPaths[id].push(path);
|
|
5136
5510
|
svgGroup.appendChild(path);
|
|
5137
5511
|
});
|
|
5138
5512
|
}
|
|
@@ -5222,6 +5596,9 @@ class BoundaryBorderLayer extends BaseLayer {
|
|
|
5222
5596
|
}
|
|
5223
5597
|
return segments;
|
|
5224
5598
|
}
|
|
5599
|
+
resetPaths() {
|
|
5600
|
+
this.boudaryBorderPaths = {};
|
|
5601
|
+
}
|
|
5225
5602
|
}
|
|
5226
5603
|
|
|
5227
5604
|
var antennaOneOnline = "data:image/png;base64,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";
|
|
@@ -5434,7 +5811,7 @@ class DrawLayer extends BaseLayer {
|
|
|
5434
5811
|
this.pointLayer,
|
|
5435
5812
|
this.svgElementLayer,
|
|
5436
5813
|
this.visionOffLayer,
|
|
5437
|
-
];
|
|
5814
|
+
]?.filter((layer) => layer.getElements().length > 0);
|
|
5438
5815
|
}
|
|
5439
5816
|
getPathLayers() {
|
|
5440
5817
|
return this.pathLayer;
|
|
@@ -5449,6 +5826,14 @@ class BoundaryDataBuilder {
|
|
|
5449
5826
|
* 创建边界元素数据
|
|
5450
5827
|
*/
|
|
5451
5828
|
static create(type, coordinates, style) {
|
|
5829
|
+
const len = coordinates?.length || 0;
|
|
5830
|
+
const firstPoint = coordinates?.[0];
|
|
5831
|
+
const lastPoint = coordinates?.[len - 1];
|
|
5832
|
+
const isClosed = firstPoint?.[0] === lastPoint?.[0] && firstPoint?.[1] === lastPoint?.[1];
|
|
5833
|
+
// 如果地图没有闭合,则手动新增闭合点,避免border最后一部分没有闭合的情况
|
|
5834
|
+
if (!isClosed) {
|
|
5835
|
+
coordinates.push([firstPoint?.[0], firstPoint?.[1], lastPoint?.[2]]);
|
|
5836
|
+
}
|
|
5452
5837
|
return {
|
|
5453
5838
|
type,
|
|
5454
5839
|
coordinates,
|
|
@@ -5763,7 +6148,6 @@ class MapDataProcessor {
|
|
|
5763
6148
|
if (mapData.vision_off_areas && mapData.vision_off_areas.length > 0) {
|
|
5764
6149
|
allElements.push(...mapData.vision_off_areas);
|
|
5765
6150
|
}
|
|
5766
|
-
console.log('allElements', allElements);
|
|
5767
6151
|
// 按照元素类型分组并设置不同的层级
|
|
5768
6152
|
return this.createLayeredMapData(allElements);
|
|
5769
6153
|
}
|
|
@@ -5852,8 +6236,13 @@ class MapDataProcessor {
|
|
|
5852
6236
|
// 为ObstacleData创建兼容的MapElement接口
|
|
5853
6237
|
const mapElement = element;
|
|
5854
6238
|
const obstacleElement = ObstacleDataBuilder.fromMapElement(mapElement, this.mapConfig.obstacle);
|
|
5855
|
-
if (obstacleElement)
|
|
6239
|
+
if (obstacleElement) {
|
|
5856
6240
|
result.push(obstacleElement);
|
|
6241
|
+
const { addObstacles } = useSubBoundaryBorderStore.getState();
|
|
6242
|
+
addObstacles(`obstacle-${obstacleElement.originalData.id}`, {
|
|
6243
|
+
...obstacleElement,
|
|
6244
|
+
});
|
|
6245
|
+
}
|
|
5857
6246
|
}
|
|
5858
6247
|
catch (error) {
|
|
5859
6248
|
console.warn(`Error processing OBSTACLE element:`, element, error);
|
|
@@ -5914,9 +6303,13 @@ class MapDataProcessor {
|
|
|
5914
6303
|
element.direction !== undefined) {
|
|
5915
6304
|
const mapElement = element;
|
|
5916
6305
|
const svgElement = SvgElementDataBuilder.fromMapElement(mapElement, this.mapConfig.doodle);
|
|
5917
|
-
|
|
5918
|
-
if (svgElement)
|
|
6306
|
+
if (svgElement) {
|
|
5919
6307
|
result.push(svgElement);
|
|
6308
|
+
const { addSvgElements } = useSubBoundaryBorderStore.getState();
|
|
6309
|
+
addSvgElements(`time-limit-obstacle-${svgElement.originalData.id}`, {
|
|
6310
|
+
...svgElement,
|
|
6311
|
+
});
|
|
6312
|
+
}
|
|
5920
6313
|
}
|
|
5921
6314
|
// 如果有points数据,按传统方式绘制
|
|
5922
6315
|
else if ('points' in element &&
|
|
@@ -5925,8 +6318,13 @@ class MapDataProcessor {
|
|
|
5925
6318
|
element.points.length >= 3) {
|
|
5926
6319
|
const mapElement = element;
|
|
5927
6320
|
const polygonElement = ObstacleDataBuilder.createTimeLimitObstacle(mapElement, this.mapConfig.obstacle);
|
|
5928
|
-
if (polygonElement)
|
|
6321
|
+
if (polygonElement) {
|
|
5929
6322
|
result.push(polygonElement);
|
|
6323
|
+
const { addObstacles } = useSubBoundaryBorderStore.getState();
|
|
6324
|
+
addObstacles(`time-limit-obstacle-${polygonElement.originalData.id}`, {
|
|
6325
|
+
...polygonElement,
|
|
6326
|
+
});
|
|
6327
|
+
}
|
|
5930
6328
|
}
|
|
5931
6329
|
}
|
|
5932
6330
|
catch (error) {
|
|
@@ -5971,35 +6369,15 @@ class PathDataBuilder {
|
|
|
5971
6369
|
style,
|
|
5972
6370
|
};
|
|
5973
6371
|
}
|
|
5974
|
-
// /**
|
|
5975
|
-
// * 创建隧道元素数据
|
|
5976
|
-
// */
|
|
5977
|
-
// static createTunnel(element: MapElement): DrawElement | null {
|
|
5978
|
-
// const convertedPoints = convertPointsFormat(element.tunnel?.points || element.points);
|
|
5979
|
-
// if (!convertedPoints || convertedPoints.length < 2) return null;
|
|
5980
|
-
// return this.create(convertedPoints, DEFAULT_STYLES.PATH_EDGE);
|
|
5981
|
-
// }
|
|
5982
|
-
// /**
|
|
5983
|
-
// * 创建充电桩内部隧道路径(用于CHARGING_PILE元素中的tunnel.points)
|
|
5984
|
-
// */
|
|
5985
|
-
// static createChargingPileTunnelPath(element: MapElement): DrawElement | null {
|
|
5986
|
-
// if (!element.tunnel || !element.tunnel.points) return null;
|
|
5987
|
-
// const convertedTunnelPoints = convertPointsFormat(element.tunnel.points);
|
|
5988
|
-
// if (!convertedTunnelPoints || convertedTunnelPoints.length < 2) return null;
|
|
5989
|
-
// return this.create(convertedTunnelPoints, {
|
|
5990
|
-
// strokeColor: DEFAULT_STYLES.CHANNEL.strokeColor,
|
|
5991
|
-
// lineWidth: 2,
|
|
5992
|
-
// opacity: 1.0
|
|
5993
|
-
// });
|
|
5994
|
-
// }
|
|
5995
6372
|
/**
|
|
5996
6373
|
* 创建边缘路径
|
|
5997
6374
|
*/
|
|
5998
6375
|
static createEdgePath(points, config) {
|
|
5999
|
-
const
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6376
|
+
const convertedPoints = convertPointsFormat(points);
|
|
6377
|
+
const drawElement = this.create(convertedPoints, {
|
|
6378
|
+
lineColor: config?.color,
|
|
6379
|
+
lineWidth: config?.lineWidth,
|
|
6380
|
+
opacity: config?.opacity,
|
|
6003
6381
|
});
|
|
6004
6382
|
drawElement.originalData = points;
|
|
6005
6383
|
return drawElement;
|
|
@@ -6008,10 +6386,11 @@ class PathDataBuilder {
|
|
|
6008
6386
|
* 创建割草路径
|
|
6009
6387
|
*/
|
|
6010
6388
|
static createMowingPath(points, config) {
|
|
6011
|
-
const
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6389
|
+
const convertedPoints = convertPointsFormat(points);
|
|
6390
|
+
const drawElement = this.create(convertedPoints, {
|
|
6391
|
+
lineColor: config?.color,
|
|
6392
|
+
lineWidth: config?.lineWidth,
|
|
6393
|
+
opacity: config?.opacity,
|
|
6015
6394
|
});
|
|
6016
6395
|
drawElement.originalData = points;
|
|
6017
6396
|
return drawElement;
|
|
@@ -6020,11 +6399,11 @@ class PathDataBuilder {
|
|
|
6020
6399
|
* 创建传输路径
|
|
6021
6400
|
*/
|
|
6022
6401
|
static createTransPath(points, config) {
|
|
6023
|
-
const
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
opacity: config?.opacity
|
|
6402
|
+
const convertedPoints = convertPointsFormat(points);
|
|
6403
|
+
const drawElement = this.create(convertedPoints, {
|
|
6404
|
+
lineColor: config?.color,
|
|
6405
|
+
lineWidth: config?.lineWidth,
|
|
6406
|
+
opacity: config?.opacity,
|
|
6028
6407
|
});
|
|
6029
6408
|
drawElement.originalData = points;
|
|
6030
6409
|
return drawElement;
|
|
@@ -6043,55 +6422,59 @@ class PathDataProcessor {
|
|
|
6043
6422
|
if (!pathData || typeof pathData !== 'object') {
|
|
6044
6423
|
return [];
|
|
6045
6424
|
}
|
|
6046
|
-
// 获取所有分区的路径数据
|
|
6047
|
-
const allPathItems = Object.values(pathData).reduce((acc, partitionData) => {
|
|
6048
|
-
if (partitionData && partitionData.points && partitionData.points.length > 0) {
|
|
6049
|
-
acc.push(...partitionData.points);
|
|
6050
|
-
}
|
|
6051
|
-
return acc;
|
|
6052
|
-
}, []);
|
|
6053
6425
|
// 合并配置
|
|
6054
6426
|
const config = mapConfig.path;
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
const
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
6079
|
-
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6427
|
+
const result = Object.keys(pathData).map((key) => {
|
|
6428
|
+
const id = Number(key);
|
|
6429
|
+
const points = pathData[key].points || [];
|
|
6430
|
+
// 使用Python相同的逻辑:按线段分组而不是按点分组
|
|
6431
|
+
const pathSegments = createPathSegmentsByType(points);
|
|
6432
|
+
const elements = [];
|
|
6433
|
+
// 处理边缘路径段
|
|
6434
|
+
for (const segment of pathSegments.edge) {
|
|
6435
|
+
// 转换 Point[] 为 number[][]
|
|
6436
|
+
const points = segment.points.map((point) => [point.x, point.y]);
|
|
6437
|
+
const element = PathDataBuilder.createEdgePath(points, {
|
|
6438
|
+
lineWidth: config.lineWidth,
|
|
6439
|
+
color: config.edgeLineColor,
|
|
6440
|
+
opacity: config.opacity,
|
|
6441
|
+
});
|
|
6442
|
+
element.originalData = points;
|
|
6443
|
+
element.pathType = PathSegmentType.EDGE;
|
|
6444
|
+
elements.push(element);
|
|
6445
|
+
}
|
|
6446
|
+
// 处理割草路径段
|
|
6447
|
+
for (const segment of pathSegments.mowing) {
|
|
6448
|
+
// 转换 Point[] 为 number[][]
|
|
6449
|
+
const points = segment.points.map((point) => [point.x, point.y]);
|
|
6450
|
+
const element = PathDataBuilder.createMowingPath(points, {
|
|
6451
|
+
lineWidth: config.lineWidth,
|
|
6452
|
+
color: config.mowedLineColor,
|
|
6453
|
+
opacity: config.opacity,
|
|
6454
|
+
});
|
|
6455
|
+
element.originalData = points;
|
|
6456
|
+
element.pathType = PathSegmentType.MOWING;
|
|
6457
|
+
elements.push(element);
|
|
6458
|
+
}
|
|
6459
|
+
// 处理传输路径段(只有在showTransPaths为true时才添加)
|
|
6460
|
+
for (const segment of pathSegments.trans) {
|
|
6461
|
+
// 转换 Point[] 为 number[][]
|
|
6462
|
+
const points = segment.points.map((point) => [point.x, point.y]);
|
|
6463
|
+
const element = PathDataBuilder.createTransPath(points, {
|
|
6464
|
+
lineWidth: config.lineWidth,
|
|
6465
|
+
color: config.transLineColor,
|
|
6466
|
+
opacity: config.opacity,
|
|
6467
|
+
});
|
|
6468
|
+
element.originalData = points;
|
|
6469
|
+
element.pathType = PathSegmentType.TRANS;
|
|
6470
|
+
elements.push(element);
|
|
6471
|
+
}
|
|
6472
|
+
return {
|
|
6473
|
+
id,
|
|
6474
|
+
elements,
|
|
6475
|
+
};
|
|
6476
|
+
});
|
|
6477
|
+
return result;
|
|
6095
6478
|
}
|
|
6096
6479
|
}
|
|
6097
6480
|
|
|
@@ -6100,15 +6483,19 @@ class PathDataProcessor {
|
|
|
6100
6483
|
* 专门处理边界标签的创建、定位和管理
|
|
6101
6484
|
*/
|
|
6102
6485
|
class BoundaryLabelsManager {
|
|
6103
|
-
constructor(svgView, boundaryData) {
|
|
6486
|
+
constructor(svgView, boundaryData, { unitType, language }) {
|
|
6104
6487
|
this.container = null;
|
|
6105
6488
|
this.overlayDiv = null;
|
|
6106
6489
|
this.globalClickHandler = null;
|
|
6107
6490
|
// 当前展开的边界id
|
|
6108
6491
|
this.currentExpandedBoundaryId = null;
|
|
6492
|
+
// 旋转角度
|
|
6493
|
+
this.rotation = 0;
|
|
6109
6494
|
this.svgView = svgView;
|
|
6110
6495
|
this.boundaryData = boundaryData;
|
|
6111
6496
|
this.initializeContainer();
|
|
6497
|
+
this.unitType = unitType;
|
|
6498
|
+
this.language = language;
|
|
6112
6499
|
}
|
|
6113
6500
|
/**
|
|
6114
6501
|
* 初始化容器
|
|
@@ -6174,7 +6561,7 @@ class BoundaryLabelsManager {
|
|
|
6174
6561
|
labelDiv.setAttribute('data-boundary-id', boundary.id.toString());
|
|
6175
6562
|
// 样式设置
|
|
6176
6563
|
labelDiv.style.position = 'absolute';
|
|
6177
|
-
labelDiv.style.backgroundColor = 'rgba(30, 30, 31, 0.
|
|
6564
|
+
labelDiv.style.backgroundColor = 'rgba(30, 30, 31, 0.6)';
|
|
6178
6565
|
labelDiv.style.color = 'rgba(255, 255, 255, 1)';
|
|
6179
6566
|
labelDiv.style.padding = '6px';
|
|
6180
6567
|
labelDiv.style.borderRadius = '12px';
|
|
@@ -6182,17 +6569,17 @@ class BoundaryLabelsManager {
|
|
|
6182
6569
|
labelDiv.style.fontWeight = 'bold';
|
|
6183
6570
|
labelDiv.style.whiteSpace = 'nowrap';
|
|
6184
6571
|
labelDiv.style.maxWidth = '220px';
|
|
6185
|
-
labelDiv.style.transform =
|
|
6572
|
+
labelDiv.style.transform = `translate(-50%, -50%) rotate(${-this.rotation}deg)`;
|
|
6186
6573
|
labelDiv.style.pointerEvents = 'auto';
|
|
6187
6574
|
labelDiv.style.boxShadow = '0 2px 8px rgba(0,0,0,0.4)';
|
|
6188
6575
|
labelDiv.style.cursor = 'pointer';
|
|
6189
|
-
labelDiv.style.transition = 'background-color 0.2s ease
|
|
6576
|
+
labelDiv.style.transition = 'background-color 0.2s ease';
|
|
6190
6577
|
labelDiv.style.userSelect = 'none';
|
|
6191
6578
|
labelDiv.style.zIndex = BoundaryLabelsManager.Z_INDEX.DEFAULT.toString();
|
|
6192
6579
|
// 计算进度
|
|
6193
|
-
const progress = boundary.finishedArea && boundary.area
|
|
6194
|
-
`${Math.
|
|
6195
|
-
'0%';
|
|
6580
|
+
const progress = boundary.finishedArea && boundary.area
|
|
6581
|
+
? `${Math.floor((boundary.finishedArea / boundary.area) * 100)}%`
|
|
6582
|
+
: '0%';
|
|
6196
6583
|
// 基础内容(始终显示)
|
|
6197
6584
|
const baseContent = document.createElement('div');
|
|
6198
6585
|
baseContent.className = 'boundary-label-base';
|
|
@@ -6204,15 +6591,19 @@ class BoundaryLabelsManager {
|
|
|
6204
6591
|
extendedContent.style.marginTop = '6px';
|
|
6205
6592
|
extendedContent.style.fontSize = '11px';
|
|
6206
6593
|
extendedContent.style.opacity = '0.9';
|
|
6207
|
-
extendedContent.style.display =
|
|
6594
|
+
extendedContent.style.display =
|
|
6595
|
+
this.currentExpandedBoundaryId === boundary.id ? 'block' : 'none';
|
|
6208
6596
|
extendedContent.style.borderTop = '1px solid rgba(255,255,255,0.2)';
|
|
6209
6597
|
extendedContent.style.paddingTop = '6px';
|
|
6598
|
+
const boundaryLayer = this.svgView.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
|
|
6599
|
+
const mowingBoundarys = boundaryLayer.getMowingBoundarys();
|
|
6210
6600
|
// 面积信息
|
|
6211
|
-
const totalArea = convertAreaByUnits(boundary.area || 0,
|
|
6212
|
-
const finishedArea = convertAreaByUnits(boundary.finishedArea || 0,
|
|
6601
|
+
const totalArea = convertAreaByUnits(boundary.area || 0, this.unitType);
|
|
6602
|
+
const finishedArea = convertAreaByUnits(boundary.finishedArea || 0, this.unitType);
|
|
6213
6603
|
const coverageText = `Coverage: ${finishedArea.value}/${totalArea.value}`;
|
|
6604
|
+
const isMowing = mowingBoundarys.includes(boundary.id);
|
|
6214
6605
|
// 日期信息
|
|
6215
|
-
const dateText = formatBoundaryDateText(boundary.endTime || 0);
|
|
6606
|
+
const dateText = formatBoundaryDateText(isMowing ? Date.now() / 1000 : boundary.endTime || 0);
|
|
6216
6607
|
const covertHtml = `<div style="margin-bottom: 3px; font-weight: bold;">${coverageText}</div>`;
|
|
6217
6608
|
const dateHtml = `<div>${dateText}</div>`;
|
|
6218
6609
|
extendedContent.innerHTML = boundary.finishedArea > 0 ? `${covertHtml}${dateHtml}` : covertHtml;
|
|
@@ -6275,7 +6666,6 @@ class BoundaryLabelsManager {
|
|
|
6275
6666
|
this.collapseOtherLabels(boundaryId);
|
|
6276
6667
|
// 展开当前标签
|
|
6277
6668
|
extendedContent.style.display = 'block';
|
|
6278
|
-
labelDiv.style.whiteSpace = 'normal';
|
|
6279
6669
|
this.currentExpandedBoundaryId = boundaryId;
|
|
6280
6670
|
}
|
|
6281
6671
|
/**
|
|
@@ -6285,7 +6675,7 @@ class BoundaryLabelsManager {
|
|
|
6285
6675
|
if (!this.container)
|
|
6286
6676
|
return;
|
|
6287
6677
|
const allLabels = this.container.querySelectorAll('.boundary-label');
|
|
6288
|
-
allLabels.forEach(label => {
|
|
6678
|
+
allLabels.forEach((label) => {
|
|
6289
6679
|
const labelBoundaryId = label.getAttribute('data-boundary-id');
|
|
6290
6680
|
if (labelBoundaryId && labelBoundaryId !== boundaryId.toString()) {
|
|
6291
6681
|
const extendedContent = label.querySelector('.boundary-label-extended');
|
|
@@ -6331,7 +6721,6 @@ class BoundaryLabelsManager {
|
|
|
6331
6721
|
// 计算边界中心点的地图坐标
|
|
6332
6722
|
const mapCenter = this.calculatePolygonCentroid(boundary.points);
|
|
6333
6723
|
if (!mapCenter) {
|
|
6334
|
-
console.warn(`BoundaryLabelsManager: 无法计算边界 ${boundary.name} (ID: ${boundary.id}) 的中心点`);
|
|
6335
6724
|
return;
|
|
6336
6725
|
}
|
|
6337
6726
|
// 直接使用预计算的数据进行坐标转换
|
|
@@ -6388,7 +6777,7 @@ class BoundaryLabelsManager {
|
|
|
6388
6777
|
if (points.length < 3)
|
|
6389
6778
|
return null;
|
|
6390
6779
|
// 过滤有效点
|
|
6391
|
-
const validPoints = points.filter(point => point.length >= 2);
|
|
6780
|
+
const validPoints = points.filter((point) => point.length >= 2);
|
|
6392
6781
|
if (validPoints.length < 3)
|
|
6393
6782
|
return null;
|
|
6394
6783
|
// 确保多边形是封闭的(如果不是,自动闭合)
|
|
@@ -6416,7 +6805,6 @@ class BoundaryLabelsManager {
|
|
|
6416
6805
|
area = area / 2;
|
|
6417
6806
|
// 如果面积为0,回退到简单的平均值计算
|
|
6418
6807
|
if (Math.abs(area) < 1e-10) {
|
|
6419
|
-
console.warn('BoundaryLabelsManager: 多边形面积为0,使用平均值计算重心');
|
|
6420
6808
|
return this.calculateAverageCenter(validPoints);
|
|
6421
6809
|
}
|
|
6422
6810
|
centroidX = centroidX / (6 * area);
|
|
@@ -6437,7 +6825,7 @@ class BoundaryLabelsManager {
|
|
|
6437
6825
|
});
|
|
6438
6826
|
return {
|
|
6439
6827
|
x: sumX / points.length,
|
|
6440
|
-
y: sumY / points.length
|
|
6828
|
+
y: sumY / points.length,
|
|
6441
6829
|
};
|
|
6442
6830
|
}
|
|
6443
6831
|
/**
|
|
@@ -6511,12 +6899,6 @@ class BoundaryLabelsManager {
|
|
|
6511
6899
|
this.container.style.display = visible ? 'block' : 'none';
|
|
6512
6900
|
}
|
|
6513
6901
|
}
|
|
6514
|
-
/**
|
|
6515
|
-
* 获取边界数据
|
|
6516
|
-
*/
|
|
6517
|
-
getBoundaryData() {
|
|
6518
|
-
return this.boundaryData;
|
|
6519
|
-
}
|
|
6520
6902
|
/**
|
|
6521
6903
|
* 根据ID获取特定边界的标签元素
|
|
6522
6904
|
*/
|
|
@@ -6533,7 +6915,7 @@ class BoundaryLabelsManager {
|
|
|
6533
6915
|
return;
|
|
6534
6916
|
const allLabels = this.container.querySelectorAll('.boundary-label');
|
|
6535
6917
|
this.currentExpandedBoundaryId = null;
|
|
6536
|
-
allLabels.forEach(label => {
|
|
6918
|
+
allLabels.forEach((label) => {
|
|
6537
6919
|
const extendedContent = label.querySelector('.boundary-label-extended');
|
|
6538
6920
|
if (extendedContent) {
|
|
6539
6921
|
extendedContent.style.display = 'none';
|
|
@@ -6548,7 +6930,7 @@ class BoundaryLabelsManager {
|
|
|
6548
6930
|
if (!this.container)
|
|
6549
6931
|
return;
|
|
6550
6932
|
const allLabels = this.container.querySelectorAll('.boundary-label');
|
|
6551
|
-
allLabels.forEach(label => {
|
|
6933
|
+
allLabels.forEach((label) => {
|
|
6552
6934
|
const labelElement = label;
|
|
6553
6935
|
if (interactive) {
|
|
6554
6936
|
labelElement.style.pointerEvents = 'auto';
|
|
@@ -6567,34 +6949,26 @@ class BoundaryLabelsManager {
|
|
|
6567
6949
|
});
|
|
6568
6950
|
}
|
|
6569
6951
|
/**
|
|
6570
|
-
*
|
|
6571
|
-
|
|
6572
|
-
resetZIndex() {
|
|
6573
|
-
this.resetLabelZIndex();
|
|
6574
|
-
}
|
|
6575
|
-
/**
|
|
6576
|
-
* 获取层级常量(静态方法)
|
|
6577
|
-
*/
|
|
6578
|
-
static getZIndexConstants() {
|
|
6579
|
-
return BoundaryLabelsManager.Z_INDEX;
|
|
6580
|
-
}
|
|
6581
|
-
/**
|
|
6582
|
-
* 调试方法:检查当前所有边界标签的层级
|
|
6952
|
+
* 设置标签旋转角度,使其与地图旋转相反,保持水平状态
|
|
6953
|
+
* @param rotation 地图的旋转角度(度)
|
|
6583
6954
|
*/
|
|
6584
|
-
|
|
6955
|
+
setRotation(rotation) {
|
|
6585
6956
|
if (!this.container)
|
|
6586
6957
|
return;
|
|
6587
|
-
|
|
6588
|
-
|
|
6958
|
+
this.rotation = rotation;
|
|
6959
|
+
const labels = this.container.querySelectorAll('.boundary-label');
|
|
6960
|
+
labels.forEach((label) => {
|
|
6589
6961
|
const labelElement = label;
|
|
6590
|
-
|
|
6962
|
+
// 应用与地图旋转相反的旋转,保持标签水平
|
|
6963
|
+
const counterRotation = -rotation;
|
|
6964
|
+
labelElement.style.transform = `translate(-50%, -50%) rotate(${counterRotation}deg)`;
|
|
6591
6965
|
});
|
|
6592
6966
|
}
|
|
6593
6967
|
}
|
|
6594
6968
|
// 简化的层级定义
|
|
6595
6969
|
BoundaryLabelsManager.Z_INDEX = {
|
|
6596
6970
|
DEFAULT: 900, // 默认层级
|
|
6597
|
-
ACTIVE: 9999 // 点击激活时的高层级
|
|
6971
|
+
ACTIVE: 9999, // 点击激活时的高层级
|
|
6598
6972
|
};
|
|
6599
6973
|
|
|
6600
6974
|
/**
|
|
@@ -6607,6 +6981,10 @@ class ChargingPileManager {
|
|
|
6607
6981
|
this.container = null;
|
|
6608
6982
|
this.overlayDiv = null;
|
|
6609
6983
|
this.pileElements = new Map();
|
|
6984
|
+
// 原始旋转角度
|
|
6985
|
+
this.originalRotation = 0;
|
|
6986
|
+
// 旋转角度
|
|
6987
|
+
this.rotation = 0;
|
|
6610
6988
|
this.svgView = svgView;
|
|
6611
6989
|
this.initializeContainer();
|
|
6612
6990
|
}
|
|
@@ -6661,7 +7039,9 @@ class ChargingPileManager {
|
|
|
6661
7039
|
// 将弧度转换为角度
|
|
6662
7040
|
const angle = (direction * 180) / Math.PI;
|
|
6663
7041
|
const rotationDegree = 270 - angle; // 坐标系转换
|
|
6664
|
-
|
|
7042
|
+
this.originalRotation = rotationDegree;
|
|
7043
|
+
const actualRotation = rotationDegree - this.rotation;
|
|
7044
|
+
pileDiv.style.transform = `translate(-50%, -50%) rotate(${actualRotation}deg)`;
|
|
6665
7045
|
// 添加动画
|
|
6666
7046
|
// this.addChargingPileAnimation(pileDiv, imgElement, rotationDegree);
|
|
6667
7047
|
// 生成唯一ID
|
|
@@ -6710,30 +7090,15 @@ class ChargingPileManager {
|
|
|
6710
7090
|
updatePositions() {
|
|
6711
7091
|
if (!this.overlayDiv || !this.container)
|
|
6712
7092
|
return;
|
|
6713
|
-
|
|
6714
|
-
const divWidth = divRect.width;
|
|
6715
|
-
const divHeight = divRect.height;
|
|
6716
|
-
// 获取SVG的viewBox
|
|
6717
|
-
const svg = this.svgView.getSVG();
|
|
6718
|
-
if (!svg)
|
|
6719
|
-
return;
|
|
6720
|
-
const viewBox = svg.viewBox.baseVal;
|
|
6721
|
-
const viewBoxData = {
|
|
6722
|
-
x: viewBox.x,
|
|
6723
|
-
y: viewBox.y,
|
|
6724
|
-
width: viewBox.width,
|
|
6725
|
-
height: viewBox.height,
|
|
6726
|
-
};
|
|
6727
|
-
this.updatePositionsWithPrecomputedData(divWidth, divHeight, viewBoxData);
|
|
7093
|
+
this.updatePositionsWithPrecomputedData();
|
|
6728
7094
|
}
|
|
6729
7095
|
/**
|
|
6730
7096
|
* 使用预计算数据更新位置
|
|
6731
7097
|
*/
|
|
6732
|
-
updatePositionsWithPrecomputedData(
|
|
7098
|
+
updatePositionsWithPrecomputedData() {
|
|
6733
7099
|
this.chargingPileElements.forEach((element, _index) => {
|
|
6734
7100
|
const center = element.coordinates[0];
|
|
6735
|
-
const pixelPosition = this.convertMapCoordinateToPixelWithPrecomputedData(center[0], center[1]
|
|
6736
|
-
console.log('updatePositionsWithPrecomputedData----->', element, pixelPosition, divWidth, divHeight, viewBox);
|
|
7101
|
+
const pixelPosition = this.convertMapCoordinateToPixelWithPrecomputedData(center[0], center[1]);
|
|
6737
7102
|
if (pixelPosition) {
|
|
6738
7103
|
const pileId = `pile_${center[0]}_${center[1]}`;
|
|
6739
7104
|
const pileElement = this.pileElements.get(pileId);
|
|
@@ -6747,7 +7112,15 @@ class ChargingPileManager {
|
|
|
6747
7112
|
/**
|
|
6748
7113
|
* 使用预计算数据进行坐标转换
|
|
6749
7114
|
*/
|
|
6750
|
-
convertMapCoordinateToPixelWithPrecomputedData(mapX, mapY
|
|
7115
|
+
convertMapCoordinateToPixelWithPrecomputedData(mapX, mapY) {
|
|
7116
|
+
// 获取叠加层div的CSS尺寸
|
|
7117
|
+
const divWidth = parseFloat(this.overlayDiv.style.width) || this.overlayDiv.offsetWidth;
|
|
7118
|
+
const divHeight = parseFloat(this.overlayDiv.style.height) || this.overlayDiv.offsetHeight;
|
|
7119
|
+
// 获取SVG的viewBox
|
|
7120
|
+
const svg = this.svgView.getSVG();
|
|
7121
|
+
if (!svg)
|
|
7122
|
+
return { x: 0, y: 0 };
|
|
7123
|
+
const viewBox = svg.viewBox.baseVal;
|
|
6751
7124
|
// 计算地图坐标在viewBox中的相对位置
|
|
6752
7125
|
const relativeX = (mapX - viewBox.x) / viewBox.width;
|
|
6753
7126
|
const relativeY = (mapY - viewBox.y) / viewBox.height;
|
|
@@ -6766,26 +7139,6 @@ class ChargingPileManager {
|
|
|
6766
7139
|
this.container.innerHTML = '';
|
|
6767
7140
|
}
|
|
6768
7141
|
}
|
|
6769
|
-
/**
|
|
6770
|
-
* 设置可见性
|
|
6771
|
-
*/
|
|
6772
|
-
setVisible(visible) {
|
|
6773
|
-
if (this.container) {
|
|
6774
|
-
this.container.style.display = visible ? 'block' : 'none';
|
|
6775
|
-
}
|
|
6776
|
-
}
|
|
6777
|
-
/**
|
|
6778
|
-
* 获取充电桩数量
|
|
6779
|
-
*/
|
|
6780
|
-
getElementCount() {
|
|
6781
|
-
return this.chargingPileElements.length;
|
|
6782
|
-
}
|
|
6783
|
-
/**
|
|
6784
|
-
* 充电桩不需要动态层级调整(为了接口统一而保留)
|
|
6785
|
-
*/
|
|
6786
|
-
resetZIndex() {
|
|
6787
|
-
// 充电桩层级始终保持固定,无需重置
|
|
6788
|
-
}
|
|
6789
7142
|
/**
|
|
6790
7143
|
* 销毁管理器
|
|
6791
7144
|
*/
|
|
@@ -6797,10 +7150,21 @@ class ChargingPileManager {
|
|
|
6797
7150
|
this.container = null;
|
|
6798
7151
|
this.overlayDiv = null;
|
|
6799
7152
|
}
|
|
7153
|
+
/**
|
|
7154
|
+
* 设置充电桩旋转角度
|
|
7155
|
+
*/
|
|
7156
|
+
setRotation(rotation) {
|
|
7157
|
+
this.rotation = rotation;
|
|
7158
|
+
const allContainers = this.container.querySelectorAll('.charging-pile');
|
|
7159
|
+
allContainers.forEach((container) => {
|
|
7160
|
+
const pileElement = container;
|
|
7161
|
+
pileElement.style.transform = `translate(-50%, -50%) rotate(${this.originalRotation - this.rotation}deg)`;
|
|
7162
|
+
});
|
|
7163
|
+
}
|
|
6800
7164
|
}
|
|
6801
7165
|
// 简化的层级定义 - 充电桩只需要一个固定层级
|
|
6802
7166
|
ChargingPileManager.Z_INDEX = {
|
|
6803
|
-
CHARGING_PILE:
|
|
7167
|
+
CHARGING_PILE: 750, // 充电桩图标固定层级
|
|
6804
7168
|
};
|
|
6805
7169
|
|
|
6806
7170
|
/**
|
|
@@ -6818,6 +7182,8 @@ class AntennaManager {
|
|
|
6818
7182
|
this.globalClickHandler = null;
|
|
6819
7183
|
this.antennaTooltipFlag = false;
|
|
6820
7184
|
this.singleAntennaTooltipFlag = false;
|
|
7185
|
+
// 旋转角度
|
|
7186
|
+
this.rotation = 0;
|
|
6821
7187
|
this.svgView = svgView;
|
|
6822
7188
|
this.initializeContainer();
|
|
6823
7189
|
this.setupGlobalClickHandler();
|
|
@@ -6890,7 +7256,7 @@ class AntennaManager {
|
|
|
6890
7256
|
const antennaContainer = document.createElement('div');
|
|
6891
7257
|
antennaContainer.className = 'antenna-container-item';
|
|
6892
7258
|
antennaContainer.style.position = 'absolute';
|
|
6893
|
-
antennaContainer.style.transform =
|
|
7259
|
+
antennaContainer.style.transform = `translate(-50%, -50%) rotate(${-this.rotation}deg)`;
|
|
6894
7260
|
antennaContainer.style.pointerEvents = 'auto';
|
|
6895
7261
|
antennaContainer.style.zIndex = AntennaManager.Z_INDEX.DEFAULT.toString();
|
|
6896
7262
|
antennaContainer.setAttribute('data-antenna-id', antennaData.type.toString());
|
|
@@ -6931,8 +7297,6 @@ class AntennaManager {
|
|
|
6931
7297
|
this.singleAntennaTooltipFlag = true;
|
|
6932
7298
|
}
|
|
6933
7299
|
this.expandTooltip(antennaContainer);
|
|
6934
|
-
// 调试:检查层级变化
|
|
6935
|
-
this.debugCheckZIndex();
|
|
6936
7300
|
});
|
|
6937
7301
|
// 添加悬停效果
|
|
6938
7302
|
antennaDiv.addEventListener('mouseenter', () => {
|
|
@@ -7182,39 +7546,6 @@ class AntennaManager {
|
|
|
7182
7546
|
this.container.innerHTML = '';
|
|
7183
7547
|
}
|
|
7184
7548
|
}
|
|
7185
|
-
/**
|
|
7186
|
-
* 设置可见性
|
|
7187
|
-
*/
|
|
7188
|
-
setVisible(visible) {
|
|
7189
|
-
if (this.container) {
|
|
7190
|
-
this.container.style.display = visible ? 'block' : 'none';
|
|
7191
|
-
}
|
|
7192
|
-
}
|
|
7193
|
-
/**
|
|
7194
|
-
* 获取天线数量
|
|
7195
|
-
*/
|
|
7196
|
-
getElementCount() {
|
|
7197
|
-
return this.antennaElements.length;
|
|
7198
|
-
}
|
|
7199
|
-
/**
|
|
7200
|
-
* 重置天线层级(公共方法)
|
|
7201
|
-
*/
|
|
7202
|
-
resetZIndex() {
|
|
7203
|
-
this.resetAntennaZIndex();
|
|
7204
|
-
}
|
|
7205
|
-
/**
|
|
7206
|
-
* 获取层级常量(静态方法)
|
|
7207
|
-
*/
|
|
7208
|
-
static getZIndexConstants() {
|
|
7209
|
-
return AntennaManager.Z_INDEX;
|
|
7210
|
-
}
|
|
7211
|
-
/**
|
|
7212
|
-
* 调试方法:检查当前所有天线的层级
|
|
7213
|
-
*/
|
|
7214
|
-
debugCheckZIndex() {
|
|
7215
|
-
if (!this.container)
|
|
7216
|
-
return;
|
|
7217
|
-
}
|
|
7218
7549
|
/**
|
|
7219
7550
|
* 销毁管理器
|
|
7220
7551
|
*/
|
|
@@ -7231,10 +7562,21 @@ class AntennaManager {
|
|
|
7231
7562
|
this.container = null;
|
|
7232
7563
|
this.overlayDiv = null;
|
|
7233
7564
|
}
|
|
7565
|
+
/**
|
|
7566
|
+
* 设置天线旋转角度
|
|
7567
|
+
*/
|
|
7568
|
+
setRotation(rotation) {
|
|
7569
|
+
this.rotation = rotation;
|
|
7570
|
+
const allContainers = this.container.querySelectorAll('.antenna-container-item');
|
|
7571
|
+
allContainers.forEach((container) => {
|
|
7572
|
+
const antennaContainer = container;
|
|
7573
|
+
antennaContainer.style.transform = `translate(-50%, -50%) rotate(${-this.rotation}deg)`;
|
|
7574
|
+
});
|
|
7575
|
+
}
|
|
7234
7576
|
}
|
|
7235
7577
|
// 简化的层级定义
|
|
7236
7578
|
AntennaManager.Z_INDEX = {
|
|
7237
|
-
DEFAULT:
|
|
7579
|
+
DEFAULT: 800, // 默认层级
|
|
7238
7580
|
ACTIVE: 9999, // 点击激活时的高层级
|
|
7239
7581
|
};
|
|
7240
7582
|
|
|
@@ -7285,8 +7627,8 @@ const EDIT_BEHAVIOR = {
|
|
|
7285
7627
|
SUCCESS_MESSAGE_DURATION_MS: 3000,
|
|
7286
7628
|
};
|
|
7287
7629
|
|
|
7288
|
-
class
|
|
7289
|
-
constructor(svgView,
|
|
7630
|
+
class MowerPositionManager {
|
|
7631
|
+
constructor(svgView, mowerPositionConfig, modelType, overlayDiv, onAnimationComplete, onMowingPositionChange) {
|
|
7290
7632
|
this.container = null;
|
|
7291
7633
|
this.overlayDiv = null;
|
|
7292
7634
|
this.mowerElement = null;
|
|
@@ -7303,9 +7645,11 @@ class MowerPostionManager {
|
|
|
7303
7645
|
this.deltaPosition = null;
|
|
7304
7646
|
this.onlyUpdateTheta = false;
|
|
7305
7647
|
this.svgView = svgView;
|
|
7306
|
-
this.
|
|
7648
|
+
this.mowerPositionConfig = mowerPositionConfig;
|
|
7649
|
+
this.modelType = modelType;
|
|
7307
7650
|
this.overlayDiv = overlayDiv;
|
|
7308
7651
|
this.onAnimationComplete = onAnimationComplete;
|
|
7652
|
+
this.onMowingPositionChange = onMowingPositionChange;
|
|
7309
7653
|
this.initializeContainer();
|
|
7310
7654
|
}
|
|
7311
7655
|
get animationFlag() {
|
|
@@ -7351,12 +7695,13 @@ class MowerPostionManager {
|
|
|
7351
7695
|
imgElement.style.height = '100%';
|
|
7352
7696
|
imgElement.style.objectFit = 'contain';
|
|
7353
7697
|
// 获取图片源
|
|
7354
|
-
const imageSrc = getMowerImage(this.
|
|
7698
|
+
const imageSrc = getMowerImage(this.mowerPositionConfig);
|
|
7355
7699
|
if (imageSrc) {
|
|
7356
7700
|
imgElement.src = imageSrc;
|
|
7357
7701
|
}
|
|
7358
7702
|
this.mowerElement.appendChild(imgElement);
|
|
7359
7703
|
this.container.appendChild(this.mowerElement);
|
|
7704
|
+
this.updatePosition(this.mowerPositionConfig);
|
|
7360
7705
|
}
|
|
7361
7706
|
/**
|
|
7362
7707
|
* 设置叠加层div引用(用于坐标转换)
|
|
@@ -7370,54 +7715,69 @@ class MowerPostionManager {
|
|
|
7370
7715
|
getElement() {
|
|
7371
7716
|
return this.container;
|
|
7372
7717
|
}
|
|
7718
|
+
//
|
|
7719
|
+
setEdger(edger) {
|
|
7720
|
+
this.hasEdger = edger;
|
|
7721
|
+
}
|
|
7373
7722
|
/**
|
|
7374
7723
|
* 根据最后一次有效的位置更新数据
|
|
7375
7724
|
*/
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7725
|
+
updatePositionByLastPosition(chargingPilesPositionConfig) {
|
|
7726
|
+
if (!chargingPilesPositionConfig)
|
|
7727
|
+
return;
|
|
7728
|
+
this.mowerPositionConfig = chargingPilesPositionConfig;
|
|
7729
|
+
const positonOutOfRange = isOutOfRange(chargingPilesPositionConfig);
|
|
7730
|
+
const positionInValid = isInvalidPosition(chargingPilesPositionConfig);
|
|
7731
|
+
let postureX = 0;
|
|
7732
|
+
let postureY = 0;
|
|
7733
|
+
let postureTheta = 0;
|
|
7379
7734
|
const lastPosition = this.lastPosition;
|
|
7380
|
-
if (
|
|
7381
|
-
|
|
7735
|
+
if (positonOutOfRange || positionInValid) {
|
|
7736
|
+
postureX = lastPosition?.x || 0;
|
|
7737
|
+
postureY = lastPosition?.y || 0;
|
|
7738
|
+
postureTheta = lastPosition?.rotation || 0;
|
|
7382
7739
|
}
|
|
7383
7740
|
else {
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
postureY: lastPosition.y,
|
|
7388
|
-
postureTheta: lastPosition.rotation,
|
|
7389
|
-
}, 0);
|
|
7741
|
+
postureX = chargingPilesPositionConfig.postureX || 0;
|
|
7742
|
+
postureY = chargingPilesPositionConfig.postureY || 0;
|
|
7743
|
+
postureTheta = chargingPilesPositionConfig.postureTheta || 0;
|
|
7390
7744
|
}
|
|
7745
|
+
// 检查是否需要更新图片
|
|
7746
|
+
this.updateMowerImage(chargingPilesPositionConfig);
|
|
7747
|
+
// 立即更新位置
|
|
7748
|
+
this.setElementPosition(postureX, postureY, postureTheta);
|
|
7391
7749
|
}
|
|
7392
7750
|
/**
|
|
7393
7751
|
* 更新割草机位置
|
|
7394
7752
|
*/
|
|
7395
|
-
updatePosition(
|
|
7396
|
-
console.log('updatePosition----->', positonConfig);
|
|
7753
|
+
updatePosition(positionConfig, animationTime = 0) {
|
|
7397
7754
|
// 检查是否需要更新图片
|
|
7398
|
-
this.updateMowerImage(
|
|
7755
|
+
this.updateMowerImage(positionConfig);
|
|
7756
|
+
// 更新配置
|
|
7757
|
+
this.mowerPositionConfig = positionConfig;
|
|
7758
|
+
const postureX = positionConfig?.postureX || this.lastPosition?.x || 0;
|
|
7759
|
+
const postureY = positionConfig?.postureY || this.lastPosition?.y || 0;
|
|
7760
|
+
const postureTheta = positionConfig?.postureTheta || this.lastPosition?.rotation || 0;
|
|
7399
7761
|
// 停止当前动画(如果有)
|
|
7400
7762
|
this.stopAnimation();
|
|
7401
7763
|
// 第一个点
|
|
7402
7764
|
if (!this.currentPosition) {
|
|
7403
7765
|
this.currentPosition = {
|
|
7404
|
-
x:
|
|
7405
|
-
y:
|
|
7406
|
-
rotation:
|
|
7766
|
+
x: postureX,
|
|
7767
|
+
y: postureY,
|
|
7768
|
+
rotation: postureTheta,
|
|
7407
7769
|
};
|
|
7408
7770
|
this.setElementPosition(this.currentPosition.x, this.currentPosition.y, this.currentPosition.rotation);
|
|
7409
7771
|
return;
|
|
7410
7772
|
}
|
|
7411
7773
|
// 根据动画时长决定更新方式
|
|
7412
7774
|
if (animationTime > 0) {
|
|
7413
|
-
this.startAnimationToPosition(
|
|
7775
|
+
this.startAnimationToPosition(positionConfig, animationTime);
|
|
7414
7776
|
}
|
|
7415
7777
|
else {
|
|
7416
7778
|
// 立即更新位置
|
|
7417
|
-
this.setElementPosition(
|
|
7779
|
+
this.setElementPosition(positionConfig.postureX, positionConfig.postureY, positionConfig.postureTheta);
|
|
7418
7780
|
}
|
|
7419
|
-
// 更新配置
|
|
7420
|
-
this.mowerPositonConfig = positonConfig;
|
|
7421
7781
|
}
|
|
7422
7782
|
/**
|
|
7423
7783
|
* 更新割草机图片
|
|
@@ -7428,9 +7788,14 @@ class MowerPostionManager {
|
|
|
7428
7788
|
const imgElement = this.mowerElement.querySelector('img');
|
|
7429
7789
|
if (!imgElement)
|
|
7430
7790
|
return;
|
|
7431
|
-
const imageSrc = getMowerImage(positonConfig);
|
|
7791
|
+
const imageSrc = getMowerImage(positonConfig, this.modelType, this.hasEdger);
|
|
7432
7792
|
if (imageSrc) {
|
|
7433
7793
|
imgElement.src = imageSrc;
|
|
7794
|
+
imgElement.style.display = 'block';
|
|
7795
|
+
}
|
|
7796
|
+
else {
|
|
7797
|
+
imgElement.style.display = 'none';
|
|
7798
|
+
return;
|
|
7434
7799
|
}
|
|
7435
7800
|
}
|
|
7436
7801
|
/**
|
|
@@ -7440,7 +7805,9 @@ class MowerPostionManager {
|
|
|
7440
7805
|
const { x: pointX, y: pointY } = convertCoordinate(x, y);
|
|
7441
7806
|
const targetPixelPosition = this.convertMapCoordinateToOverlayPixel(pointX, pointY);
|
|
7442
7807
|
const targetRotation = radToDegree(theta);
|
|
7808
|
+
this.currentPosition = { x: x, y: y, rotation: theta };
|
|
7443
7809
|
this.lastPosition = { x, y, rotation: theta };
|
|
7810
|
+
// console.log('setElementPosition', x, y, theta, targetRotation, positonOutOfRange, positionValid, targetPixelPosition);
|
|
7444
7811
|
if (!this.mowerElement)
|
|
7445
7812
|
return;
|
|
7446
7813
|
this.mowerElement.style.left = `${targetPixelPosition?.x}px`;
|
|
@@ -7466,29 +7833,48 @@ class MowerPostionManager {
|
|
|
7466
7833
|
startAnimationToPosition(positionConfig, duration) {
|
|
7467
7834
|
if (!this.mowerElement || !this.currentPosition)
|
|
7468
7835
|
return;
|
|
7469
|
-
console.log('before startAnimationToPosition----->', this.startPosition, this.currentPosition, this.targetPosition, positionConfig);
|
|
7470
|
-
this.startPosition = { ...this.currentPosition, rotation: radNormalize(this.currentPosition.rotation) };
|
|
7471
7836
|
this.targetPosition = {
|
|
7472
7837
|
x: positionConfig.postureX,
|
|
7473
7838
|
y: positionConfig.postureY,
|
|
7474
7839
|
rotation: positionConfig.postureTheta,
|
|
7475
7840
|
};
|
|
7841
|
+
const isTargetPositionInvalid = isInvalidPosition({
|
|
7842
|
+
postureX: this.targetPosition.x,
|
|
7843
|
+
postureY: this.targetPosition.y,
|
|
7844
|
+
postureTheta: this.targetPosition.rotation,
|
|
7845
|
+
});
|
|
7846
|
+
const isTargetPositionOutOfRange = isOutOfRange({
|
|
7847
|
+
postureX: this.targetPosition.x,
|
|
7848
|
+
postureY: this.targetPosition.y,
|
|
7849
|
+
postureTheta: this.targetPosition.rotation,
|
|
7850
|
+
});
|
|
7851
|
+
// 如果目标坐标点不合理,则舍弃不使用
|
|
7852
|
+
if (isTargetPositionInvalid || isTargetPositionOutOfRange) {
|
|
7853
|
+
return;
|
|
7854
|
+
}
|
|
7855
|
+
this.startPosition = {
|
|
7856
|
+
...this.currentPosition,
|
|
7857
|
+
rotation: radNormalize(this.currentPosition.rotation),
|
|
7858
|
+
};
|
|
7476
7859
|
this.animationDuration = duration;
|
|
7477
7860
|
this.startTime = window.performance.now();
|
|
7478
7861
|
this.isAnimating = true;
|
|
7479
7862
|
this.onlyUpdateTheta =
|
|
7480
7863
|
distance(this.currentPosition.x, this.currentPosition.y, positionConfig.postureX, positionConfig.postureY) < 0.2;
|
|
7481
7864
|
// 为了实现倒车时,割草机图标旋转方向正确,需要计算当前位置和目标位置的夹角
|
|
7482
|
-
const calTheta = calAngle(
|
|
7865
|
+
// const calTheta = calAngle(
|
|
7866
|
+
// this.startPosition.x,
|
|
7867
|
+
// this.startPosition.y,
|
|
7868
|
+
// this.targetPosition.x,
|
|
7869
|
+
// this.targetPosition.y
|
|
7870
|
+
// );
|
|
7483
7871
|
const startTheta = this.startPosition.rotation;
|
|
7484
|
-
const targetTheta = this.
|
|
7485
|
-
console.log('begain startAnimationToPosition----->', this.startPosition, this.targetPosition, this.onlyUpdateTheta);
|
|
7872
|
+
const targetTheta = this.targetPosition.rotation;
|
|
7486
7873
|
this.deltaPosition = {
|
|
7487
7874
|
x: this.onlyUpdateTheta ? 0 : this.targetPosition.x - this.startPosition.x,
|
|
7488
7875
|
y: this.onlyUpdateTheta ? 0 : this.targetPosition.y - this.startPosition.y,
|
|
7489
|
-
rotation: radNormalize(targetTheta - startTheta)
|
|
7876
|
+
rotation: radNormalize(targetTheta - startTheta),
|
|
7490
7877
|
};
|
|
7491
|
-
console.log('deltaPosition----->', targetTheta, startTheta, calTheta, this.deltaPosition);
|
|
7492
7878
|
// 开始动画循环
|
|
7493
7879
|
this.animateStep();
|
|
7494
7880
|
}
|
|
@@ -7499,7 +7885,7 @@ class MowerPostionManager {
|
|
|
7499
7885
|
* 动画步骤
|
|
7500
7886
|
*/
|
|
7501
7887
|
animateStep() {
|
|
7502
|
-
if (!this.isAnimating || !this.
|
|
7888
|
+
if (!this.isAnimating || !this.targetPosition || !this.startPosition)
|
|
7503
7889
|
return;
|
|
7504
7890
|
const currentTime = window.performance.now();
|
|
7505
7891
|
const elapsed = currentTime - this.startTime;
|
|
@@ -7510,20 +7896,24 @@ class MowerPostionManager {
|
|
|
7510
7896
|
const currentX = this.startPosition.x + this.deltaPosition.x * easedProgress;
|
|
7511
7897
|
const currentY = this.startPosition.y + this.deltaPosition.y * easedProgress;
|
|
7512
7898
|
const currentRotation = this.startPosition.rotation + this.deltaPosition.rotation * easedProgress;
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7899
|
+
// 假设在这里进行更新路径数据
|
|
7900
|
+
if (this.onMowingPositionChange) {
|
|
7901
|
+
this.onMowingPositionChange({
|
|
7902
|
+
x: currentX,
|
|
7903
|
+
y: currentY,
|
|
7904
|
+
vehicleState: this.mowerPositionConfig?.vehicleState,
|
|
7905
|
+
});
|
|
7906
|
+
}
|
|
7907
|
+
// console.log('animateStep-->', this.startPosition, this.deltaPosition, this.targetPosition, easedProgress)
|
|
7908
|
+
this.setElementPosition(currentX, currentY, currentRotation);
|
|
7516
7909
|
// 继续动画或结束
|
|
7517
7910
|
if (progress < 1) {
|
|
7518
7911
|
// 设置当前位置
|
|
7519
|
-
this.setElementPosition(currentX, currentY, currentRotation);
|
|
7520
7912
|
this.animationId = window.requestAnimationFrame(() => this.animateStep());
|
|
7521
7913
|
}
|
|
7522
7914
|
else {
|
|
7523
|
-
this.setElementPosition(currentX, currentY, currentRotation);
|
|
7524
7915
|
// 动画完成
|
|
7525
7916
|
this.stopAnimation();
|
|
7526
|
-
console.log('end animateStep----->', this.lastPosition, this.currentPosition, this.targetPosition);
|
|
7527
7917
|
// 通知动画完成
|
|
7528
7918
|
if (this.onAnimationComplete) {
|
|
7529
7919
|
this.onAnimationComplete();
|
|
@@ -7568,12 +7958,6 @@ class MowerPostionManager {
|
|
|
7568
7958
|
this.container.style.display = visible ? 'block' : 'none';
|
|
7569
7959
|
}
|
|
7570
7960
|
}
|
|
7571
|
-
/**
|
|
7572
|
-
* 检查是否正在动画中
|
|
7573
|
-
*/
|
|
7574
|
-
getIsAnimating() {
|
|
7575
|
-
return this.isAnimating;
|
|
7576
|
-
}
|
|
7577
7961
|
/**
|
|
7578
7962
|
* 销毁管理器
|
|
7579
7963
|
*/
|
|
@@ -7589,13 +7973,86 @@ class MowerPostionManager {
|
|
|
7589
7973
|
}
|
|
7590
7974
|
}
|
|
7591
7975
|
|
|
7976
|
+
// 记录割草状态,状态变更的时候,变量不触发重新渲染
|
|
7977
|
+
const useProcessMowingState = create((set) => ({
|
|
7978
|
+
processStateIsMowing: false,
|
|
7979
|
+
updateProcessStateIsMowing: (isMowing) => set({ processStateIsMowing: isMowing }),
|
|
7980
|
+
resetProcessStateIsMowing: () => set({ processStateIsMowing: false }),
|
|
7981
|
+
}));
|
|
7982
|
+
|
|
7983
|
+
/**
|
|
7984
|
+
* 高级节流函数
|
|
7985
|
+
* @param func 要节流的函数
|
|
7986
|
+
* @param delay 延迟时间(毫秒)
|
|
7987
|
+
* @param options 配置选项
|
|
7988
|
+
* @returns 节流后的函数
|
|
7989
|
+
*/
|
|
7990
|
+
function throttleAdvanced(func, delay, options = { leading: true, trailing: true }) {
|
|
7991
|
+
let lastExecTime = 0;
|
|
7992
|
+
let timeoutId = null;
|
|
7993
|
+
let lastArgs = null;
|
|
7994
|
+
return function throttled(...args) {
|
|
7995
|
+
const currentTime = Date.now();
|
|
7996
|
+
lastArgs = args;
|
|
7997
|
+
// 如果距离上次执行的时间小于延迟时间
|
|
7998
|
+
if (currentTime - lastExecTime < delay) {
|
|
7999
|
+
// 清除之前的定时器
|
|
8000
|
+
if (timeoutId) {
|
|
8001
|
+
clearTimeout(timeoutId);
|
|
8002
|
+
}
|
|
8003
|
+
// 设置新的定时器
|
|
8004
|
+
timeoutId = setTimeout(() => {
|
|
8005
|
+
if (options.trailing && lastArgs) {
|
|
8006
|
+
lastExecTime = Date.now();
|
|
8007
|
+
func.apply(this, lastArgs);
|
|
8008
|
+
lastArgs = null;
|
|
8009
|
+
}
|
|
8010
|
+
timeoutId = null;
|
|
8011
|
+
}, delay - (currentTime - lastExecTime));
|
|
8012
|
+
}
|
|
8013
|
+
else {
|
|
8014
|
+
// 如果距离上次执行的时间已经超过延迟时间
|
|
8015
|
+
if (options.leading) {
|
|
8016
|
+
lastExecTime = currentTime;
|
|
8017
|
+
func.apply(this, args);
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
};
|
|
8021
|
+
}
|
|
8022
|
+
/**
|
|
8023
|
+
* 检测当前设备是否为移动设备
|
|
8024
|
+
* @returns {boolean} 如果是移动设备返回true,否则返回false
|
|
8025
|
+
*/
|
|
8026
|
+
function isMobileDevice() {
|
|
8027
|
+
// 确保在浏览器环境中运行
|
|
8028
|
+
if (typeof window === 'undefined' || typeof navigator === 'undefined') {
|
|
8029
|
+
return false;
|
|
8030
|
+
}
|
|
8031
|
+
// 检查用户代理字符串
|
|
8032
|
+
const userAgent = navigator.userAgent.toLowerCase();
|
|
8033
|
+
const mobileKeywords = [
|
|
8034
|
+
'android', 'webos', 'iphone', 'ipad', 'ipod',
|
|
8035
|
+
'blackberry', 'windows phone', 'mobile'
|
|
8036
|
+
];
|
|
8037
|
+
const isMobileUserAgent = mobileKeywords.some(keyword => userAgent.includes(keyword));
|
|
8038
|
+
// 检查触摸屏支持
|
|
8039
|
+
const hasTouchScreen = 'ontouchstart' in window ||
|
|
8040
|
+
(navigator.maxTouchPoints && navigator.maxTouchPoints > 0);
|
|
8041
|
+
// 检查屏幕尺寸(移动设备通常屏幕较小)
|
|
8042
|
+
const isSmallScreen = window.innerWidth <= 768;
|
|
8043
|
+
// 综合判断:用户代理包含移动设备关键词,或者有触摸屏且屏幕较小
|
|
8044
|
+
return isMobileUserAgent || (hasTouchScreen && isSmallScreen);
|
|
8045
|
+
}
|
|
8046
|
+
|
|
7592
8047
|
// Google Maps 叠加层类 - 带编辑功能
|
|
7593
8048
|
class MowerMapOverlay {
|
|
7594
|
-
constructor(bounds, mapData,
|
|
8049
|
+
constructor(bounds, mapData, partitionBoundary, mowerPositionConfig, modelType, pathData, isEditMode = false, unitType = UnitsType.Imperial, language = 'en', mapConfig = {}, antennaConfig = {}, mowPartitionData = null, defaultTransform, onMapLoad, onPathLoad, dragCallbacks) {
|
|
7595
8050
|
this.div = null;
|
|
7596
8051
|
this.svgMapView = null;
|
|
7597
8052
|
this.offscreenContainer = null;
|
|
7598
8053
|
this.overlayView = null;
|
|
8054
|
+
this.defaultTransform = { x: 0, y: 0, rotation: 0 };
|
|
8055
|
+
this.hasEdger = false;
|
|
7599
8056
|
// boundary数据
|
|
7600
8057
|
this.boundaryData = [];
|
|
7601
8058
|
// 边界标签管理器
|
|
@@ -7605,7 +8062,7 @@ class MowerMapOverlay {
|
|
|
7605
8062
|
// 天线管理器
|
|
7606
8063
|
this.antennaManager = null;
|
|
7607
8064
|
// 割草机位置管理器
|
|
7608
|
-
this.
|
|
8065
|
+
this.mowerPositionManager = null;
|
|
7609
8066
|
// 当前动画时长
|
|
7610
8067
|
this.currentAnimationTime = 0;
|
|
7611
8068
|
// 是否正在用户动画中(区分用户主动触发的动画和地图重绘)
|
|
@@ -7628,16 +8085,38 @@ class MowerMapOverlay {
|
|
|
7628
8085
|
// 初始状态记录(用于计算相对于初始状态的偏移)
|
|
7629
8086
|
this.initialOffset = { x: 0, y: 0 };
|
|
7630
8087
|
this.initialRotation = 0;
|
|
8088
|
+
this.mowPartitionData = null;
|
|
8089
|
+
this.updatePathDataByMowingPositionThrottled = throttleAdvanced(this.updatePathDataByMowingPosition, 300, {
|
|
8090
|
+
leading: true,
|
|
8091
|
+
trailing: false,
|
|
8092
|
+
});
|
|
7631
8093
|
this.bounds = bounds;
|
|
7632
8094
|
this.mapData = mapData;
|
|
8095
|
+
this.partitionBoundary = partitionBoundary;
|
|
7633
8096
|
this.pathData = pathData;
|
|
7634
8097
|
this.isEditMode = isEditMode;
|
|
8098
|
+
this.unitType = unitType;
|
|
8099
|
+
this.language = language;
|
|
7635
8100
|
this.mapConfig = mapConfig;
|
|
7636
8101
|
this.antennaConfig = antennaConfig;
|
|
7637
8102
|
this.onMapLoad = onMapLoad;
|
|
7638
8103
|
this.onPathLoad = onPathLoad;
|
|
7639
8104
|
this.dragCallbacks = dragCallbacks;
|
|
7640
|
-
this.
|
|
8105
|
+
this.mowerPositionConfig = mowerPositionConfig;
|
|
8106
|
+
this.mowPartitionData = mowPartitionData;
|
|
8107
|
+
this.modelType = modelType;
|
|
8108
|
+
// 设置默认的transform
|
|
8109
|
+
if (defaultTransform) {
|
|
8110
|
+
this.defaultTransform = {
|
|
8111
|
+
x: defaultTransform.x ?? 0,
|
|
8112
|
+
y: defaultTransform.y ?? 0,
|
|
8113
|
+
rotation: defaultTransform.rotation ?? 0,
|
|
8114
|
+
};
|
|
8115
|
+
// defaultTransform的x对应经度偏移量,y对应纬度偏移量
|
|
8116
|
+
this.latLngOffset.lng = this.defaultTransform.x;
|
|
8117
|
+
this.latLngOffset.lat = this.defaultTransform.y;
|
|
8118
|
+
this.currentRotation = this.defaultTransform.rotation;
|
|
8119
|
+
}
|
|
7641
8120
|
// 创建 OverlayView 实例
|
|
7642
8121
|
if (window.google && window.google.maps) {
|
|
7643
8122
|
this.overlayView = new window.google.maps.OverlayView();
|
|
@@ -7652,26 +8131,28 @@ class MowerMapOverlay {
|
|
|
7652
8131
|
this.overlayView.handleSave = this.handleSave.bind(this);
|
|
7653
8132
|
this.overlayView.setCustomIcons = this.setCustomIcons.bind(this);
|
|
7654
8133
|
this.overlayView.getCurrentDragState = this.getCurrentDragState.bind(this);
|
|
8134
|
+
this.overlayView.setTransform = this.setTransform.bind(this);
|
|
8135
|
+
this.overlayView.resetToDefaultTransform = this.resetToDefaultTransform.bind(this);
|
|
7655
8136
|
}
|
|
7656
8137
|
this.boundaryData = generateBoundaryData(mapData, pathData);
|
|
7657
8138
|
}
|
|
7658
|
-
updatePosition(
|
|
7659
|
-
console.log('updatePosition==', positonConfig, animationTime);
|
|
8139
|
+
updatePosition(positionConfig, animationTime = 2200) {
|
|
7660
8140
|
// 保存当前动画时长
|
|
7661
8141
|
this.currentAnimationTime = animationTime;
|
|
7662
8142
|
// 标记是否为用户动画(大于0表示用户主动触发的动画)
|
|
7663
8143
|
this.isUserAnimation = animationTime > 0;
|
|
7664
8144
|
// 更新割草机位置配置
|
|
7665
|
-
this.
|
|
8145
|
+
this.mowerPositionConfig = positionConfig;
|
|
7666
8146
|
// 更新割草机位置管理器
|
|
7667
|
-
if (this.
|
|
7668
|
-
this.
|
|
8147
|
+
if (this.mowerPositionManager) {
|
|
8148
|
+
this.mowerPositionManager.updatePosition(positionConfig, animationTime);
|
|
7669
8149
|
}
|
|
7670
8150
|
}
|
|
7671
|
-
|
|
8151
|
+
updatePositionByLastPosition(chargingPilesPositionConfig) {
|
|
8152
|
+
this.mowerPositionConfig = chargingPilesPositionConfig;
|
|
7672
8153
|
// 更新配置
|
|
7673
|
-
if (this.
|
|
7674
|
-
this.
|
|
8154
|
+
if (this.mowerPositionManager) {
|
|
8155
|
+
this.mowerPositionManager.updatePositionByLastPosition(chargingPilesPositionConfig);
|
|
7675
8156
|
}
|
|
7676
8157
|
}
|
|
7677
8158
|
setMap(map) {
|
|
@@ -7679,6 +8160,12 @@ class MowerMapOverlay {
|
|
|
7679
8160
|
this.overlayView.setMap(map);
|
|
7680
8161
|
}
|
|
7681
8162
|
}
|
|
8163
|
+
setEdger(edger) {
|
|
8164
|
+
this.hasEdger = edger;
|
|
8165
|
+
if (this.mowerPositionManager) {
|
|
8166
|
+
this.mowerPositionManager.setEdger(edger);
|
|
8167
|
+
}
|
|
8168
|
+
}
|
|
7682
8169
|
getMap() {
|
|
7683
8170
|
return this.overlayView ? this.overlayView.getMap() : null;
|
|
7684
8171
|
}
|
|
@@ -7688,6 +8175,23 @@ class MowerMapOverlay {
|
|
|
7688
8175
|
getPanes() {
|
|
7689
8176
|
return this.overlayView ? this.overlayView.getPanes() : null;
|
|
7690
8177
|
}
|
|
8178
|
+
resetBorderLayerHighlight() {
|
|
8179
|
+
this.mowPartitionData = null;
|
|
8180
|
+
const boundaryBorderLayer = this.svgMapView?.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
|
|
8181
|
+
if (!boundaryBorderLayer)
|
|
8182
|
+
return;
|
|
8183
|
+
boundaryBorderLayer.setMowingBoundarys([]);
|
|
8184
|
+
this.svgMapView?.renderLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
|
|
8185
|
+
}
|
|
8186
|
+
setBorderLayerHighlight(mowPartitionData) {
|
|
8187
|
+
this.mowPartitionData = mowPartitionData;
|
|
8188
|
+
const boundaryBorderLayer = this.svgMapView?.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
|
|
8189
|
+
const partitionIds = mowPartitionData?.partitionIds || [];
|
|
8190
|
+
if (!boundaryBorderLayer)
|
|
8191
|
+
return;
|
|
8192
|
+
boundaryBorderLayer.setMowingBoundarys(partitionIds);
|
|
8193
|
+
this.svgMapView?.renderLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
|
|
8194
|
+
}
|
|
7691
8195
|
onAdd() {
|
|
7692
8196
|
// 创建包含SVG的div
|
|
7693
8197
|
this.div = document.createElement('div');
|
|
@@ -7710,6 +8214,7 @@ class MowerMapOverlay {
|
|
|
7710
8214
|
this.createBoundaryLabelsManager();
|
|
7711
8215
|
// 创建割草机位置管理器
|
|
7712
8216
|
this.createMowerPositionManager();
|
|
8217
|
+
this.setManagerRotation(this.defaultTransform.rotation);
|
|
7713
8218
|
// 如果处于编辑模式,创建编辑界面
|
|
7714
8219
|
if (this.isEditMode) {
|
|
7715
8220
|
this.createEditInterface();
|
|
@@ -7748,23 +8253,17 @@ class MowerMapOverlay {
|
|
|
7748
8253
|
const map = this.getMap();
|
|
7749
8254
|
if (!map || !this.svgMapView)
|
|
7750
8255
|
return;
|
|
7751
|
-
|
|
7752
|
-
// const center = map.getCenter();
|
|
7753
|
-
// 基础公式:像素/米 = 156543.03392 * cos(latitude) / (2 ^ zoom)
|
|
7754
|
-
// const metersPerPixel =
|
|
7755
|
-
// (156543.03392 * Math.cos((center.lat() * Math.PI) / 180)) / Math.pow(2, currentZoom);
|
|
7756
|
-
// 缩放比例 = 1 / 米/像素
|
|
7757
|
-
// const scale = (1 / metersPerPixel) * 50;
|
|
7758
|
-
// 应用缩放到SVG
|
|
7759
|
-
// this.svgMapView.setZoom(scale);
|
|
8256
|
+
this.draw();
|
|
7760
8257
|
}
|
|
7761
8258
|
// 创建边界标签管理器
|
|
7762
8259
|
createBoundaryLabelsManager() {
|
|
7763
8260
|
if (!this.div || !this.svgMapView)
|
|
7764
8261
|
return;
|
|
7765
|
-
console.log('this.boundaryData----->', this.boundaryData);
|
|
7766
8262
|
// 创建边界标签管理器
|
|
7767
|
-
this.boundaryLabelsManager = new BoundaryLabelsManager(this.svgMapView, this.boundaryData
|
|
8263
|
+
this.boundaryLabelsManager = new BoundaryLabelsManager(this.svgMapView, this.boundaryData, {
|
|
8264
|
+
unitType: this.unitType,
|
|
8265
|
+
language: this.language,
|
|
8266
|
+
});
|
|
7768
8267
|
// 设置叠加层div引用
|
|
7769
8268
|
this.boundaryLabelsManager.setOverlayDiv(this.div);
|
|
7770
8269
|
// 添加所有边界标签
|
|
@@ -7808,11 +8307,12 @@ class MowerMapOverlay {
|
|
|
7808
8307
|
if (!this.div || !this.svgMapView)
|
|
7809
8308
|
return;
|
|
7810
8309
|
// 创建割草机位置管理器,传入动画完成回调
|
|
7811
|
-
this.
|
|
8310
|
+
this.mowerPositionManager = new MowerPositionManager(this.svgMapView, this.mowerPositionConfig, this.modelType, this.div, () => { }, this.updatePathDataByMowingPositionThrottled.bind(this));
|
|
7812
8311
|
// 设置叠加层div引用
|
|
7813
|
-
this.
|
|
8312
|
+
this.mowerPositionManager.setOverlayDiv(this.div);
|
|
8313
|
+
this.mowerPositionManager.setEdger(this.hasEdger);
|
|
7814
8314
|
// 获取容器并添加到主div
|
|
7815
|
-
const container = this.
|
|
8315
|
+
const container = this.mowerPositionManager.getElement();
|
|
7816
8316
|
if (container) {
|
|
7817
8317
|
this.div.appendChild(container);
|
|
7818
8318
|
}
|
|
@@ -7839,23 +8339,9 @@ class MowerMapOverlay {
|
|
|
7839
8339
|
this.boundaryLabelsManager.updatePositionsWithPrecomputedData(width, height, viewBoxInfo);
|
|
7840
8340
|
}
|
|
7841
8341
|
// 更新管理器位置
|
|
7842
|
-
updateManagerPositions(
|
|
8342
|
+
updateManagerPositions(_width, _height) {
|
|
7843
8343
|
if (!this.div)
|
|
7844
8344
|
return;
|
|
7845
|
-
// 获取SVG元素和其viewBox
|
|
7846
|
-
const svgElement = this.div.querySelector('svg');
|
|
7847
|
-
if (!svgElement)
|
|
7848
|
-
return;
|
|
7849
|
-
const viewBox = svgElement.viewBox.baseVal;
|
|
7850
|
-
if (!viewBox)
|
|
7851
|
-
return;
|
|
7852
|
-
// 构造viewBox信息对象
|
|
7853
|
-
({
|
|
7854
|
-
x: viewBox.x,
|
|
7855
|
-
y: viewBox.y,
|
|
7856
|
-
width: viewBox.width,
|
|
7857
|
-
height: viewBox.height,
|
|
7858
|
-
});
|
|
7859
8345
|
// 更新充电桩位置
|
|
7860
8346
|
if (this.chargingPileManager) {
|
|
7861
8347
|
this.chargingPileManager.updatePositions();
|
|
@@ -7904,7 +8390,7 @@ class MowerMapOverlay {
|
|
|
7904
8390
|
this.rotateHandle.style.pointerEvents = 'auto';
|
|
7905
8391
|
this.rotateHandle.innerHTML = DEFAULT_ROTATE_ICON;
|
|
7906
8392
|
this.editContainer.appendChild(this.rotateHandle);
|
|
7907
|
-
//
|
|
8393
|
+
// 创建拖拽手柄(左下角)- 仅在移动设备上显示
|
|
7908
8394
|
this.dragHandle = document.createElement('div');
|
|
7909
8395
|
this.dragHandle.style.position = 'absolute';
|
|
7910
8396
|
this.dragHandle.style.bottom = '-20px';
|
|
@@ -7915,6 +8401,10 @@ class MowerMapOverlay {
|
|
|
7915
8401
|
this.dragHandle.style.zIndex = EDIT_STYLES.Z_INDEX.HANDLE;
|
|
7916
8402
|
this.dragHandle.style.pointerEvents = 'auto';
|
|
7917
8403
|
this.dragHandle.innerHTML = DEFAULT_DRAG_ICON;
|
|
8404
|
+
// 在PC设备上隐藏拖拽手柄
|
|
8405
|
+
if (!isMobileDevice()) {
|
|
8406
|
+
this.dragHandle.style.display = 'none';
|
|
8407
|
+
}
|
|
7918
8408
|
this.editContainer.appendChild(this.dragHandle);
|
|
7919
8409
|
// 将编辑容器添加到主div
|
|
7920
8410
|
this.div.appendChild(this.editContainer);
|
|
@@ -7930,9 +8420,10 @@ class MowerMapOverlay {
|
|
|
7930
8420
|
}
|
|
7931
8421
|
// 获取当前拖拽状态
|
|
7932
8422
|
getCurrentDragState() {
|
|
8423
|
+
// 返回基于地图中心点的经纬度偏移量
|
|
7933
8424
|
return {
|
|
7934
|
-
|
|
7935
|
-
|
|
8425
|
+
x: this.latLngOffset.lng + this.tempPixelOffset.x - this.initialOffset.x,
|
|
8426
|
+
y: this.latLngOffset.lat + this.tempPixelOffset.y - this.initialOffset.y,
|
|
7936
8427
|
rotation: this.currentRotation - this.initialRotation,
|
|
7937
8428
|
isDragging: this.isDragging,
|
|
7938
8429
|
isRotating: this.isRotating,
|
|
@@ -7955,7 +8446,6 @@ class MowerMapOverlay {
|
|
|
7955
8446
|
this.boundaryLabelsManager.collapseAllLabels();
|
|
7956
8447
|
}
|
|
7957
8448
|
this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
|
|
7958
|
-
console.log('开始旋转操作');
|
|
7959
8449
|
});
|
|
7960
8450
|
// 旋转手柄的触摸事件
|
|
7961
8451
|
this.rotateHandle.addEventListener('touchstart', (e) => {
|
|
@@ -7971,39 +8461,41 @@ class MowerMapOverlay {
|
|
|
7971
8461
|
this.boundaryLabelsManager.collapseAllLabels();
|
|
7972
8462
|
}
|
|
7973
8463
|
this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
|
|
7974
|
-
console.log('开始旋转操作(触摸)');
|
|
7975
|
-
}, { passive: false });
|
|
7976
|
-
// 拖拽手柄的鼠标事件
|
|
7977
|
-
this.dragHandle.addEventListener('mousedown', (e) => {
|
|
7978
|
-
e.preventDefault();
|
|
7979
|
-
e.stopPropagation();
|
|
7980
|
-
e.stopImmediatePropagation();
|
|
7981
|
-
this.isDragging = true;
|
|
7982
|
-
this.startPos = { x: e.clientX, y: e.clientY };
|
|
7983
|
-
this.dragHandle.style.cursor = 'grabbing';
|
|
7984
|
-
// 开始编辑时关闭所有展开的边界标签
|
|
7985
|
-
if (this.boundaryLabelsManager) {
|
|
7986
|
-
this.boundaryLabelsManager.collapseAllLabels();
|
|
7987
|
-
}
|
|
7988
|
-
this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
|
|
7989
|
-
console.log('开始拖动操作(通过手柄)');
|
|
7990
|
-
});
|
|
7991
|
-
// 拖拽手柄的触摸事件
|
|
7992
|
-
this.dragHandle.addEventListener('touchstart', (e) => {
|
|
7993
|
-
e.preventDefault();
|
|
7994
|
-
e.stopPropagation();
|
|
7995
|
-
e.stopImmediatePropagation();
|
|
7996
|
-
this.isDragging = true;
|
|
7997
|
-
const touch = e.touches[0];
|
|
7998
|
-
this.startPos = { x: touch.clientX, y: touch.clientY };
|
|
7999
|
-
this.dragHandle.style.cursor = 'grabbing';
|
|
8000
|
-
this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
|
|
8001
|
-
console.log('开始拖动操作(通过手柄,触摸)');
|
|
8002
8464
|
}, { passive: false });
|
|
8465
|
+
// 拖拽手柄的鼠标事件 - 仅在移动设备上启用
|
|
8466
|
+
if (isMobileDevice()) {
|
|
8467
|
+
this.dragHandle.addEventListener('mousedown', (e) => {
|
|
8468
|
+
e.preventDefault();
|
|
8469
|
+
e.stopPropagation();
|
|
8470
|
+
e.stopImmediatePropagation();
|
|
8471
|
+
this.isDragging = true;
|
|
8472
|
+
this.startPos = { x: e.clientX, y: e.clientY };
|
|
8473
|
+
this.dragHandle.style.cursor = 'grabbing';
|
|
8474
|
+
// 开始编辑时关闭所有展开的边界标签
|
|
8475
|
+
if (this.boundaryLabelsManager) {
|
|
8476
|
+
this.boundaryLabelsManager.collapseAllLabels();
|
|
8477
|
+
}
|
|
8478
|
+
this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
|
|
8479
|
+
});
|
|
8480
|
+
// 拖拽手柄的触摸事件
|
|
8481
|
+
this.dragHandle.addEventListener('touchstart', (e) => {
|
|
8482
|
+
e.preventDefault();
|
|
8483
|
+
e.stopPropagation();
|
|
8484
|
+
e.stopImmediatePropagation();
|
|
8485
|
+
this.isDragging = true;
|
|
8486
|
+
const touch = e.touches[0];
|
|
8487
|
+
this.startPos = { x: touch.clientX, y: touch.clientY };
|
|
8488
|
+
this.dragHandle.style.cursor = 'grabbing';
|
|
8489
|
+
this.dragCallbacks?.onDragStart?.(this.getCurrentDragState());
|
|
8490
|
+
}, { passive: false });
|
|
8491
|
+
}
|
|
8003
8492
|
// 编辑容器的鼠标事件(整个区域拖拽)
|
|
8004
8493
|
this.editContainer.addEventListener('mousedown', (e) => {
|
|
8005
|
-
|
|
8006
|
-
|
|
8494
|
+
// 在移动设备上,检查是否点击了拖拽手柄或旋转手柄
|
|
8495
|
+
// 在PC设备上,只检查旋转手柄(拖拽手柄已隐藏)
|
|
8496
|
+
const isDragHandleClick = isMobileDevice() && e.target === this.dragHandle;
|
|
8497
|
+
const isRotateHandleClick = e.target === this.rotateHandle;
|
|
8498
|
+
if (isDragHandleClick || isRotateHandleClick) {
|
|
8007
8499
|
return;
|
|
8008
8500
|
}
|
|
8009
8501
|
e.preventDefault();
|
|
@@ -8020,8 +8512,11 @@ class MowerMapOverlay {
|
|
|
8020
8512
|
});
|
|
8021
8513
|
// 编辑容器的触摸事件(整个区域拖拽)
|
|
8022
8514
|
this.editContainer.addEventListener('touchstart', (e) => {
|
|
8023
|
-
|
|
8024
|
-
|
|
8515
|
+
// 在移动设备上,检查是否点击了拖拽手柄或旋转手柄
|
|
8516
|
+
// 在PC设备上,只检查旋转手柄(拖拽手柄已隐藏)
|
|
8517
|
+
const isDragHandleClick = isMobileDevice() && e.target === this.dragHandle;
|
|
8518
|
+
const isRotateHandleClick = e.target === this.rotateHandle;
|
|
8519
|
+
if (isDragHandleClick || isRotateHandleClick) {
|
|
8025
8520
|
return;
|
|
8026
8521
|
}
|
|
8027
8522
|
e.preventDefault();
|
|
@@ -8081,7 +8576,6 @@ class MowerMapOverlay {
|
|
|
8081
8576
|
e.preventDefault();
|
|
8082
8577
|
e.stopPropagation();
|
|
8083
8578
|
e.stopImmediatePropagation();
|
|
8084
|
-
console.log('结束编辑操作');
|
|
8085
8579
|
}
|
|
8086
8580
|
// 如果是拖拽结束,将像素偏移量转换为地理坐标偏移量
|
|
8087
8581
|
if (this.isDragging) {
|
|
@@ -8103,7 +8597,6 @@ class MowerMapOverlay {
|
|
|
8103
8597
|
e.preventDefault();
|
|
8104
8598
|
e.stopPropagation();
|
|
8105
8599
|
e.stopImmediatePropagation();
|
|
8106
|
-
console.log('结束编辑操作(触摸)');
|
|
8107
8600
|
}
|
|
8108
8601
|
// 如果是拖拽结束,将像素偏移量转换为地理坐标偏移量
|
|
8109
8602
|
if (this.isDragging) {
|
|
@@ -8209,11 +8702,12 @@ class MowerMapOverlay {
|
|
|
8209
8702
|
// 确保旋转角度在0-360度范围内
|
|
8210
8703
|
this.currentRotation = ((this.currentRotation % 360) + 360) % 360;
|
|
8211
8704
|
// 应用旋转变换到DOM元素,同时保持位移
|
|
8705
|
+
// 更新边界标签的旋转角度,使其保持水平状态
|
|
8706
|
+
this.setManagerRotation(this.currentRotation);
|
|
8212
8707
|
const transform = `translate(${this.tempPixelOffset.x}px, ${this.tempPixelOffset.y}px) rotate(${this.currentRotation}deg)`;
|
|
8213
8708
|
this.div.style.transform = transform;
|
|
8214
8709
|
// 更新鼠标起始位置为当前位置,为下次计算做准备
|
|
8215
8710
|
this.startPos = { x: mouseCurrentX, y: mouseCurrentY };
|
|
8216
|
-
console.log('旋转角度:', this.currentRotation, '角度增量:', angleDifferenceDegrees);
|
|
8217
8711
|
}
|
|
8218
8712
|
// 将像素偏移量转换为地理坐标偏移量
|
|
8219
8713
|
convertPixelOffsetToLatLng() {
|
|
@@ -8243,17 +8737,8 @@ class MowerMapOverlay {
|
|
|
8243
8737
|
// 累积更新地理坐标偏移量(不是直接赋值!)
|
|
8244
8738
|
this.latLngOffset.lat += latOffset;
|
|
8245
8739
|
this.latLngOffset.lng += lngOffset;
|
|
8246
|
-
console.log('精确转换偏移量:', {
|
|
8247
|
-
pixelOffset: this.tempPixelOffset,
|
|
8248
|
-
centerLatLng: { lat: centerLatLng.lat(), lng: centerLatLng.lng() },
|
|
8249
|
-
offsetLatLng: { lat: offsetLatLng.lat(), lng: offsetLatLng.lng() },
|
|
8250
|
-
latOffset,
|
|
8251
|
-
lngOffset,
|
|
8252
|
-
newLatLngOffset: this.latLngOffset,
|
|
8253
|
-
});
|
|
8254
8740
|
// 重置临时像素偏移量
|
|
8255
8741
|
this.tempPixelOffset = { x: 0, y: 0 };
|
|
8256
|
-
// 重新绘制以应用新的地理坐标偏移量
|
|
8257
8742
|
this.draw();
|
|
8258
8743
|
}
|
|
8259
8744
|
// 获取编辑数据
|
|
@@ -8289,8 +8774,6 @@ class MowerMapOverlay {
|
|
|
8289
8774
|
editData: editData,
|
|
8290
8775
|
timestamp: new Date().toISOString(),
|
|
8291
8776
|
};
|
|
8292
|
-
// 在这里可以添加保存逻辑,比如发送到服务器
|
|
8293
|
-
console.log('保存编辑数据:', saveData);
|
|
8294
8777
|
// 显示保存成功提示
|
|
8295
8778
|
this.showSaveSuccess();
|
|
8296
8779
|
return saveData;
|
|
@@ -8356,6 +8839,63 @@ class MowerMapOverlay {
|
|
|
8356
8839
|
this.div.style.pointerEvents = 'none';
|
|
8357
8840
|
}
|
|
8358
8841
|
}
|
|
8842
|
+
/**
|
|
8843
|
+
* 设置旋转角度
|
|
8844
|
+
*/
|
|
8845
|
+
setManagerRotation(rotation) {
|
|
8846
|
+
if (this.boundaryLabelsManager) {
|
|
8847
|
+
this.boundaryLabelsManager.setRotation(rotation);
|
|
8848
|
+
}
|
|
8849
|
+
if (this.antennaManager) {
|
|
8850
|
+
this.antennaManager.setRotation(rotation);
|
|
8851
|
+
}
|
|
8852
|
+
if (this.chargingPileManager) {
|
|
8853
|
+
this.chargingPileManager.setRotation(rotation);
|
|
8854
|
+
}
|
|
8855
|
+
}
|
|
8856
|
+
// 设置transform
|
|
8857
|
+
setTransform(transform) {
|
|
8858
|
+
if (typeof transform.x === 'number')
|
|
8859
|
+
this.tempPixelOffset.x = transform.x;
|
|
8860
|
+
if (typeof transform.y === 'number')
|
|
8861
|
+
this.tempPixelOffset.y = transform.y;
|
|
8862
|
+
if (typeof transform.rotation === 'number')
|
|
8863
|
+
this.currentRotation = transform.rotation;
|
|
8864
|
+
this.defaultTransform = {
|
|
8865
|
+
x: transform.x,
|
|
8866
|
+
y: transform.y,
|
|
8867
|
+
rotation: transform.rotation,
|
|
8868
|
+
};
|
|
8869
|
+
// defaultTransform的x对应经度偏移量,y对应纬度偏移量
|
|
8870
|
+
this.latLngOffset.lng = this.defaultTransform.x;
|
|
8871
|
+
this.latLngOffset.lat = this.defaultTransform.y;
|
|
8872
|
+
this.setManagerRotation(this.currentRotation);
|
|
8873
|
+
this.draw();
|
|
8874
|
+
}
|
|
8875
|
+
// 重置到默认的transform
|
|
8876
|
+
resetToDefaultTransform() {
|
|
8877
|
+
// 重置所有偏移和旋转相关的状态,x对应经度偏移,y对应纬度偏移
|
|
8878
|
+
this.latLngOffset.lng = this.defaultTransform.x;
|
|
8879
|
+
this.latLngOffset.lat = this.defaultTransform.y;
|
|
8880
|
+
this.tempPixelOffset = { x: 0, y: 0 };
|
|
8881
|
+
this.currentRotation = this.defaultTransform.rotation;
|
|
8882
|
+
this.initialOffset = { x: 0, y: 0 };
|
|
8883
|
+
this.initialRotation = 0;
|
|
8884
|
+
// 重新绘制
|
|
8885
|
+
// 重置边界标签的旋转角度
|
|
8886
|
+
if (this.boundaryLabelsManager) {
|
|
8887
|
+
this.boundaryLabelsManager.setRotation(this.defaultTransform.rotation);
|
|
8888
|
+
}
|
|
8889
|
+
if (this.antennaManager) {
|
|
8890
|
+
this.antennaManager.setRotation(this.defaultTransform.rotation);
|
|
8891
|
+
}
|
|
8892
|
+
if (this.chargingPileManager) {
|
|
8893
|
+
this.chargingPileManager.setRotation(this.defaultTransform.rotation);
|
|
8894
|
+
}
|
|
8895
|
+
this.draw();
|
|
8896
|
+
// 触发拖拽回调,通知外部状态更新
|
|
8897
|
+
this.dragCallbacks?.onDragEnd?.(this.getCurrentDragState());
|
|
8898
|
+
}
|
|
8359
8899
|
initializeSvgMapView() {
|
|
8360
8900
|
if (!this.offscreenContainer)
|
|
8361
8901
|
return;
|
|
@@ -8366,8 +8906,10 @@ class MowerMapOverlay {
|
|
|
8366
8906
|
this.loadMapData();
|
|
8367
8907
|
// 加载路径数据
|
|
8368
8908
|
if (this.pathData && this.svgMapView) {
|
|
8369
|
-
this.loadPathData(this.pathData);
|
|
8909
|
+
this.loadPathData(this.pathData, this.mowPartitionData);
|
|
8370
8910
|
}
|
|
8911
|
+
// 刷新绘制图层
|
|
8912
|
+
this.svgMapView.refresh();
|
|
8371
8913
|
// 获取生成的SVG并添加到叠加层div中
|
|
8372
8914
|
const svgElement = this.svgMapView.getSVG();
|
|
8373
8915
|
if (svgElement) {
|
|
@@ -8390,9 +8932,6 @@ class MowerMapOverlay {
|
|
|
8390
8932
|
// 使用现有的MapDataProcessor处理地图数据
|
|
8391
8933
|
const elements = MapDataProcessor.processMapData(this.mapData, this.mapConfig);
|
|
8392
8934
|
// 分离充电桩和天线元素,其他元素添加到SVG图层
|
|
8393
|
-
// const svgElements = elements.filter(element =>
|
|
8394
|
-
// element.type !== 'charging_pile' && element.type !== 'antenna'
|
|
8395
|
-
// );
|
|
8396
8935
|
const svgElements = elements.filter((element) => element.type !== 'charging_pile' && element.type !== 'antenna');
|
|
8397
8936
|
const chargingPileElements = elements.filter((element) => element.type === 'charging_pile');
|
|
8398
8937
|
// 处理SVG图层元素
|
|
@@ -8401,14 +8940,16 @@ class MowerMapOverlay {
|
|
|
8401
8940
|
// 处理天线数据
|
|
8402
8941
|
let antennaElements = [];
|
|
8403
8942
|
if (this.antennaConfig.length > 0) {
|
|
8404
|
-
antennaElements = AntennaDataBuilder.fromAntennaData(this.antennaConfig);
|
|
8943
|
+
antennaElements = AntennaDataBuilder.fromAntennaData(this.antennaConfig, this.mapConfig);
|
|
8405
8944
|
}
|
|
8406
8945
|
// 添加图层到SvgMapView
|
|
8407
8946
|
const layers = drawLayer.getLayers();
|
|
8408
8947
|
this.svgMapView.clear();
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8948
|
+
this.svgMapView.addLayers(layers);
|
|
8949
|
+
const boundaryBorderLayer = this.svgMapView.getLayer(LAYER_DEFAULT_TYPE.BOUNDARY_BORDER);
|
|
8950
|
+
if (boundaryBorderLayer) {
|
|
8951
|
+
boundaryBorderLayer.setMowingBoundarys(this.mowPartitionData?.partitionIds || []);
|
|
8952
|
+
}
|
|
8412
8953
|
this.createChargingPileManager();
|
|
8413
8954
|
// 使用管理器处理充电桩和天线
|
|
8414
8955
|
if (this.chargingPileManager && chargingPileElements.length > 0) {
|
|
@@ -8434,40 +8975,94 @@ class MowerMapOverlay {
|
|
|
8434
8975
|
console.error('加载地图数据时出错:', error);
|
|
8435
8976
|
}
|
|
8436
8977
|
}
|
|
8437
|
-
loadPathData(pathData) {
|
|
8978
|
+
loadPathData(pathData, mowPartitionData) {
|
|
8438
8979
|
try {
|
|
8439
8980
|
// 使用现有的PathDataProcessor处理路径数据
|
|
8440
8981
|
const pathElements = PathDataProcessor.processPathData(pathData, this.mapConfig);
|
|
8982
|
+
const newPathElements = pathElements.map((pathElement) => {
|
|
8983
|
+
const { id, elements } = pathElement;
|
|
8984
|
+
const isMowBoundary = mowPartitionData && mowPartitionData?.partitionIds?.includes(id);
|
|
8985
|
+
if (isMowBoundary) {
|
|
8986
|
+
return {
|
|
8987
|
+
id: id,
|
|
8988
|
+
elements: elements.map((element) => {
|
|
8989
|
+
const isTransPath = element.pathType === PathSegmentType.TRANS;
|
|
8990
|
+
if (isTransPath) {
|
|
8991
|
+
return element;
|
|
8992
|
+
}
|
|
8993
|
+
return {
|
|
8994
|
+
...element,
|
|
8995
|
+
style: {
|
|
8996
|
+
...element.style,
|
|
8997
|
+
lineColor: DEFAULT_STYLES.path.mowingLineColor,
|
|
8998
|
+
},
|
|
8999
|
+
};
|
|
9000
|
+
}),
|
|
9001
|
+
};
|
|
9002
|
+
}
|
|
9003
|
+
else {
|
|
9004
|
+
return pathElement;
|
|
9005
|
+
}
|
|
9006
|
+
});
|
|
8441
9007
|
const pathLayer = new PathLayer();
|
|
8442
|
-
pathLayer.addElements(
|
|
9008
|
+
pathLayer.addElements(newPathElements);
|
|
8443
9009
|
// 添加图层到SvgMapView
|
|
8444
9010
|
this.svgMapView.removeLayerByType(LAYER_DEFAULT_TYPE.PATH);
|
|
8445
9011
|
this.svgMapView.addLayer(pathLayer);
|
|
8446
|
-
|
|
9012
|
+
this.svgMapView.renderLayer(LAYER_DEFAULT_TYPE.PATH);
|
|
8447
9013
|
// 调用回调
|
|
8448
|
-
const elementCount =
|
|
9014
|
+
const elementCount = newPathElements.length;
|
|
8449
9015
|
this.onPathLoad?.(elementCount);
|
|
8450
9016
|
}
|
|
8451
9017
|
catch (error) {
|
|
8452
9018
|
console.error('加载路径数据时出错:', error);
|
|
8453
9019
|
}
|
|
8454
9020
|
}
|
|
9021
|
+
/**
|
|
9022
|
+
* 根据割草机位置,更新路径数据
|
|
9023
|
+
* @param position 割草机位置
|
|
9024
|
+
*/
|
|
9025
|
+
updatePathDataByMowingPosition(position) {
|
|
9026
|
+
// 找到当前position所在的分区id,将该点更新到pathData中
|
|
9027
|
+
const currentPartitionId = getPartitionId(this.partitionBoundary, position.x, position.y);
|
|
9028
|
+
const processStateIsMowing = useProcessMowingState.getState().processStateIsMowing;
|
|
9029
|
+
if (currentPartitionId && this.pathData?.[currentPartitionId]) {
|
|
9030
|
+
const currentPathData = this.pathData[currentPartitionId];
|
|
9031
|
+
this.pathData[currentPartitionId] = {
|
|
9032
|
+
...currentPathData,
|
|
9033
|
+
points: [
|
|
9034
|
+
...(currentPathData?.points || []),
|
|
9035
|
+
{
|
|
9036
|
+
postureX: Number(position.x),
|
|
9037
|
+
postureY: Number(position.y),
|
|
9038
|
+
knifeRotation: processStateIsMowing && position.vehicleState === RobotStatus.MOWING ? '01' : '00', // "knifeRotation": "01",//刀盘是否转动 00-否 01-是
|
|
9039
|
+
pathType: '', //这里由于从实时路径是获取不到pathType的,所以这里暂时不传。让路径是否绘制取决于knifeRotation
|
|
9040
|
+
partitionId: currentPartitionId.toString(),
|
|
9041
|
+
},
|
|
9042
|
+
],
|
|
9043
|
+
};
|
|
9044
|
+
this.updatePathData(this.pathData, this.mowPartitionData);
|
|
9045
|
+
}
|
|
9046
|
+
}
|
|
9047
|
+
updateMowPartitionData(mowPartitionData) {
|
|
9048
|
+
this.mowPartitionData = mowPartitionData;
|
|
9049
|
+
}
|
|
8455
9050
|
/** 更新历史路径数据 */
|
|
8456
|
-
updatePathData(pathData) {
|
|
9051
|
+
updatePathData(pathData, mowPartitionData) {
|
|
8457
9052
|
if (!this.svgMapView || !pathData)
|
|
8458
9053
|
return;
|
|
8459
9054
|
// 找到pathLayer,将其删除,然后重新添加
|
|
8460
|
-
this.loadPathData(pathData);
|
|
9055
|
+
this.loadPathData(pathData, mowPartitionData);
|
|
8461
9056
|
}
|
|
9057
|
+
/** 更新边界标签信息 */
|
|
8462
9058
|
updateBoundaryLabelInfo(pathJson) {
|
|
8463
9059
|
if (!pathJson)
|
|
8464
9060
|
return;
|
|
8465
9061
|
const boundaryData = generateBoundaryData(this.mapData, pathJson);
|
|
8466
|
-
console.log('boundaryData==', boundaryData);
|
|
9062
|
+
// console.log('boundaryData==', boundaryData);
|
|
8467
9063
|
this.boundaryLabelsManager?.updateBoundaryData(boundaryData);
|
|
8468
9064
|
}
|
|
8469
9065
|
draw() {
|
|
8470
|
-
console.log('draw方法被调用');
|
|
8471
9066
|
// 防御性检查:如果this.div为null,说明onAdd还没被调用,直接返回
|
|
8472
9067
|
if (!this.div) {
|
|
8473
9068
|
return;
|
|
@@ -8525,13 +9120,17 @@ class MowerMapOverlay {
|
|
|
8525
9120
|
this.div.style.transform = transform;
|
|
8526
9121
|
}
|
|
8527
9122
|
else {
|
|
8528
|
-
//
|
|
8529
|
-
|
|
8530
|
-
|
|
9123
|
+
// 非拖拽时:应用当前偏移和旋转(包括默认值)
|
|
9124
|
+
const transforms = [];
|
|
9125
|
+
// 应用像素偏移
|
|
9126
|
+
if (this.tempPixelOffset.x !== 0 || this.tempPixelOffset.y !== 0) {
|
|
9127
|
+
transforms.push(`translate(${this.tempPixelOffset.x}px, ${this.tempPixelOffset.y}px)`);
|
|
8531
9128
|
}
|
|
8532
|
-
|
|
8533
|
-
|
|
9129
|
+
// 应用旋转
|
|
9130
|
+
if (this.currentRotation !== 0) {
|
|
9131
|
+
transforms.push(`rotate(${this.currentRotation}deg)`);
|
|
8534
9132
|
}
|
|
9133
|
+
this.div.style.transform = transforms.join(' ');
|
|
8535
9134
|
}
|
|
8536
9135
|
// 更新SVG视图框以适应新的尺寸
|
|
8537
9136
|
if (this.svgMapView) {
|
|
@@ -8551,11 +9150,11 @@ class MowerMapOverlay {
|
|
|
8551
9150
|
this.updateManagerPositions(width, height);
|
|
8552
9151
|
// 重绘的时候可能在动画中,这时候需要强制更新一次数据,不然会出现抖动的效果
|
|
8553
9152
|
// 非动画的情况下,根据最新的数据实时同步就可以了
|
|
8554
|
-
if (!this.
|
|
8555
|
-
this.
|
|
9153
|
+
if (!this.mowerPositionManager?.animationFlag) {
|
|
9154
|
+
this.mowerPositionManager?.updatePositionByLastPosition(this.mowerPositionConfig);
|
|
8556
9155
|
}
|
|
8557
9156
|
else {
|
|
8558
|
-
this.
|
|
9157
|
+
this.mowerPositionManager?.forceUpdatePosition();
|
|
8559
9158
|
}
|
|
8560
9159
|
if (this.antennaManager) {
|
|
8561
9160
|
this.antennaManager.updateAntennaPosition();
|
|
@@ -8591,9 +9190,9 @@ class MowerMapOverlay {
|
|
|
8591
9190
|
this.antennaManager = null;
|
|
8592
9191
|
}
|
|
8593
9192
|
// 清理割草机位置管理器
|
|
8594
|
-
if (this.
|
|
8595
|
-
this.
|
|
8596
|
-
this.
|
|
9193
|
+
if (this.mowerPositionManager) {
|
|
9194
|
+
this.mowerPositionManager.destroy();
|
|
9195
|
+
this.mowerPositionManager = null;
|
|
8597
9196
|
}
|
|
8598
9197
|
// 清理编辑界面
|
|
8599
9198
|
this.removeEditInterface();
|
|
@@ -8610,12 +9209,45 @@ class MowerMapOverlay {
|
|
|
8610
9209
|
}
|
|
8611
9210
|
// 显示/隐藏割草机位置
|
|
8612
9211
|
setMowerPositionVisible(visible) {
|
|
8613
|
-
if (this.
|
|
8614
|
-
this.
|
|
8615
|
-
}
|
|
9212
|
+
if (this.mowerPositionManager) {
|
|
9213
|
+
this.mowerPositionManager.setVisible(visible);
|
|
9214
|
+
}
|
|
9215
|
+
}
|
|
9216
|
+
// 获取SvgMapView实例(用于debug)
|
|
9217
|
+
getSvgMapView() {
|
|
9218
|
+
return this.svgMapView;
|
|
9219
|
+
}
|
|
9220
|
+
}
|
|
9221
|
+
|
|
9222
|
+
// 获取车辆状态的中文文案
|
|
9223
|
+
const getVehicleStateText = (vehicleState) => {
|
|
9224
|
+
switch (vehicleState) {
|
|
9225
|
+
case RobotStatus.PARKED:
|
|
9226
|
+
return 'PARKED';
|
|
9227
|
+
case RobotStatus.CHARGING:
|
|
9228
|
+
return 'CHARGING';
|
|
9229
|
+
case RobotStatus.STANDBY:
|
|
9230
|
+
return 'STANDBY';
|
|
9231
|
+
case RobotStatus.MOWING:
|
|
9232
|
+
return 'MOWING';
|
|
9233
|
+
case RobotStatus.WORKING:
|
|
9234
|
+
return 'WORKING';
|
|
9235
|
+
case RobotStatus.MAPPING:
|
|
9236
|
+
return 'MAPPING';
|
|
9237
|
+
case RobotStatus.ERROR:
|
|
9238
|
+
return 'ERROR';
|
|
9239
|
+
case RobotStatus.UPGRADING:
|
|
9240
|
+
return 'UPGRADING';
|
|
9241
|
+
case RobotStatus.DISCONNECTED:
|
|
9242
|
+
return 'DISCONNECTED';
|
|
9243
|
+
case RobotStatus.TASK_DELAY:
|
|
9244
|
+
return 'TASK_DELAY';
|
|
9245
|
+
case RobotStatus.UNKNOWN:
|
|
9246
|
+
return '未知';
|
|
9247
|
+
default:
|
|
9248
|
+
return `未知状态(${vehicleState})`;
|
|
8616
9249
|
}
|
|
8617
|
-
}
|
|
8618
|
-
|
|
9250
|
+
};
|
|
8619
9251
|
// 验证GPS坐标是否有效
|
|
8620
9252
|
const isValidGpsCoordinate = (coordinate) => {
|
|
8621
9253
|
if (!coordinate || coordinate.length < 2)
|
|
@@ -8629,47 +9261,89 @@ const isValidGpsCoordinate = (coordinate) => {
|
|
|
8629
9261
|
!(Math.abs(lng) < 0.001 && Math.abs(lat) < 0.001) // 排除接近(0,0)的坐标
|
|
8630
9262
|
);
|
|
8631
9263
|
};
|
|
9264
|
+
// 旋转坐标点
|
|
9265
|
+
const rotateCoordinate = (point, center, angleRadians) => {
|
|
9266
|
+
const [x, y] = point;
|
|
9267
|
+
const [cx, cy] = center;
|
|
9268
|
+
// 将点移动到原点
|
|
9269
|
+
const dx = x - cx;
|
|
9270
|
+
const dy = y - cy;
|
|
9271
|
+
// 应用旋转矩阵
|
|
9272
|
+
const cos = Math.cos(angleRadians);
|
|
9273
|
+
const sin = Math.sin(angleRadians);
|
|
9274
|
+
const rotatedX = dx * cos - dy * sin;
|
|
9275
|
+
const rotatedY = dx * sin + dy * cos;
|
|
9276
|
+
// 移回原位置
|
|
9277
|
+
return [rotatedX + cx, rotatedY + cy];
|
|
9278
|
+
};
|
|
8632
9279
|
// 获取有效的GPS边界
|
|
8633
|
-
const getValidGpsBounds = (mapData) => {
|
|
9280
|
+
const getValidGpsBounds = (mapData, rotation = 0) => {
|
|
9281
|
+
let bounds;
|
|
8634
9282
|
// 首先尝试使用地图数据中的GPS坐标
|
|
8635
9283
|
if (isValidGpsCoordinate(mapData.sw_gps) && isValidGpsCoordinate(mapData.ne_gps)) {
|
|
8636
|
-
|
|
9284
|
+
bounds = {
|
|
8637
9285
|
sw: mapData.sw_gps,
|
|
8638
9286
|
ne: mapData.ne_gps,
|
|
8639
9287
|
};
|
|
8640
9288
|
}
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
9289
|
+
else {
|
|
9290
|
+
// 如果GPS坐标无效,尝试从地图几何数据估算
|
|
9291
|
+
const { sw, ne } = estimateGpsFromMapBounds(mapData);
|
|
9292
|
+
if (sw && ne) {
|
|
9293
|
+
console.warn('GPS坐标无效,使用地图几何数据估算边界:', sw, ne);
|
|
9294
|
+
bounds = {
|
|
9295
|
+
sw: [sw[0], sw[1]],
|
|
9296
|
+
ne: [ne[0], ne[1]],
|
|
9297
|
+
};
|
|
9298
|
+
}
|
|
9299
|
+
else {
|
|
9300
|
+
// 最后的fallback:使用默认坐标
|
|
9301
|
+
console.warn('无法获取有效的GPS边界,使用默认坐标');
|
|
9302
|
+
bounds = {
|
|
9303
|
+
sw: [-9.1562, -37.7503],
|
|
9304
|
+
ne: [31.247, 5.797],
|
|
9305
|
+
};
|
|
9306
|
+
}
|
|
9307
|
+
}
|
|
9308
|
+
// 如果有旋转角度,计算旋转后的边界
|
|
9309
|
+
if (rotation !== 0) {
|
|
9310
|
+
const angleRadians = (rotation * Math.PI) / 180; // 转换为弧度
|
|
9311
|
+
// 计算边界中心点
|
|
9312
|
+
const centerLng = (bounds.sw[0] + bounds.ne[0]) / 2;
|
|
9313
|
+
const centerLat = (bounds.sw[1] + bounds.ne[1]) / 2;
|
|
9314
|
+
const center = [centerLng, centerLat];
|
|
9315
|
+
// 旋转四个角点
|
|
9316
|
+
const sw = rotateCoordinate(bounds.sw, center, angleRadians);
|
|
9317
|
+
const ne = rotateCoordinate(bounds.ne, center, angleRadians);
|
|
9318
|
+
const se = rotateCoordinate([bounds.ne[0], bounds.sw[1]], center, angleRadians);
|
|
9319
|
+
const nw = rotateCoordinate([bounds.sw[0], bounds.ne[1]], center, angleRadians);
|
|
9320
|
+
// 计算旋转后的边界框(包含所有旋转后的点)
|
|
9321
|
+
const lngs = [sw[0], ne[0], se[0], nw[0]];
|
|
9322
|
+
const lats = [sw[1], ne[1], se[1], nw[1]];
|
|
9323
|
+
bounds = {
|
|
9324
|
+
sw: [Math.min(...lngs), Math.min(...lats)],
|
|
9325
|
+
ne: [Math.max(...lngs), Math.max(...lats)],
|
|
8649
9326
|
};
|
|
8650
9327
|
}
|
|
8651
|
-
|
|
8652
|
-
console.warn('无法获取有效的GPS边界,使用默认坐标');
|
|
8653
|
-
return {
|
|
8654
|
-
sw: [-9.1562, -37.7503],
|
|
8655
|
-
ne: [31.247, 5.797],
|
|
8656
|
-
};
|
|
9328
|
+
return bounds;
|
|
8657
9329
|
};
|
|
8658
9330
|
// 默认配置
|
|
8659
9331
|
const defaultMapConfig = DEFAULT_STYLES;
|
|
8660
9332
|
// 地图渲染器组件
|
|
8661
|
-
const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJson, pathJson, realTimeData,
|
|
8662
|
-
const svgMapViewRef = React.useRef(null);
|
|
9333
|
+
const MowerMapRenderer = React.forwardRef(({ edger = false, unitType = UnitsType.Imperial, language = 'en', mapConfig, modelType, mapRef, mapJson, pathJson, realTimeData, antennaConfig, onMapLoad, onPathLoad, onError, className, style, googleMapInstance, isEditMode = false, dragCallbacks, defaultTransform, debug = false, }, ref) => {
|
|
8663
9334
|
const [elementCount, setElementCount] = React.useState(0);
|
|
8664
9335
|
const [pathCount, setPathCount] = React.useState(0);
|
|
8665
|
-
const [zoom, setZoom] = React.useState(1);
|
|
8666
9336
|
const [currentError, setCurrentError] = React.useState(null);
|
|
8667
9337
|
const overlayRef = React.useRef(null);
|
|
8668
9338
|
// const mapRef = useMap();
|
|
8669
9339
|
const [isGoogleMapsReady, setIsGoogleMapsReady] = React.useState(false);
|
|
8670
9340
|
const [hasInitializedBounds, setHasInitializedBounds] = React.useState(false);
|
|
8671
|
-
const { clearSubBoundaryBorder } = useSubBoundaryBorderStore();
|
|
9341
|
+
const { clearSubBoundaryBorder, clearObstacles, clearSvgElements } = useSubBoundaryBorderStore();
|
|
8672
9342
|
const currentProcessMowingStatusRef = React.useRef(false);
|
|
9343
|
+
const { updateProcessStateIsMowing, processStateIsMowing } = useProcessMowingState();
|
|
9344
|
+
const [mowPartitionData, setMowPartitionData] = React.useState(null);
|
|
9345
|
+
// Debug相关状态
|
|
9346
|
+
const [debugInfo, setDebugInfo] = React.useState({});
|
|
8673
9347
|
// 处理地图分区边界
|
|
8674
9348
|
const partitionBoundary = React.useMemo(() => {
|
|
8675
9349
|
const allBoundaryElements = [];
|
|
@@ -8692,22 +9366,67 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
|
|
|
8692
9366
|
}, [mapConfig]);
|
|
8693
9367
|
const mergedAntennaConfig = React.useMemo(() => antennaConfig, [antennaConfig]);
|
|
8694
9368
|
const mowerPositionData = React.useMemo(() => {
|
|
9369
|
+
// realTimeData 中包含三个种类的数据,之需要实时坐标的数据即可。
|
|
9370
|
+
if (!realTimeData || realTimeData.length === 0)
|
|
9371
|
+
return {
|
|
9372
|
+
postureX: 0,
|
|
9373
|
+
postureY: 0,
|
|
9374
|
+
postureTheta: 0,
|
|
9375
|
+
vehicleState: RobotStatus.DISCONNECTED,
|
|
9376
|
+
};
|
|
9377
|
+
let currentPositionData;
|
|
9378
|
+
if (realTimeData.length === 1 && realTimeData[0].type === RealTimeDataType.LOCATION) {
|
|
9379
|
+
currentPositionData = realTimeData[0];
|
|
9380
|
+
}
|
|
9381
|
+
else {
|
|
9382
|
+
currentPositionData = realTimeData?.find((item) => item.type === RealTimeDataType.LOCATION);
|
|
9383
|
+
}
|
|
9384
|
+
if (!currentPositionData)
|
|
9385
|
+
return undefined;
|
|
8695
9386
|
return {
|
|
8696
9387
|
postureTheta: currentPositionData?.postureTheta
|
|
8697
9388
|
? Number(currentPositionData.postureTheta)
|
|
8698
9389
|
: 0,
|
|
8699
9390
|
postureX: currentPositionData?.postureX ? Number(currentPositionData.postureX) : 0,
|
|
8700
9391
|
postureY: currentPositionData?.postureY ? Number(currentPositionData.postureY) : 0,
|
|
8701
|
-
|
|
8702
|
-
|
|
9392
|
+
lastPostureTheta: currentPositionData?.lastPostureTheta
|
|
9393
|
+
? Number(currentPositionData.lastPostureTheta)
|
|
9394
|
+
: 0,
|
|
9395
|
+
lastPostureX: currentPositionData?.lastPostureX
|
|
9396
|
+
? Number(currentPositionData.lastPostureX)
|
|
9397
|
+
: 0,
|
|
9398
|
+
lastPostureY: currentPositionData?.lastPostureY
|
|
9399
|
+
? Number(currentPositionData.lastPostureY)
|
|
9400
|
+
: 0,
|
|
9401
|
+
vehicleState: currentPositionData?.vehicleState || RobotStatus.DISCONNECTED,
|
|
8703
9402
|
};
|
|
8704
|
-
}, [
|
|
8705
|
-
console.log('mowerPositionData==', currentPositionData, mowerPositionData);
|
|
9403
|
+
}, [realTimeData, modelType]);
|
|
8706
9404
|
// 处理错误
|
|
8707
9405
|
const handleError = (error) => {
|
|
8708
9406
|
setCurrentError(error);
|
|
8709
9407
|
onError?.(error);
|
|
8710
9408
|
};
|
|
9409
|
+
const fitBounds = React.useCallback(() => {
|
|
9410
|
+
if (!mapJson || !mapRef)
|
|
9411
|
+
return null;
|
|
9412
|
+
// 计算边界
|
|
9413
|
+
const bounds = calculateMapBounds(mapJson);
|
|
9414
|
+
if (!bounds) {
|
|
9415
|
+
handleError('无法计算地图边界');
|
|
9416
|
+
return;
|
|
9417
|
+
}
|
|
9418
|
+
// 将自定义边界转换为Google Maps LatLngBounds(使用有效的GPS坐标)
|
|
9419
|
+
const validBounds = getValidGpsBounds(mapJson, defaultTransform?.rotation);
|
|
9420
|
+
// 地图数据中的坐标格式是 [longitude, latitude]
|
|
9421
|
+
const swLat = validBounds.sw[1] + defaultTransform.y;
|
|
9422
|
+
const swLng = validBounds.sw[0] + defaultTransform.x;
|
|
9423
|
+
const neLat = validBounds.ne[1] + defaultTransform.y;
|
|
9424
|
+
const neLng = validBounds.ne[0] + defaultTransform.x;
|
|
9425
|
+
const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
|
|
9426
|
+
new window.google.maps.LatLng(neLat, neLng) // 东北角
|
|
9427
|
+
);
|
|
9428
|
+
mapRef.fitBounds(googleBounds);
|
|
9429
|
+
}, [mapJson, mapRef, defaultTransform]);
|
|
8711
9430
|
// 初始化Google Maps叠加层
|
|
8712
9431
|
const initializeGoogleMapsOverlay = async () => {
|
|
8713
9432
|
if (!mapJson)
|
|
@@ -8731,7 +9450,8 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
|
|
|
8731
9450
|
return;
|
|
8732
9451
|
}
|
|
8733
9452
|
// 将自定义边界转换为Google Maps LatLngBounds(使用有效的GPS坐标)
|
|
8734
|
-
|
|
9453
|
+
// 这里需要使用0度,需要原始的坐标点去计算元素的实际位置,只有在fitbounds的时候才需要使用旋转后的坐标点
|
|
9454
|
+
const validBounds = getValidGpsBounds(mapJson, 0);
|
|
8735
9455
|
// 地图数据中的坐标格式是 [longitude, latitude]
|
|
8736
9456
|
const swLat = validBounds.sw[1];
|
|
8737
9457
|
const swLng = validBounds.sw[0];
|
|
@@ -8740,17 +9460,13 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
|
|
|
8740
9460
|
const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
|
|
8741
9461
|
new window.google.maps.LatLng(neLat, neLng) // 东北角
|
|
8742
9462
|
);
|
|
8743
|
-
console.log('使用有效GPS坐标创建边界:', {
|
|
8744
|
-
sw: { lat: swLat, lng: swLng },
|
|
8745
|
-
ne: { lat: neLat, lng: neLng },
|
|
8746
|
-
});
|
|
8747
9463
|
// 如果已经存在叠加层,先移除它
|
|
8748
9464
|
if (overlayRef.current) {
|
|
8749
9465
|
overlayRef.current.setMap(null);
|
|
8750
9466
|
overlayRef.current = null;
|
|
8751
9467
|
}
|
|
8752
9468
|
// 创建叠加层
|
|
8753
|
-
const overlay = new MowerMapOverlay(googleBounds, mapJson, mowerPositionData, pathJson || {}, isEditMode, mergedMapConfig, mergedAntennaConfig, (count) => {
|
|
9469
|
+
const overlay = new MowerMapOverlay(googleBounds, mapJson, partitionBoundary, mowerPositionData, modelType, pathJson || {}, isEditMode, unitType, language, mergedMapConfig, mergedAntennaConfig, null, defaultTransform, (count) => {
|
|
8754
9470
|
setElementCount(count);
|
|
8755
9471
|
onMapLoad?.(count);
|
|
8756
9472
|
}, (count) => {
|
|
@@ -8760,6 +9476,7 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
|
|
|
8760
9476
|
// 设置地图
|
|
8761
9477
|
overlay.setMap(mapInstance);
|
|
8762
9478
|
overlayRef.current = overlay;
|
|
9479
|
+
overlay.setEdger(edger);
|
|
8763
9480
|
// 只在首次初始化时自适应视图
|
|
8764
9481
|
if (!hasInitializedBounds) {
|
|
8765
9482
|
mapInstance.fitBounds(googleBounds);
|
|
@@ -8771,39 +9488,33 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
|
|
|
8771
9488
|
handleError(`初始化Google Maps叠加层失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
8772
9489
|
}
|
|
8773
9490
|
};
|
|
8774
|
-
const
|
|
8775
|
-
|
|
8776
|
-
const
|
|
8777
|
-
|
|
8778
|
-
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
overlayRef.current.updatePathData(newRealTimeData.pathData);
|
|
8788
|
-
}
|
|
8789
|
-
overlayRef.current.updateBoundaryLabelInfo(newRealTimeData.pathData);
|
|
8790
|
-
};
|
|
9491
|
+
const resetInCharginPie = React.useCallback(() => {
|
|
9492
|
+
const elements = MapDataProcessor.processMapData(mapJson, mergedMapConfig);
|
|
9493
|
+
const chargingPiles = elements.find((element) => element.type === 'charging_pile');
|
|
9494
|
+
if (!overlayRef.current)
|
|
9495
|
+
return;
|
|
9496
|
+
// 如果在充电桩上,则直接更新位置到充电桩的位置
|
|
9497
|
+
overlayRef.current.updatePosition({
|
|
9498
|
+
...mowerPositionData,
|
|
9499
|
+
postureX: chargingPiles?.originalData.position[0],
|
|
9500
|
+
postureY: chargingPiles?.originalData.position[1],
|
|
9501
|
+
postureTheta: chargingPiles?.originalData.direction - Math.PI || 0,
|
|
9502
|
+
}, 0);
|
|
9503
|
+
}, [mapJson, mowerPositionData]);
|
|
8791
9504
|
// 初始化效果
|
|
8792
9505
|
React.useEffect(() => {
|
|
8793
9506
|
initializeGoogleMapsOverlay();
|
|
8794
|
-
console.log('init mow map');
|
|
8795
9507
|
// 清理函数
|
|
8796
9508
|
return () => {
|
|
8797
9509
|
clearSubBoundaryBorder();
|
|
9510
|
+
clearObstacles();
|
|
9511
|
+
clearSvgElements();
|
|
9512
|
+
updateProcessStateIsMowing(false);
|
|
8798
9513
|
currentProcessMowingStatusRef.current = false;
|
|
8799
9514
|
if (overlayRef.current) {
|
|
8800
9515
|
overlayRef.current.setMap(null);
|
|
8801
9516
|
overlayRef.current = null;
|
|
8802
9517
|
}
|
|
8803
|
-
if (svgMapViewRef.current) {
|
|
8804
|
-
svgMapViewRef.current.destroy();
|
|
8805
|
-
svgMapViewRef.current = null;
|
|
8806
|
-
}
|
|
8807
9518
|
};
|
|
8808
9519
|
}, [mapJson, pathJson, mergedMapConfig, mergedAntennaConfig]);
|
|
8809
9520
|
// 监听编辑模式变化
|
|
@@ -8823,13 +9534,12 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
|
|
|
8823
9534
|
return;
|
|
8824
9535
|
const elements = MapDataProcessor.processMapData(mapJson, mergedMapConfig);
|
|
8825
9536
|
const chargingPiles = elements.find((element) => element.type === 'charging_pile');
|
|
8826
|
-
console.log('chargingPiles==', chargingPiles, currentPositionData);
|
|
8827
9537
|
if (!mowerPositionData || !overlayRef.current)
|
|
8828
9538
|
return;
|
|
8829
9539
|
const inChargingPiles = [RobotStatus.CHARGING, RobotStatus.PARKED];
|
|
8830
9540
|
const isOffLine = mowerPositionData.vehicleState === RobotStatus.DISCONNECTED;
|
|
8831
9541
|
const isInChargingPile = inChargingPiles.includes(mowerPositionData.vehicleState);
|
|
8832
|
-
//
|
|
9542
|
+
// 如果在充电桩上,则直接更新位置到充电桩的位置
|
|
8833
9543
|
if (isInChargingPile) {
|
|
8834
9544
|
overlayRef.current.updatePosition({
|
|
8835
9545
|
...mowerPositionData,
|
|
@@ -8839,68 +9549,247 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
|
|
|
8839
9549
|
}, 0);
|
|
8840
9550
|
}
|
|
8841
9551
|
else {
|
|
9552
|
+
// 如果车辆是disabled状态或者超出边界(默认超过1000m),则更新位置到上一次的位置
|
|
8842
9553
|
const positonOutOfRange = isOutOfRange(mowerPositionData);
|
|
8843
9554
|
const positionValid = isInvalidPosition(mowerPositionData);
|
|
8844
|
-
const
|
|
9555
|
+
const isStandby = mowerPositionData.vehicleState === RobotStatus.STANDBY;
|
|
8845
9556
|
if (positonOutOfRange || positionValid || isOffLine) {
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
9557
|
+
// 初始信息是通过后端接口获取的,此时当前位置数据不可用的时候,可以取上一次的位置数据
|
|
9558
|
+
// mowerPositionData 中可能会包含上一次的位置数据,
|
|
9559
|
+
const lastPostureX = mowerPositionData.lastPostureX;
|
|
9560
|
+
const lastPostureY = mowerPositionData.lastPostureY;
|
|
9561
|
+
const lastPostureTheta = mowerPositionData.lastPostureTheta;
|
|
9562
|
+
if (lastPostureX && lastPostureY && lastPostureTheta) {
|
|
9563
|
+
overlayRef.current.updatePositionByLastPosition(mowerPositionData);
|
|
9564
|
+
}
|
|
9565
|
+
else {
|
|
9566
|
+
overlayRef.current.updatePositionByLastPosition({
|
|
9567
|
+
...mowerPositionData,
|
|
9568
|
+
postureX: chargingPiles?.originalData.position[0],
|
|
9569
|
+
postureY: chargingPiles?.originalData.position[1],
|
|
9570
|
+
postureTheta: chargingPiles?.originalData.direction - Math.PI || 0,
|
|
9571
|
+
});
|
|
9572
|
+
}
|
|
8852
9573
|
}
|
|
8853
9574
|
else {
|
|
8854
|
-
overlayRef.current.updatePosition(mowerPositionData,
|
|
9575
|
+
overlayRef.current.updatePosition(mowerPositionData, isStandby ? 0 : 2000);
|
|
8855
9576
|
}
|
|
8856
9577
|
}
|
|
8857
|
-
}, [
|
|
9578
|
+
}, [mowerPositionData]);
|
|
9579
|
+
// 更新debug信息
|
|
8858
9580
|
React.useEffect(() => {
|
|
8859
|
-
if (!
|
|
8860
|
-
!pathJson ||
|
|
8861
|
-
!realTimeData ||
|
|
8862
|
-
realTimeData.length === 0 ||
|
|
8863
|
-
!Array.isArray(realTimeData))
|
|
9581
|
+
if (!debug)
|
|
8864
9582
|
return;
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
// 提供ref方法
|
|
8871
|
-
React.useImperativeHandle(ref, () => ({
|
|
8872
|
-
setZoom: (newZoom) => {
|
|
8873
|
-
setZoom(newZoom);
|
|
8874
|
-
if (svgMapViewRef.current) {
|
|
8875
|
-
svgMapViewRef.current.setZoom(newZoom);
|
|
9583
|
+
const updateDebugInfo = () => {
|
|
9584
|
+
const newDebugInfo = {};
|
|
9585
|
+
// 获取地图GPS边界
|
|
9586
|
+
if (mapJson) {
|
|
9587
|
+
newDebugInfo.mapBounds = getValidGpsBounds(mapJson, defaultTransform?.rotation);
|
|
8876
9588
|
}
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
9589
|
+
// 获取SVG viewBox信息
|
|
9590
|
+
if (overlayRef.current) {
|
|
9591
|
+
const overlay = overlayRef.current;
|
|
9592
|
+
const svgMapView = overlay.getSvgMapView?.();
|
|
9593
|
+
if (svgMapView) {
|
|
9594
|
+
const viewBoxInfo = svgMapView.getViewBoxInfo();
|
|
9595
|
+
// 计算实际的米单位数据(除以缩放比例)
|
|
9596
|
+
const SCALE_FACTOR = 50; // 根据项目中的缩放因子
|
|
9597
|
+
newDebugInfo.viewBox = {
|
|
9598
|
+
x: viewBoxInfo.x / SCALE_FACTOR,
|
|
9599
|
+
y: viewBoxInfo.y / SCALE_FACTOR,
|
|
9600
|
+
width: viewBoxInfo.width / SCALE_FACTOR,
|
|
9601
|
+
height: viewBoxInfo.height / SCALE_FACTOR,
|
|
9602
|
+
scale: SCALE_FACTOR,
|
|
9603
|
+
// 计算左下角和右上角坐标
|
|
9604
|
+
sw: {
|
|
9605
|
+
x: viewBoxInfo.x / SCALE_FACTOR,
|
|
9606
|
+
y: viewBoxInfo.y / SCALE_FACTOR,
|
|
9607
|
+
},
|
|
9608
|
+
ne: {
|
|
9609
|
+
x: (viewBoxInfo.x + viewBoxInfo.width) / SCALE_FACTOR,
|
|
9610
|
+
y: (viewBoxInfo.y + viewBoxInfo.height) / SCALE_FACTOR,
|
|
9611
|
+
},
|
|
9612
|
+
};
|
|
9613
|
+
}
|
|
8883
9614
|
}
|
|
8884
|
-
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
|
|
8889
|
-
|
|
9615
|
+
// 获取当前割草机位置
|
|
9616
|
+
if (mowerPositionData) {
|
|
9617
|
+
newDebugInfo.mowerPosition = {
|
|
9618
|
+
x: mowerPositionData.postureX || 0,
|
|
9619
|
+
y: mowerPositionData.postureY || 0,
|
|
9620
|
+
theta: mowerPositionData.postureTheta || 0,
|
|
9621
|
+
lastX: mowerPositionData.lastPostureX || 0,
|
|
9622
|
+
lastY: mowerPositionData.lastPostureY || 0,
|
|
9623
|
+
lastTheta: mowerPositionData.lastPostureTheta || 0,
|
|
9624
|
+
vehicleState: mowerPositionData.vehicleState || RobotStatus.UNKNOWN,
|
|
9625
|
+
vehicleStateText: getVehicleStateText(mowerPositionData.vehicleState || RobotStatus.UNKNOWN),
|
|
9626
|
+
};
|
|
8890
9627
|
}
|
|
8891
|
-
|
|
8892
|
-
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
9628
|
+
// 获取当前割草地块数据
|
|
9629
|
+
newDebugInfo.partitionData = mowPartitionData;
|
|
9630
|
+
setDebugInfo(newDebugInfo);
|
|
9631
|
+
};
|
|
9632
|
+
updateDebugInfo();
|
|
9633
|
+
}, [debug, mapJson, mowerPositionData, mowPartitionData, defaultTransform]);
|
|
9634
|
+
// 当关键数据变化时立即更新debug信息
|
|
9635
|
+
React.useEffect(() => {
|
|
9636
|
+
if (!debug)
|
|
9637
|
+
return;
|
|
9638
|
+
const updateDebugInfo = () => {
|
|
9639
|
+
const newDebugInfo = {};
|
|
9640
|
+
// 获取地图GPS边界
|
|
9641
|
+
if (mapJson) {
|
|
9642
|
+
newDebugInfo.mapBounds = getValidGpsBounds(mapJson, defaultTransform?.rotation);
|
|
9643
|
+
}
|
|
9644
|
+
// 获取SVG viewBox信息
|
|
9645
|
+
if (overlayRef.current) {
|
|
9646
|
+
const overlay = overlayRef.current;
|
|
9647
|
+
const svgMapView = overlay.getSvgMapView?.();
|
|
9648
|
+
if (svgMapView) {
|
|
9649
|
+
const viewBoxInfo = svgMapView.getViewBoxInfo();
|
|
9650
|
+
// 计算实际的米单位数据(除以缩放比例)
|
|
9651
|
+
const SCALE_FACTOR = 50; // 根据项目中的缩放因子
|
|
9652
|
+
newDebugInfo.viewBox = {
|
|
9653
|
+
x: viewBoxInfo.x / SCALE_FACTOR,
|
|
9654
|
+
y: viewBoxInfo.y / SCALE_FACTOR,
|
|
9655
|
+
width: viewBoxInfo.width / SCALE_FACTOR,
|
|
9656
|
+
height: viewBoxInfo.height / SCALE_FACTOR,
|
|
9657
|
+
scale: SCALE_FACTOR,
|
|
9658
|
+
// 计算左下角和右上角坐标
|
|
9659
|
+
sw: {
|
|
9660
|
+
x: viewBoxInfo.x / SCALE_FACTOR,
|
|
9661
|
+
y: viewBoxInfo.y / SCALE_FACTOR,
|
|
9662
|
+
},
|
|
9663
|
+
ne: {
|
|
9664
|
+
x: (viewBoxInfo.x + viewBoxInfo.width) / SCALE_FACTOR,
|
|
9665
|
+
y: (viewBoxInfo.y + viewBoxInfo.height) / SCALE_FACTOR,
|
|
9666
|
+
},
|
|
9667
|
+
};
|
|
8902
9668
|
}
|
|
8903
9669
|
}
|
|
9670
|
+
// 获取当前割草机位置
|
|
9671
|
+
if (mowerPositionData) {
|
|
9672
|
+
newDebugInfo.mowerPosition = {
|
|
9673
|
+
x: mowerPositionData.postureX || 0,
|
|
9674
|
+
y: mowerPositionData.postureY || 0,
|
|
9675
|
+
theta: mowerPositionData.postureTheta || 0,
|
|
9676
|
+
lastX: mowerPositionData.lastPostureX || 0,
|
|
9677
|
+
lastY: mowerPositionData.lastPostureY || 0,
|
|
9678
|
+
lastTheta: mowerPositionData.lastPostureTheta || 0,
|
|
9679
|
+
vehicleState: mowerPositionData.vehicleState || RobotStatus.UNKNOWN,
|
|
9680
|
+
vehicleStateText: getVehicleStateText(mowerPositionData.vehicleState || RobotStatus.UNKNOWN),
|
|
9681
|
+
};
|
|
9682
|
+
}
|
|
9683
|
+
// 获取当前割草地块数据
|
|
9684
|
+
newDebugInfo.partitionData = mowPartitionData;
|
|
9685
|
+
setDebugInfo(newDebugInfo);
|
|
9686
|
+
};
|
|
9687
|
+
updateDebugInfo();
|
|
9688
|
+
}, [debug, mowerPositionData, mowPartitionData]);
|
|
9689
|
+
React.useEffect(() => {
|
|
9690
|
+
if (!realTimeData || realTimeData.length === 0 || !Array.isArray(realTimeData)) {
|
|
9691
|
+
return;
|
|
9692
|
+
}
|
|
9693
|
+
let curMowPartitionData = mowPartitionData;
|
|
9694
|
+
// realtime中包含当前割草任务的数据,根据数据进行path路径和边界的高亮操作,
|
|
9695
|
+
const mowingPartition = realTimeData.find((item) => item.type === RealTimeDataType.PARTITION);
|
|
9696
|
+
if (mowingPartition) {
|
|
9697
|
+
setMowPartitionData(mowingPartition);
|
|
9698
|
+
curMowPartitionData = mowingPartition;
|
|
9699
|
+
}
|
|
9700
|
+
const positionData = realTimeData?.find((item) => item?.type === RealTimeDataType.LOCATION);
|
|
9701
|
+
const statusData = realTimeData?.find((item) => item?.type === RealTimeDataType.STATUS);
|
|
9702
|
+
if (statusData || positionData) {
|
|
9703
|
+
const currentStatus = statusData?.vehicleState || positionData?.vehicleState;
|
|
9704
|
+
// 车辆回桩不会回传最后的park的位置,所以根据实时数据的状态数据判断车辆回到桩上
|
|
9705
|
+
if ([RobotStatus.CHARGING, RobotStatus.PARKED].includes(currentStatus || RobotStatus.UNKNOWN)) {
|
|
9706
|
+
resetInCharginPie();
|
|
9707
|
+
}
|
|
9708
|
+
else if (currentStatus === RobotStatus.WORKING) {
|
|
9709
|
+
// 兜底收不到割草地块的实时数据,使用状态来兜底
|
|
9710
|
+
overlayRef.current.resetBorderLayerHighlight();
|
|
9711
|
+
setMowPartitionData({});
|
|
9712
|
+
curMowPartitionData = {};
|
|
9713
|
+
}
|
|
9714
|
+
else if (currentStatus === RobotStatus.MOWING &&
|
|
9715
|
+
curMowPartitionData &&
|
|
9716
|
+
!curMowPartitionData?.partitionIds) {
|
|
9717
|
+
// 如果当前是割草状态,但是地块数据初始化过且不存在则认为是全局割草,则把所有地块都高亮
|
|
9718
|
+
const allPartitionIds = mapJson?.sub_maps?.map((item) => item?.id);
|
|
9719
|
+
setMowPartitionData({
|
|
9720
|
+
partitionIds: allPartitionIds,
|
|
9721
|
+
});
|
|
9722
|
+
curMowPartitionData = {
|
|
9723
|
+
partitionIds: allPartitionIds,
|
|
9724
|
+
};
|
|
9725
|
+
}
|
|
9726
|
+
}
|
|
9727
|
+
if (!mapJson || !pathJson || !overlayRef.current)
|
|
9728
|
+
return;
|
|
9729
|
+
// 根据后端推送的实时数据,进行不同处理
|
|
9730
|
+
if (curMowPartitionData) {
|
|
9731
|
+
const isMowing = curMowPartitionData?.partitionIds && curMowPartitionData.partitionIds.length > 0;
|
|
9732
|
+
overlayRef.current.updateMowPartitionData(curMowPartitionData);
|
|
9733
|
+
if (!isMowing) {
|
|
9734
|
+
overlayRef.current.resetBorderLayerHighlight();
|
|
9735
|
+
}
|
|
9736
|
+
else {
|
|
9737
|
+
overlayRef.current.setBorderLayerHighlight(curMowPartitionData);
|
|
9738
|
+
}
|
|
9739
|
+
}
|
|
9740
|
+
// 如果一次性推送的是多条数据,则把多条数据处理后存入pathData,然后更新路径数据和边界标签信息
|
|
9741
|
+
// 如果一次只推送一条数据,则只解析里面的进度数据,然后更新边界标签信息,剩下的实时轨迹数据由车辆运动产生时存入pathData
|
|
9742
|
+
if (realTimeData.length > 1) {
|
|
9743
|
+
const { pathData, isMowing } = handleMultipleRealTimeData({
|
|
9744
|
+
realTimeData,
|
|
9745
|
+
isMowing: processStateIsMowing,
|
|
9746
|
+
pathData: pathJson,
|
|
9747
|
+
partitionBoundary,
|
|
9748
|
+
});
|
|
9749
|
+
updateProcessStateIsMowing(isMowing);
|
|
9750
|
+
if (pathData) {
|
|
9751
|
+
overlayRef.current.updatePathData(pathData, curMowPartitionData);
|
|
9752
|
+
overlayRef.current.updateBoundaryLabelInfo(pathData);
|
|
9753
|
+
}
|
|
9754
|
+
}
|
|
9755
|
+
else {
|
|
9756
|
+
const { isMowing, pathData } = getProcessMowingDataFromRealTimeData({
|
|
9757
|
+
realTimeData,
|
|
9758
|
+
isMowing: processStateIsMowing,
|
|
9759
|
+
pathData: pathJson,
|
|
9760
|
+
});
|
|
9761
|
+
updateProcessStateIsMowing(isMowing);
|
|
9762
|
+
overlayRef.current.updatePathData(pathData, curMowPartitionData);
|
|
9763
|
+
// 更新进度数据
|
|
9764
|
+
if (pathData) {
|
|
9765
|
+
overlayRef.current.updateBoundaryLabelInfo(pathData);
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
}, [realTimeData, mapJson, pathJson]);
|
|
9769
|
+
React.useEffect(() => {
|
|
9770
|
+
if (!overlayRef.current || !defaultTransform)
|
|
9771
|
+
return;
|
|
9772
|
+
overlayRef.current?.setTransform(defaultTransform);
|
|
9773
|
+
const validBounds = getValidGpsBounds(mapJson, defaultTransform?.rotation);
|
|
9774
|
+
// 地图数据中的坐标格式是 [longitude, latitude]
|
|
9775
|
+
const swLat = validBounds.sw[1] + defaultTransform.y;
|
|
9776
|
+
const swLng = validBounds.sw[0] + defaultTransform.x;
|
|
9777
|
+
const neLat = validBounds.ne[1] + defaultTransform.y;
|
|
9778
|
+
const neLng = validBounds.ne[0] + defaultTransform.x;
|
|
9779
|
+
const googleBounds = new window.google.maps.LatLngBounds(new window.google.maps.LatLng(swLat, swLng), // 西南角
|
|
9780
|
+
new window.google.maps.LatLng(neLat, neLng) // 东北角
|
|
9781
|
+
);
|
|
9782
|
+
mapRef.fitBounds(googleBounds);
|
|
9783
|
+
}, [defaultTransform]);
|
|
9784
|
+
React.useEffect(() => {
|
|
9785
|
+
if (!overlayRef || !overlayRef.current)
|
|
9786
|
+
return;
|
|
9787
|
+
overlayRef.current.setEdger(edger);
|
|
9788
|
+
}, [edger]);
|
|
9789
|
+
// 提供ref方法
|
|
9790
|
+
React.useImperativeHandle(ref, () => ({
|
|
9791
|
+
fitToView: () => {
|
|
9792
|
+
fitBounds();
|
|
8904
9793
|
},
|
|
8905
9794
|
getOverlay: () => {
|
|
8906
9795
|
return overlayRef.current;
|
|
@@ -8929,13 +9818,35 @@ const MowerMapRenderer = React.forwardRef(({ mapConfig, modelType, mapRef, mapJs
|
|
|
8929
9818
|
getElementCount: () => elementCount,
|
|
8930
9819
|
getPathCount: () => pathCount,
|
|
8931
9820
|
isGoogleMapsReady: () => isGoogleMapsReady,
|
|
9821
|
+
setTransform: (t) => overlayRef.current?.setTransform(t),
|
|
9822
|
+
resetToDefaultTransform: () => overlayRef.current?.resetToDefaultTransform(),
|
|
8932
9823
|
}));
|
|
9824
|
+
// Debug信息组件
|
|
9825
|
+
const DebugInfo = () => {
|
|
9826
|
+
if (!debug)
|
|
9827
|
+
return null;
|
|
9828
|
+
return (jsxRuntime.jsxs("div", { style: {
|
|
9829
|
+
position: 'fixed',
|
|
9830
|
+
bottom: '10px',
|
|
9831
|
+
left: '10px',
|
|
9832
|
+
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
9833
|
+
color: 'white',
|
|
9834
|
+
padding: '10px',
|
|
9835
|
+
borderRadius: '5px',
|
|
9836
|
+
fontSize: '12px',
|
|
9837
|
+
fontFamily: 'monospace',
|
|
9838
|
+
zIndex: 10000,
|
|
9839
|
+
maxWidth: '300px',
|
|
9840
|
+
lineHeight: '1.4',
|
|
9841
|
+
}, children: [jsxRuntime.jsx("div", { style: { fontWeight: 'bold', marginBottom: '8px' }, children: "\uD83D\uDC1B Debug Info" }), debugInfo.mapBounds && (jsxRuntime.jsxs("div", { style: { marginBottom: '6px' }, children: [jsxRuntime.jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDCCD Map GPS Bounds:" }), jsxRuntime.jsxs("div", { children: ["SW: [", debugInfo.mapBounds.sw[0].toFixed(6), ", ", debugInfo.mapBounds.sw[1].toFixed(6), "]"] }), jsxRuntime.jsxs("div", { children: ["NE: [", debugInfo.mapBounds.ne[0].toFixed(6), ", ", debugInfo.mapBounds.ne[1].toFixed(6), "]"] })] })), debugInfo.viewBox && (jsxRuntime.jsxs("div", { style: { marginBottom: '6px' }, children: [jsxRuntime.jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDCD0 SVG ViewBox (meters):" }), jsxRuntime.jsxs("div", { children: ["SW: [", debugInfo.viewBox.sw.x.toFixed(2), ", ", debugInfo.viewBox.sw.y.toFixed(2), "]"] }), jsxRuntime.jsxs("div", { children: ["NE: [", debugInfo.viewBox.ne.x.toFixed(2), ", ", debugInfo.viewBox.ne.y.toFixed(2), "]"] }), jsxRuntime.jsxs("div", { children: ["Size: ", debugInfo.viewBox.width.toFixed(2), "m \u00D7 ", debugInfo.viewBox.height.toFixed(2), "m"] }), jsxRuntime.jsxs("div", { children: ["Scale: 1:", debugInfo.viewBox.scale] })] })), debugInfo.mowerPosition && (jsxRuntime.jsxs("div", { style: { marginBottom: '6px' }, children: [jsxRuntime.jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDE9C Mower Position:" }), jsxRuntime.jsxs("div", { children: ["Current: X=", debugInfo.mowerPosition.x.toFixed(2), ", Y=", debugInfo.mowerPosition.y.toFixed(2)] }), jsxRuntime.jsxs("div", { children: ["Theta: ", ((debugInfo.mowerPosition.theta * 180) / Math.PI).toFixed(1), "\u00B0"] }), jsxRuntime.jsxs("div", { children: ["Last: X=", debugInfo.mowerPosition.lastX.toFixed(2), ", Y=", debugInfo.mowerPosition.lastY.toFixed(2)] }), jsxRuntime.jsxs("div", { children: ["Last Theta: ", ((debugInfo.mowerPosition.lastTheta * 180) / Math.PI).toFixed(1), "\u00B0"] }), jsxRuntime.jsxs("div", { children: ["Status: ", debugInfo.mowerPosition.vehicleStateText, " (", debugInfo.mowerPosition.vehicleState, ")"] })] })), debugInfo.partitionData && (jsxRuntime.jsxs("div", { style: { marginBottom: '6px' }, children: [jsxRuntime.jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDD32 Mow Partition Data:" }), jsxRuntime.jsxs("div", { children: ["Type: ", debugInfo.partitionData.type || 'N/A'] }), debugInfo.partitionData.partitionIds &&
|
|
9842
|
+
debugInfo.partitionData.partitionIds.length > 0 ? (jsxRuntime.jsxs("div", { children: ["Active IDs: [", debugInfo.partitionData.partitionIds.join(', '), "]"] })) : (jsxRuntime.jsx("div", { children: "No active partitions" })), debugInfo.partitionData.time && (jsxRuntime.jsxs("div", { children: ["Updated: ", new Date(debugInfo.partitionData.time).toLocaleTimeString()] }))] })), !debugInfo.partitionData && (jsxRuntime.jsxs("div", { style: { marginBottom: '6px' }, children: [jsxRuntime.jsx("div", { style: { fontWeight: 'bold' }, children: "\uD83D\uDD32 Mow Partition Data:" }), jsxRuntime.jsx("div", { style: { color: '#888' }, children: "No partition data available" })] }))] }));
|
|
9843
|
+
};
|
|
8933
9844
|
// 错误显示
|
|
8934
9845
|
if (currentError) {
|
|
8935
|
-
return (jsxRuntime.
|
|
9846
|
+
return (jsxRuntime.jsxs("div", { className: className, style: style, children: [jsxRuntime.jsxs("div", { style: { color: 'red', padding: '10px' }, children: ["\u9519\u8BEF: ", currentError] }), jsxRuntime.jsx(DebugInfo, {})] }));
|
|
8936
9847
|
}
|
|
8937
|
-
// 使用goole maps
|
|
8938
|
-
return null;
|
|
9848
|
+
// 使用goole maps自定义叠加层,返回debug信息(如果启用)
|
|
9849
|
+
return debug ? jsxRuntime.jsx(DebugInfo, {}) : null;
|
|
8939
9850
|
});
|
|
8940
9851
|
MowerMapRenderer.displayName = 'MowerMapRenderer';
|
|
8941
9852
|
|