@fleet-frontend/mower-maps 0.1.0-beta.9 → 0.1.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.esm.js +767 -566
- package/dist/index.js +767 -566
- package/dist/render/AntennaManager.d.ts +1 -0
- package/dist/render/AntennaManager.d.ts.map +1 -1
- package/dist/render/BoundaryLabelsManager.d.ts +1 -0
- package/dist/render/BoundaryLabelsManager.d.ts.map +1 -1
- package/dist/render/MowerMapOverlay.d.ts +1 -0
- package/dist/render/MowerMapOverlay.d.ts.map +1 -1
- package/dist/render/MowerMapRenderer.d.ts.map +1 -1
- package/dist/render/layers/ObstacleLayer.d.ts +16 -0
- package/dist/render/layers/ObstacleLayer.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3934,336 +3934,6 @@ class BoundaryLayer extends BaseLayer {
|
|
|
3934
3934
|
}
|
|
3935
3935
|
}
|
|
3936
3936
|
|
|
3937
|
-
/**
|
|
3938
|
-
* 障碍物图层
|
|
3939
|
-
* 专门处理障碍物元素的渲染
|
|
3940
|
-
*/
|
|
3941
|
-
class ObstacleLayer extends BaseLayer {
|
|
3942
|
-
constructor() {
|
|
3943
|
-
super();
|
|
3944
|
-
this.level = 5;
|
|
3945
|
-
this.type = LAYER_DEFAULT_TYPE.OBSTACLE;
|
|
3946
|
-
}
|
|
3947
|
-
/**
|
|
3948
|
-
* SVG渲染方法
|
|
3949
|
-
*/
|
|
3950
|
-
drawSVG(svgGroup) {
|
|
3951
|
-
if (!this.visible || this.elements.length === 0) {
|
|
3952
|
-
return;
|
|
3953
|
-
}
|
|
3954
|
-
// 只渲染障碍物类型的元素
|
|
3955
|
-
for (const element of this.elements) {
|
|
3956
|
-
if (element.type === 'obstacle') {
|
|
3957
|
-
this.renderObstacle(svgGroup, element);
|
|
3958
|
-
}
|
|
3959
|
-
}
|
|
3960
|
-
}
|
|
3961
|
-
/**
|
|
3962
|
-
* 渲染障碍物元素
|
|
3963
|
-
*/
|
|
3964
|
-
renderObstacle(svgGroup, element) {
|
|
3965
|
-
const { coordinates, style } = element;
|
|
3966
|
-
if (coordinates.length < 3)
|
|
3967
|
-
return;
|
|
3968
|
-
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
3969
|
-
// 构建点集合,使用整数坐标
|
|
3970
|
-
const points = coordinates.map((coord) => `${coord[0]},${coord[1]}`).join(' ');
|
|
3971
|
-
polygon.setAttribute('points', points);
|
|
3972
|
-
polygon.setAttribute('fill', style.fillColor || 'rgba(220, 53, 69, 0.2)');
|
|
3973
|
-
polygon.setAttribute('stroke', style.lineColor || '#dc3545');
|
|
3974
|
-
// 确保最小线条宽度
|
|
3975
|
-
const lineWidth = Math.max(style.lineWidth || 2, 0.5);
|
|
3976
|
-
polygon.setAttribute('stroke-width', lineWidth.toString());
|
|
3977
|
-
polygon.setAttribute('stroke-linecap', 'round');
|
|
3978
|
-
polygon.setAttribute('stroke-linejoin', 'round');
|
|
3979
|
-
polygon.setAttribute('opacity', (style.opacity || 1).toString());
|
|
3980
|
-
polygon.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
3981
|
-
polygon.classList.add('vector-obstacle');
|
|
3982
|
-
svgGroup.appendChild(polygon);
|
|
3983
|
-
}
|
|
3984
|
-
}
|
|
3985
|
-
|
|
3986
|
-
var chargingPileImage = "data:image/png;base64,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";
|
|
3987
|
-
|
|
3988
|
-
/**
|
|
3989
|
-
* 充电桩图层
|
|
3990
|
-
* 专门处理充电桩元素的渲染
|
|
3991
|
-
*/
|
|
3992
|
-
class ChargingPileLayer extends BaseLayer {
|
|
3993
|
-
constructor() {
|
|
3994
|
-
super();
|
|
3995
|
-
this.level = 8; // 中等层级
|
|
3996
|
-
this.type = LAYER_DEFAULT_TYPE.CHARGING_PILE;
|
|
3997
|
-
}
|
|
3998
|
-
/**
|
|
3999
|
-
* SVG渲染方法
|
|
4000
|
-
*/
|
|
4001
|
-
drawSVG(svgGroup) {
|
|
4002
|
-
if (!this.visible || this.elements.length === 0) {
|
|
4003
|
-
return;
|
|
4004
|
-
}
|
|
4005
|
-
// 只渲染充电桩类型的元素
|
|
4006
|
-
for (const element of this.elements) {
|
|
4007
|
-
if (element.type === 'charging_pile') {
|
|
4008
|
-
this.renderChargingPile(svgGroup, element);
|
|
4009
|
-
}
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
4012
|
-
/**
|
|
4013
|
-
* 渲染充电桩元素
|
|
4014
|
-
*/
|
|
4015
|
-
renderChargingPile(svgGroup, element) {
|
|
4016
|
-
const { coordinates, style } = element;
|
|
4017
|
-
if (coordinates.length === 0)
|
|
4018
|
-
return;
|
|
4019
|
-
const center = coordinates[0];
|
|
4020
|
-
const size = style.radius ? style.radius * 2 : 55; // 默认55px大小
|
|
4021
|
-
const direction = element.originalData?.direction || 0;
|
|
4022
|
-
// 将弧度转换为角度
|
|
4023
|
-
const angle = (direction * 180) / Math.PI;
|
|
4024
|
-
const rotationDegree = 270 - angle; // 坐标系转换
|
|
4025
|
-
const image = document.createElementNS('http://www.w3.org/2000/svg', 'image');
|
|
4026
|
-
// 图片居中定位
|
|
4027
|
-
const x = center[0];
|
|
4028
|
-
const y = center[1];
|
|
4029
|
-
image.setAttribute('x', x.toString());
|
|
4030
|
-
image.setAttribute('y', y.toString());
|
|
4031
|
-
image.setAttribute('width', `${size}px`);
|
|
4032
|
-
image.setAttribute('height', `${size}px`);
|
|
4033
|
-
image.setAttribute('href', chargingPileImage);
|
|
4034
|
-
image.setAttribute('opacity', '0'); // 初始透明
|
|
4035
|
-
// 添加SVG原生动画,传入默认角度
|
|
4036
|
-
this.addChargingPileAnimation(image, center, rotationDegree);
|
|
4037
|
-
image.classList.add('vector-charging-pile');
|
|
4038
|
-
svgGroup.appendChild(image);
|
|
4039
|
-
}
|
|
4040
|
-
/**
|
|
4041
|
-
* 添加充电桩SVG原生动画
|
|
4042
|
-
*/
|
|
4043
|
-
addChargingPileAnimation(image, center, defaultAngle) {
|
|
4044
|
-
// 透明度动画 - 先显示出来
|
|
4045
|
-
const animateOpacity = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
|
|
4046
|
-
animateOpacity.setAttribute('attributeName', 'opacity');
|
|
4047
|
-
animateOpacity.setAttribute('values', '0;1');
|
|
4048
|
-
animateOpacity.setAttribute('dur', '0.5s');
|
|
4049
|
-
animateOpacity.setAttribute('fill', 'freeze');
|
|
4050
|
-
image.appendChild(animateOpacity);
|
|
4051
|
-
// 旋转动画 - 从180度旋转到默认角度
|
|
4052
|
-
const animateTransform = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
|
|
4053
|
-
animateTransform.setAttribute('attributeName', 'transform');
|
|
4054
|
-
animateTransform.setAttribute('type', 'rotate');
|
|
4055
|
-
animateTransform.setAttribute('values', `${180 + defaultAngle} ${center[0]} ${center[1]};${defaultAngle} ${center[0]} ${center[1]}`);
|
|
4056
|
-
animateTransform.setAttribute('dur', '1s');
|
|
4057
|
-
animateTransform.setAttribute('repeatCount', '1'); // 只播放一次
|
|
4058
|
-
animateTransform.setAttribute('begin', '0.5s'); // 延迟0.5秒开始,等透明度动画完成
|
|
4059
|
-
animateTransform.setAttribute('fill', 'freeze'); // 保持最终状态
|
|
4060
|
-
image.appendChild(animateTransform);
|
|
4061
|
-
}
|
|
4062
|
-
}
|
|
4063
|
-
|
|
4064
|
-
/**
|
|
4065
|
-
* 点图层
|
|
4066
|
-
* 专门处理点元素的渲染
|
|
4067
|
-
*/
|
|
4068
|
-
class PointLayer extends BaseLayer {
|
|
4069
|
-
constructor() {
|
|
4070
|
-
super();
|
|
4071
|
-
this.level = 11;
|
|
4072
|
-
this.type = LAYER_DEFAULT_TYPE.POINT;
|
|
4073
|
-
}
|
|
4074
|
-
/**
|
|
4075
|
-
* SVG渲染方法
|
|
4076
|
-
*/
|
|
4077
|
-
drawSVG(svgGroup) {
|
|
4078
|
-
if (!this.visible || this.elements.length === 0) {
|
|
4079
|
-
return;
|
|
4080
|
-
}
|
|
4081
|
-
// 只渲染点类型的元素
|
|
4082
|
-
for (const element of this.elements) {
|
|
4083
|
-
if (element.type === 'point') {
|
|
4084
|
-
this.renderPoint(svgGroup, element);
|
|
4085
|
-
}
|
|
4086
|
-
}
|
|
4087
|
-
}
|
|
4088
|
-
/**
|
|
4089
|
-
* 渲染点元素
|
|
4090
|
-
*/
|
|
4091
|
-
renderPoint(svgGroup, element) {
|
|
4092
|
-
const { coordinates, style } = element;
|
|
4093
|
-
if (coordinates.length === 0)
|
|
4094
|
-
return;
|
|
4095
|
-
const coordinate = coordinates[0];
|
|
4096
|
-
const radius = style.radius || 4;
|
|
4097
|
-
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
4098
|
-
circle.setAttribute('cx', coordinate[0].toString());
|
|
4099
|
-
circle.setAttribute('cy', coordinate[1].toString());
|
|
4100
|
-
circle.setAttribute('r', radius.toString());
|
|
4101
|
-
circle.setAttribute('fill', style.fillColor || style.strokeColor || '#000000');
|
|
4102
|
-
circle.setAttribute('stroke', style.strokeColor || '#000000');
|
|
4103
|
-
// 确保最小线条宽度
|
|
4104
|
-
const lineWidth = Math.max(style.lineWidth || 1, 0.5);
|
|
4105
|
-
circle.setAttribute('stroke-width', lineWidth.toString());
|
|
4106
|
-
circle.setAttribute('opacity', (style.opacity || 1).toString());
|
|
4107
|
-
circle.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
4108
|
-
circle.classList.add('vector-point');
|
|
4109
|
-
svgGroup.appendChild(circle);
|
|
4110
|
-
}
|
|
4111
|
-
}
|
|
4112
|
-
|
|
4113
|
-
/**
|
|
4114
|
-
* SVG元素图层
|
|
4115
|
-
* 专门处理SVG元素的渲染
|
|
4116
|
-
*/
|
|
4117
|
-
class SvgElementLayer extends BaseLayer {
|
|
4118
|
-
constructor() {
|
|
4119
|
-
super();
|
|
4120
|
-
this.level = 6;
|
|
4121
|
-
this.scale = 1;
|
|
4122
|
-
this.type = LAYER_DEFAULT_TYPE.SVG;
|
|
4123
|
-
}
|
|
4124
|
-
/**
|
|
4125
|
-
* SVG渲染方法
|
|
4126
|
-
*/
|
|
4127
|
-
drawSVG(svgGroup) {
|
|
4128
|
-
if (!this.visible || this.elements.length === 0) {
|
|
4129
|
-
return;
|
|
4130
|
-
}
|
|
4131
|
-
// 只渲染SVG类型的元素
|
|
4132
|
-
for (const element of this.elements) {
|
|
4133
|
-
const expirationTs = element.originalData?.expiration_ts;
|
|
4134
|
-
const current = Date.now() / 1000;
|
|
4135
|
-
if (expirationTs && current > expirationTs) {
|
|
4136
|
-
continue;
|
|
4137
|
-
}
|
|
4138
|
-
if (element.type === 'svg') {
|
|
4139
|
-
this.renderSvgElement(svgGroup, element);
|
|
4140
|
-
}
|
|
4141
|
-
}
|
|
4142
|
-
}
|
|
4143
|
-
/**
|
|
4144
|
-
* 渲染SVG元素
|
|
4145
|
-
*/
|
|
4146
|
-
renderSvgElement(svgGroup, element) {
|
|
4147
|
-
const { coordinates, style, metadata } = element;
|
|
4148
|
-
if (coordinates.length === 0)
|
|
4149
|
-
return;
|
|
4150
|
-
const center = coordinates[0];
|
|
4151
|
-
if (!metadata || !metadata.svg) {
|
|
4152
|
-
this.renderSvgPlaceholder(svgGroup, center, metadata, style);
|
|
4153
|
-
return;
|
|
4154
|
-
}
|
|
4155
|
-
try {
|
|
4156
|
-
// 解析SVG字符串
|
|
4157
|
-
const svgString = metadata.svg.replace(/\\n/g, '\n').replace(/\\"/g, '"');
|
|
4158
|
-
const parser = new DOMParser();
|
|
4159
|
-
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml');
|
|
4160
|
-
const svgElement = svgDoc.documentElement;
|
|
4161
|
-
if (svgElement.tagName === 'svg') {
|
|
4162
|
-
// 获取原始SVG尺寸
|
|
4163
|
-
const originalWidth = parseFloat(svgElement.getAttribute('width') || '139');
|
|
4164
|
-
const originalHeight = parseFloat(svgElement.getAttribute('height') || '138');
|
|
4165
|
-
// 计算变换参数
|
|
4166
|
-
const userScale = metadata.scale || 1;
|
|
4167
|
-
const direction = metadata.direction || 0;
|
|
4168
|
-
// 设置原始SVG的实际尺寸(限制大小)
|
|
4169
|
-
svgElement.setAttribute('width', originalWidth.toString());
|
|
4170
|
-
svgElement.setAttribute('height', originalHeight.toString());
|
|
4171
|
-
// 创建变换组
|
|
4172
|
-
const transformGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
4173
|
-
// 在transformGroup上应用变换:平移到中心,旋转,缩放,然后居中SVG
|
|
4174
|
-
const transform = [
|
|
4175
|
-
`translate(${center[0]}, ${center[1]})`,
|
|
4176
|
-
`rotate(${-(direction * 180) / Math.PI})`,
|
|
4177
|
-
`scale(${userScale})`,
|
|
4178
|
-
`translate(${-originalWidth / 2}, ${-originalHeight / 2})`,
|
|
4179
|
-
].join(' ');
|
|
4180
|
-
transformGroup.setAttribute('transform', transform);
|
|
4181
|
-
// 设置样式
|
|
4182
|
-
if (style.opacity !== undefined) {
|
|
4183
|
-
transformGroup.setAttribute('opacity', style.opacity.toString());
|
|
4184
|
-
}
|
|
4185
|
-
// 将限制好尺寸的原始SVG添加到transformGroup中
|
|
4186
|
-
transformGroup.appendChild(svgElement);
|
|
4187
|
-
// 将transformGroup添加到svgGroup中
|
|
4188
|
-
svgGroup.appendChild(transformGroup);
|
|
4189
|
-
}
|
|
4190
|
-
else {
|
|
4191
|
-
this.renderSvgPlaceholder(svgGroup, center, metadata, style);
|
|
4192
|
-
}
|
|
4193
|
-
}
|
|
4194
|
-
catch (error) {
|
|
4195
|
-
console.warn('Failed to parse SVG:', error);
|
|
4196
|
-
this.renderSvgPlaceholder(svgGroup, center, metadata, style);
|
|
4197
|
-
}
|
|
4198
|
-
}
|
|
4199
|
-
/**
|
|
4200
|
-
* 渲染SVG占位符
|
|
4201
|
-
*/
|
|
4202
|
-
renderSvgPlaceholder(svgGroup, center, metadata, style) {
|
|
4203
|
-
const size = (metadata?.scale || 1) * 20;
|
|
4204
|
-
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
4205
|
-
rect.setAttribute('x', ((center[0] - size / 2) / 50).toString());
|
|
4206
|
-
rect.setAttribute('y', ((center[1] - size / 2) / 50).toString());
|
|
4207
|
-
rect.setAttribute('width', size.toString());
|
|
4208
|
-
rect.setAttribute('height', size.toString());
|
|
4209
|
-
rect.setAttribute('fill', style.fillColor);
|
|
4210
|
-
rect.setAttribute('stroke', style.lineColor);
|
|
4211
|
-
rect.setAttribute('stroke-width', style.lineWidth.toString());
|
|
4212
|
-
rect.setAttribute('opacity', style.opacity.toString());
|
|
4213
|
-
svgGroup.appendChild(rect);
|
|
4214
|
-
}
|
|
4215
|
-
}
|
|
4216
|
-
|
|
4217
|
-
/**
|
|
4218
|
-
* 边界图层
|
|
4219
|
-
* 专门处理边界元素的渲染
|
|
4220
|
-
*/
|
|
4221
|
-
class VisionOffLayer extends BaseLayer {
|
|
4222
|
-
constructor() {
|
|
4223
|
-
super();
|
|
4224
|
-
this.type = LAYER_DEFAULT_TYPE.VISION_OFF_AREA;
|
|
4225
|
-
this.level = 7; // 中等层级
|
|
4226
|
-
}
|
|
4227
|
-
/**
|
|
4228
|
-
* SVG渲染方法
|
|
4229
|
-
*/
|
|
4230
|
-
drawSVG(svgGroup) {
|
|
4231
|
-
if (!this.visible || this.elements.length === 0) {
|
|
4232
|
-
return;
|
|
4233
|
-
}
|
|
4234
|
-
// 只渲染边界类型的元素
|
|
4235
|
-
for (const element of this.elements) {
|
|
4236
|
-
if (element.type === 'vision_off_area') {
|
|
4237
|
-
this.renderVisionOffArea(svgGroup, element);
|
|
4238
|
-
}
|
|
4239
|
-
}
|
|
4240
|
-
}
|
|
4241
|
-
/**
|
|
4242
|
-
* 渲染边界元素
|
|
4243
|
-
*/
|
|
4244
|
-
renderVisionOffArea(svgGroup, element) {
|
|
4245
|
-
const { coordinates, style } = element;
|
|
4246
|
-
if (coordinates.length < 3)
|
|
4247
|
-
return;
|
|
4248
|
-
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
4249
|
-
// 构建点集合,使用整数坐标
|
|
4250
|
-
const points = coordinates.map((coord) => `${coord[0]},${coord[1]}`).join(' ');
|
|
4251
|
-
const fillColor = style.fillColor || 'rgba(0, 255, 0, 0.4)';
|
|
4252
|
-
polygon.setAttribute('points', points);
|
|
4253
|
-
polygon.setAttribute('fill', fillColor);
|
|
4254
|
-
polygon.setAttribute('stroke', style.lineColor);
|
|
4255
|
-
// 确保最小线条宽度
|
|
4256
|
-
const lineWidth = Math.max(style.lineWidth || 2, 0.5);
|
|
4257
|
-
polygon.setAttribute('stroke-width', lineWidth.toString());
|
|
4258
|
-
polygon.setAttribute('stroke-linecap', 'round');
|
|
4259
|
-
polygon.setAttribute('stroke-linejoin', 'round');
|
|
4260
|
-
polygon.setAttribute('opacity', (style.opacity || 1).toString());
|
|
4261
|
-
polygon.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
4262
|
-
polygon.classList.add('vector-boundary');
|
|
4263
|
-
svgGroup.appendChild(polygon);
|
|
4264
|
-
}
|
|
4265
|
-
}
|
|
4266
|
-
|
|
4267
3937
|
/**
|
|
4268
3938
|
* 边界相关样式配置
|
|
4269
3939
|
*/
|
|
@@ -7677,254 +7347,753 @@ var UnitsAreaType;
|
|
|
7677
7347
|
})(UnitsAreaType || (UnitsAreaType = {}));
|
|
7678
7348
|
|
|
7679
7349
|
/**
|
|
7680
|
-
* 默认航向相对于canvas的偏移角度: 航向默认是东
|
|
7350
|
+
* 默认航向相对于canvas的偏移角度: 航向默认是东
|
|
7351
|
+
*/
|
|
7352
|
+
const DIRECTION_DEGREE = 90; //正东相当于canvas的正北顺时针旋转90度
|
|
7353
|
+
/**
|
|
7354
|
+
* 矫正deltaTheta的范围,防止iot通道下因为车端theta接近π值时跳变造成app显示小车图标旋转的问题
|
|
7355
|
+
*
|
|
7356
|
+
* @param postureTheta
|
|
7357
|
+
* @return
|
|
7358
|
+
*/
|
|
7359
|
+
function radNormalize(radian) {
|
|
7360
|
+
if (radian > Math.PI) {
|
|
7361
|
+
radian -= 2 * Math.PI; // 顺时针大于π时,减去2π
|
|
7362
|
+
}
|
|
7363
|
+
else if (radian < -Math.PI) {
|
|
7364
|
+
radian += 2 * Math.PI; // 逆时针小于-π时,加上2π
|
|
7365
|
+
}
|
|
7366
|
+
return radian;
|
|
7367
|
+
}
|
|
7368
|
+
// 将弧度转换为角度,并加上默认航向相对于canvas的偏移角度
|
|
7369
|
+
function radToDegree(radian) {
|
|
7370
|
+
return DIRECTION_DEGREE - radian * (180 / Math.PI);
|
|
7371
|
+
}
|
|
7372
|
+
// 计算两个点之间的间隔,单位是m
|
|
7373
|
+
function distance(x1, y1, x2, y2) {
|
|
7374
|
+
return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
|
|
7375
|
+
}
|
|
7376
|
+
const mathRound = (value, decimals = 2) => {
|
|
7377
|
+
return Number.isInteger(value) ? value : round(value, decimals);
|
|
7378
|
+
};
|
|
7379
|
+
const mathFloor = (value, decimals = 2) => {
|
|
7380
|
+
return Number.isInteger(value) ? value : floor(value, decimals);
|
|
7381
|
+
};
|
|
7382
|
+
// 公制平方米-》英制平方英尺
|
|
7383
|
+
function areaToft2(area) {
|
|
7384
|
+
return area * 10.7639104167;
|
|
7385
|
+
}
|
|
7386
|
+
//
|
|
7387
|
+
// 平方英尺-》英亩
|
|
7388
|
+
function ft2ToAcre(area) {
|
|
7389
|
+
return area / 43559.98;
|
|
7390
|
+
}
|
|
7391
|
+
/**
|
|
7392
|
+
* 将数字格式化为带有度量单位前缀的形式
|
|
7393
|
+
* @param value 需要格式化的数值
|
|
7394
|
+
* @param round 是否四舍五入,默认为true
|
|
7395
|
+
* @param decimals 保留小数位数,默认为2
|
|
7396
|
+
* @returns 格式化后的字符串,根据数值大小自动添加对应的度量单位前缀(k/M/B)
|
|
7397
|
+
* @example
|
|
7398
|
+
* formatNumberWithMetricPrefix(500) // 返回 "500"
|
|
7399
|
+
* formatNumberWithMetricPrefix(1500) // 返回 "1.50k"
|
|
7400
|
+
* formatNumberWithMetricPrefix(1500000) // 返回 "1.50M"
|
|
7401
|
+
* formatNumberWithMetricPrefix(2500000000) // 返回 "2.50B"
|
|
7402
|
+
*/
|
|
7403
|
+
function formatNumberWithMetricPrefix(value, round = true, decimals = 2) {
|
|
7404
|
+
if (value === undefined)
|
|
7405
|
+
return value;
|
|
7406
|
+
if (value === 0)
|
|
7407
|
+
return '0';
|
|
7408
|
+
const mathFn = round ? mathRound : mathFloor;
|
|
7409
|
+
if (value < 1000) {
|
|
7410
|
+
return `${mathFn(value, decimals)}`;
|
|
7411
|
+
}
|
|
7412
|
+
else if (value < 1000000) {
|
|
7413
|
+
return `${mathFn(value / 1000, decimals)}K`;
|
|
7414
|
+
}
|
|
7415
|
+
else if (value < 1000000000) {
|
|
7416
|
+
return `${mathFn(value / 1000000, decimals)}M`;
|
|
7417
|
+
}
|
|
7418
|
+
else {
|
|
7419
|
+
return `${mathFn(value / 1000000000, decimals)}B`;
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
/**
|
|
7423
|
+
* 转换割草面积的方法
|
|
7424
|
+
* @param area 面积数值(单位:m²)
|
|
7425
|
+
* @param type 单位类型 'metric' | 'imperial'
|
|
7426
|
+
* @returns {{ originNum: number; numStr?: string, value: string, unit: UnitsAreaType }} 返回格式化后的面积值和单位
|
|
7427
|
+
* @example
|
|
7428
|
+
* convertMowingArea(500, 'metric') // 返回 { value: "500", unit: "m²" }
|
|
7429
|
+
* convertMowingArea(1500, 'imperial') // 返回 { value: "16.1K", unit: "ft²" }
|
|
7430
|
+
* convertMowingArea(10000, 'imperial') // 返回 { value: "2.5", unit: "ac" }
|
|
7431
|
+
*/
|
|
7432
|
+
function convertAreaByUnits(area, type) {
|
|
7433
|
+
if (area === undefined)
|
|
7434
|
+
return {
|
|
7435
|
+
originNum: 0,
|
|
7436
|
+
numStr: '0',
|
|
7437
|
+
value: `0 ${type === UnitsType.Metric
|
|
7438
|
+
? UnitsAreaType.SQUARE_METER
|
|
7439
|
+
: UnitsAreaType.SQUARE_FOOT}`,
|
|
7440
|
+
unit: type === UnitsType.Metric
|
|
7441
|
+
? UnitsAreaType.SQUARE_METER
|
|
7442
|
+
: UnitsAreaType.SQUARE_FOOT,
|
|
7443
|
+
};
|
|
7444
|
+
if (type === UnitsType.Metric) {
|
|
7445
|
+
return {
|
|
7446
|
+
originNum: area,
|
|
7447
|
+
numStr: formatNumberWithMetricPrefix(area, false),
|
|
7448
|
+
value: `${formatNumberWithMetricPrefix(area, false)} ${UnitsAreaType.SQUARE_METER}`,
|
|
7449
|
+
unit: UnitsAreaType.SQUARE_METER,
|
|
7450
|
+
};
|
|
7451
|
+
}
|
|
7452
|
+
// 将 m² 转换为 ft²(1 m² = 10.7639 ft²)
|
|
7453
|
+
const squareFeet = areaToft2(area);
|
|
7454
|
+
if (squareFeet < 10000) {
|
|
7455
|
+
return {
|
|
7456
|
+
originNum: squareFeet,
|
|
7457
|
+
numStr: formatNumberWithMetricPrefix(squareFeet, false),
|
|
7458
|
+
value: `${formatNumberWithMetricPrefix(squareFeet, false)} ${UnitsAreaType.SQUARE_FOOT}`,
|
|
7459
|
+
unit: UnitsAreaType.SQUARE_FOOT,
|
|
7460
|
+
};
|
|
7461
|
+
}
|
|
7462
|
+
const acres = ft2ToAcre(squareFeet);
|
|
7463
|
+
// 将 ft² 转换为 ac(1 ac = 43560 ft²)
|
|
7464
|
+
const result = formatNumberWithMetricPrefix(acres, false);
|
|
7465
|
+
return {
|
|
7466
|
+
originNum: acres,
|
|
7467
|
+
numStr: result,
|
|
7468
|
+
value: `${acres} ${UnitsAreaType.ACRE}`,
|
|
7469
|
+
unit: UnitsAreaType.ACRE,
|
|
7470
|
+
};
|
|
7471
|
+
}
|
|
7472
|
+
|
|
7473
|
+
/**
|
|
7474
|
+
* 日期时间格式化工具函数
|
|
7475
|
+
* 专门处理boundary中的日期时间显示格式
|
|
7476
|
+
*/
|
|
7477
|
+
/**
|
|
7478
|
+
* 获取一周的开始日期(周日)
|
|
7479
|
+
* @param date 目标日期
|
|
7480
|
+
* @returns 该周的周日日期
|
|
7481
|
+
*/
|
|
7482
|
+
function getWeekStart(date) {
|
|
7483
|
+
const startOfWeek = new Date(date);
|
|
7484
|
+
const dayOfWeek = startOfWeek.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
|
|
7485
|
+
// 向前推移到周日
|
|
7486
|
+
startOfWeek.setDate(startOfWeek.getDate() - dayOfWeek);
|
|
7487
|
+
startOfWeek.setHours(0, 0, 0, 0);
|
|
7488
|
+
return startOfWeek;
|
|
7489
|
+
}
|
|
7490
|
+
/**
|
|
7491
|
+
* 获取一周的结束日期(周六)
|
|
7492
|
+
* @param date 目标日期
|
|
7493
|
+
* @returns 该周的周六日期
|
|
7494
|
+
*/
|
|
7495
|
+
function getWeekEnd(date) {
|
|
7496
|
+
const endOfWeek = new Date(date);
|
|
7497
|
+
const dayOfWeek = endOfWeek.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
|
|
7498
|
+
// 向后推移到周六
|
|
7499
|
+
endOfWeek.setDate(endOfWeek.getDate() + (6 - dayOfWeek));
|
|
7500
|
+
endOfWeek.setHours(23, 59, 59, 999);
|
|
7501
|
+
return endOfWeek;
|
|
7502
|
+
}
|
|
7503
|
+
/**
|
|
7504
|
+
* 判断两个日期是否为同一天
|
|
7505
|
+
* @param date1 第一个日期
|
|
7506
|
+
* @param date2 第二个日期
|
|
7507
|
+
* @returns 是否为同一天
|
|
7508
|
+
*/
|
|
7509
|
+
function isSameDay(date1, date2) {
|
|
7510
|
+
return date1.getFullYear() === date2.getFullYear() &&
|
|
7511
|
+
date1.getMonth() === date2.getMonth() &&
|
|
7512
|
+
date1.getDate() === date2.getDate();
|
|
7513
|
+
}
|
|
7514
|
+
/**
|
|
7515
|
+
* 判断是否为今天
|
|
7516
|
+
* @param date 目标日期
|
|
7517
|
+
* @returns 是否为今天
|
|
7518
|
+
*/
|
|
7519
|
+
function isToday(date) {
|
|
7520
|
+
const today = new Date();
|
|
7521
|
+
return isSameDay(date, today);
|
|
7522
|
+
}
|
|
7523
|
+
/**
|
|
7524
|
+
* 判断是否为昨天
|
|
7525
|
+
* @param date 目标日期
|
|
7526
|
+
* @returns 是否为昨天
|
|
7527
|
+
*/
|
|
7528
|
+
function isYesterday(date) {
|
|
7529
|
+
const yesterday = new Date();
|
|
7530
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
7531
|
+
return isSameDay(date, yesterday);
|
|
7532
|
+
}
|
|
7533
|
+
/**
|
|
7534
|
+
* 判断是否为本周内(以周日为一周的开始)
|
|
7535
|
+
* @param date 目标日期
|
|
7536
|
+
* @returns 是否为本周内
|
|
7537
|
+
*/
|
|
7538
|
+
function isThisWeek(date) {
|
|
7539
|
+
const now = new Date();
|
|
7540
|
+
const weekStart = getWeekStart(now);
|
|
7541
|
+
const weekEnd = getWeekEnd(now);
|
|
7542
|
+
return date >= weekStart && date <= weekEnd;
|
|
7543
|
+
}
|
|
7544
|
+
/**
|
|
7545
|
+
* 格式化时间为 HH:mm 格式
|
|
7546
|
+
* @param date 目标日期
|
|
7547
|
+
* @returns 格式化后的时间字符串
|
|
7681
7548
|
*/
|
|
7682
|
-
|
|
7549
|
+
function formatTime(date) {
|
|
7550
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
7551
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
7552
|
+
return `${hours}:${minutes}`;
|
|
7553
|
+
}
|
|
7683
7554
|
/**
|
|
7684
|
-
*
|
|
7555
|
+
* 获取星期几的英文缩写
|
|
7556
|
+
* @param date 目标日期
|
|
7557
|
+
* @returns 星期几的英文缩写
|
|
7558
|
+
*/
|
|
7559
|
+
function getWeekdayAbbr(date) {
|
|
7560
|
+
const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
7561
|
+
return weekdays[date.getDay()];
|
|
7562
|
+
}
|
|
7563
|
+
/**
|
|
7564
|
+
* 格式化boundary中的日期文本
|
|
7565
|
+
* 根据时间距离当前时间的远近,显示不同的格式:
|
|
7566
|
+
* - 今天:Today HH:mm
|
|
7567
|
+
* - 昨天:Yesterday HH:mm
|
|
7568
|
+
* - 本周内:Tue HH:mm
|
|
7569
|
+
* - 其他:MM/dd/yyyy HH:mm
|
|
7685
7570
|
*
|
|
7686
|
-
* @param
|
|
7687
|
-
* @
|
|
7571
|
+
* @param timestamp 时间戳(秒)
|
|
7572
|
+
* @returns 格式化后的日期文本
|
|
7688
7573
|
*/
|
|
7689
|
-
function
|
|
7690
|
-
if (
|
|
7691
|
-
|
|
7574
|
+
function formatBoundaryDateText(timestamp) {
|
|
7575
|
+
if (!timestamp || timestamp <= 0) {
|
|
7576
|
+
return '--/--/---- --:--';
|
|
7692
7577
|
}
|
|
7693
|
-
|
|
7694
|
-
|
|
7578
|
+
const date = new Date(timestamp * 1000); // 转换为毫秒
|
|
7579
|
+
const timeStr = formatTime(date);
|
|
7580
|
+
// 判断是否为今天
|
|
7581
|
+
if (isToday(date)) {
|
|
7582
|
+
return `Today ${timeStr}`;
|
|
7695
7583
|
}
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
7700
|
-
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
const
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
return Number.isInteger(value) ? value : floor(value, decimals);
|
|
7711
|
-
};
|
|
7712
|
-
// 公制平方米-》英制平方英尺
|
|
7713
|
-
function areaToft2(area) {
|
|
7714
|
-
return area * 10.7639104167;
|
|
7715
|
-
}
|
|
7716
|
-
//
|
|
7717
|
-
// 平方英尺-》英亩
|
|
7718
|
-
function ft2ToAcre(area) {
|
|
7719
|
-
return area / 43559.98;
|
|
7584
|
+
// 判断是否为昨天
|
|
7585
|
+
if (isYesterday(date)) {
|
|
7586
|
+
return `Yesterday ${timeStr}`;
|
|
7587
|
+
}
|
|
7588
|
+
// 判断是否为本周内
|
|
7589
|
+
if (isThisWeek(date)) {
|
|
7590
|
+
const weekdayAbbr = getWeekdayAbbr(date);
|
|
7591
|
+
return `${weekdayAbbr} ${timeStr}`;
|
|
7592
|
+
}
|
|
7593
|
+
// 其他情况显示完整日期
|
|
7594
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
7595
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
7596
|
+
const year = date.getFullYear();
|
|
7597
|
+
return `${month}/${day}/${year} ${timeStr}`;
|
|
7720
7598
|
}
|
|
7599
|
+
|
|
7721
7600
|
/**
|
|
7722
|
-
*
|
|
7723
|
-
*
|
|
7724
|
-
* @param round 是否四舍五入,默认为true
|
|
7725
|
-
* @param decimals 保留小数位数,默认为2
|
|
7726
|
-
* @returns 格式化后的字符串,根据数值大小自动添加对应的度量单位前缀(k/M/B)
|
|
7727
|
-
* @example
|
|
7728
|
-
* formatNumberWithMetricPrefix(500) // 返回 "500"
|
|
7729
|
-
* formatNumberWithMetricPrefix(1500) // 返回 "1.50k"
|
|
7730
|
-
* formatNumberWithMetricPrefix(1500000) // 返回 "1.50M"
|
|
7731
|
-
* formatNumberWithMetricPrefix(2500000000) // 返回 "2.50B"
|
|
7601
|
+
* 障碍物图层
|
|
7602
|
+
* 专门处理障碍物元素的渲染
|
|
7732
7603
|
*/
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
const mathFn = round ? mathRound : mathFloor;
|
|
7739
|
-
if (value < 1000) {
|
|
7740
|
-
return `${mathFn(value, decimals)}`;
|
|
7604
|
+
class ObstacleLayer extends BaseLayer {
|
|
7605
|
+
constructor() {
|
|
7606
|
+
super();
|
|
7607
|
+
this.level = 5;
|
|
7608
|
+
this.type = LAYER_DEFAULT_TYPE.OBSTACLE;
|
|
7741
7609
|
}
|
|
7742
|
-
|
|
7743
|
-
|
|
7610
|
+
/**
|
|
7611
|
+
* SVG渲染方法
|
|
7612
|
+
*/
|
|
7613
|
+
drawSVG(svgGroup) {
|
|
7614
|
+
if (!this.visible || this.elements.length === 0) {
|
|
7615
|
+
return;
|
|
7616
|
+
}
|
|
7617
|
+
// 只渲染障碍物类型的元素
|
|
7618
|
+
for (const element of this.elements) {
|
|
7619
|
+
if (element.type === 'obstacle') {
|
|
7620
|
+
this.renderObstacle(svgGroup, element);
|
|
7621
|
+
}
|
|
7622
|
+
}
|
|
7744
7623
|
}
|
|
7745
|
-
|
|
7746
|
-
|
|
7624
|
+
/**
|
|
7625
|
+
* 将坐标点按type分组
|
|
7626
|
+
*/
|
|
7627
|
+
groupCoordinatesByType(coordinates) {
|
|
7628
|
+
const segments = [];
|
|
7629
|
+
let currentSegment = null;
|
|
7630
|
+
for (let i = 0; i < coordinates.length; i++) {
|
|
7631
|
+
const coord = coordinates[i];
|
|
7632
|
+
const type = coord[2] || 2; // 默认type为2
|
|
7633
|
+
if (!currentSegment || currentSegment.type !== type) {
|
|
7634
|
+
// 开始新的段
|
|
7635
|
+
if (currentSegment && currentSegment.points.length > 0) {
|
|
7636
|
+
// 为了连接线段,将当前点也加入上一段的结尾
|
|
7637
|
+
currentSegment.points.push(coord);
|
|
7638
|
+
}
|
|
7639
|
+
currentSegment = {
|
|
7640
|
+
type: type,
|
|
7641
|
+
points: [coord],
|
|
7642
|
+
};
|
|
7643
|
+
segments.push(currentSegment);
|
|
7644
|
+
}
|
|
7645
|
+
else {
|
|
7646
|
+
// 继续当前段
|
|
7647
|
+
currentSegment.points.push(coord);
|
|
7648
|
+
}
|
|
7649
|
+
}
|
|
7650
|
+
// 处理封闭边界:如果第一段和最后一段type相同,需要连接起来
|
|
7651
|
+
if (segments.length > 1 && segments[0].type === segments[segments.length - 1].type) {
|
|
7652
|
+
const firstSegment = segments[0];
|
|
7653
|
+
const lastSegment = segments[segments.length - 1];
|
|
7654
|
+
// 将第一个点添加到最后一段,形成封闭
|
|
7655
|
+
lastSegment.points.push(firstSegment.points[0]);
|
|
7656
|
+
}
|
|
7657
|
+
else if (segments.length === 1) {
|
|
7658
|
+
// 只有一段的情况,添加第一个点到末尾形成封闭
|
|
7659
|
+
segments[0].points.push(coordinates[0]);
|
|
7660
|
+
}
|
|
7661
|
+
return segments;
|
|
7662
|
+
}
|
|
7663
|
+
/**
|
|
7664
|
+
* 渲染障碍物元素
|
|
7665
|
+
*/
|
|
7666
|
+
renderObstacle(svgGroup, element) {
|
|
7667
|
+
const { coordinates, style, originalData } = element;
|
|
7668
|
+
const { status, start_timestamp, end_timestamp } = originalData || {};
|
|
7669
|
+
if (coordinates.length < 2 ||
|
|
7670
|
+
status === 0 ||
|
|
7671
|
+
start_timestamp > Date.now() / 1000 ||
|
|
7672
|
+
end_timestamp < Date.now() / 1000) {
|
|
7673
|
+
return;
|
|
7674
|
+
}
|
|
7675
|
+
// 1. 先遍历所有的coordinates,把所有点分为若干段的path
|
|
7676
|
+
const pathSegments = this.groupCoordinatesByType(coordinates);
|
|
7677
|
+
// 2&3. 根据type处理每个path段
|
|
7678
|
+
pathSegments.forEach((segment) => {
|
|
7679
|
+
if (segment.points.length < 2)
|
|
7680
|
+
return;
|
|
7681
|
+
if (segment.type === 2) {
|
|
7682
|
+
// type=2: 直接添加到svgGroup中
|
|
7683
|
+
this.createDirectPath(svgGroup, segment.points, style);
|
|
7684
|
+
}
|
|
7685
|
+
else if (segment.type === 1) {
|
|
7686
|
+
// type=1: 使用PathMeasure逻辑生成平行路径
|
|
7687
|
+
// this.createDirectPath(svgGroup, segment.points, style);
|
|
7688
|
+
this.createParallelPathsWithMeasure(svgGroup, segment.points, style);
|
|
7689
|
+
}
|
|
7690
|
+
});
|
|
7691
|
+
if (coordinates.length < 3)
|
|
7692
|
+
return;
|
|
7693
|
+
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
7694
|
+
// 构建点集合,使用整数坐标
|
|
7695
|
+
const points = coordinates.map((coord) => `${coord[0]},${coord[1]}`).join(' ');
|
|
7696
|
+
polygon.setAttribute('points', points);
|
|
7697
|
+
polygon.setAttribute('fill', style.fillColor || 'rgba(220, 53, 69, 0.2)');
|
|
7698
|
+
polygon.setAttribute('stroke', 'transparent');
|
|
7699
|
+
// 确保最小线条宽度
|
|
7700
|
+
const lineWidth = Math.max(style.lineWidth || 2, 0.5);
|
|
7701
|
+
polygon.setAttribute('stroke-width', lineWidth.toString());
|
|
7702
|
+
polygon.setAttribute('stroke-linecap', 'round');
|
|
7703
|
+
polygon.setAttribute('stroke-linejoin', 'round');
|
|
7704
|
+
polygon.setAttribute('opacity', (style.opacity || 1).toString());
|
|
7705
|
+
polygon.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
7706
|
+
polygon.classList.add('vector-obstacle');
|
|
7707
|
+
svgGroup.appendChild(polygon);
|
|
7708
|
+
}
|
|
7709
|
+
/**
|
|
7710
|
+
* 创建直接路径(type=2)
|
|
7711
|
+
*/
|
|
7712
|
+
createDirectPath(svgGroup, points, style) {
|
|
7713
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
7714
|
+
const strokeColor = style.lineColor;
|
|
7715
|
+
// 构建路径数据
|
|
7716
|
+
let pathData = '';
|
|
7717
|
+
for (let i = 0; i < points.length; i++) {
|
|
7718
|
+
const [x, y] = points[i];
|
|
7719
|
+
if (i === 0) {
|
|
7720
|
+
pathData += `M ${x} ${y}`;
|
|
7721
|
+
}
|
|
7722
|
+
else {
|
|
7723
|
+
pathData += ` L ${x} ${y}`;
|
|
7724
|
+
}
|
|
7725
|
+
}
|
|
7726
|
+
path.setAttribute('d', pathData);
|
|
7727
|
+
path.setAttribute('stroke', strokeColor);
|
|
7728
|
+
path.setAttribute('fill', 'none');
|
|
7729
|
+
// 确保最小线条宽度
|
|
7730
|
+
const lineWidth = dp2px(style.lineWidth || 3);
|
|
7731
|
+
path.setAttribute('stroke-width', lineWidth.toString());
|
|
7732
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
7733
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
7734
|
+
path.setAttribute('opacity', (style.opacity || 1).toString());
|
|
7735
|
+
path.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
7736
|
+
path.classList.add('vector-obstacle');
|
|
7737
|
+
svgGroup.appendChild(path);
|
|
7738
|
+
}
|
|
7739
|
+
/**
|
|
7740
|
+
* 使用PathMeasure逻辑创建平行路径(type=1)
|
|
7741
|
+
*/
|
|
7742
|
+
createParallelPathsWithMeasure(svgGroup, points, style) {
|
|
7743
|
+
const strokeColor = style.lineColor;
|
|
7744
|
+
const lineWidth = dp2px(style.lineWidth || 3);
|
|
7745
|
+
// 获取当前SVG的缩放级别,计算固定屏幕像素间距
|
|
7746
|
+
const fixedScreenDistance = lineWidth; // 固定的屏幕像素距离
|
|
7747
|
+
const offsetDistance = fixedScreenDistance; // 转换为SVG坐标系距离
|
|
7748
|
+
// 直接对每个线段生成平行直线段
|
|
7749
|
+
const parallelPaths = this.generateStraightParallelPaths(points, offsetDistance);
|
|
7750
|
+
// 渲染两条平行虚线
|
|
7751
|
+
parallelPaths.forEach((pathData, index) => {
|
|
7752
|
+
if (!pathData)
|
|
7753
|
+
return;
|
|
7754
|
+
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
7755
|
+
path.setAttribute('d', pathData);
|
|
7756
|
+
path.setAttribute('fill', 'none');
|
|
7757
|
+
path.setAttribute('stroke', strokeColor);
|
|
7758
|
+
path.setAttribute('stroke-width', lineWidth.toString());
|
|
7759
|
+
path.setAttribute('stroke-linecap', 'round');
|
|
7760
|
+
path.setAttribute('stroke-linejoin', 'round');
|
|
7761
|
+
path.setAttribute('opacity', (style.opacity || 1).toString());
|
|
7762
|
+
// 使用CSS样式设置虚线,避免随SVG缩放变化
|
|
7763
|
+
// 或者可以根据当前缩放级别动态计算dash array
|
|
7764
|
+
path.style.strokeDasharray = `${lineWidth}px ${lineWidth * 2}px`;
|
|
7765
|
+
path.classList.add(`vector-boundary-parallel-${index + 1}`);
|
|
7766
|
+
svgGroup.appendChild(path);
|
|
7767
|
+
});
|
|
7747
7768
|
}
|
|
7748
|
-
|
|
7749
|
-
|
|
7769
|
+
/**
|
|
7770
|
+
* 生成直线平行路径(每个线段分别处理)
|
|
7771
|
+
*/
|
|
7772
|
+
generateStraightParallelPaths(points, offsetDistance) {
|
|
7773
|
+
if (points.length < 2)
|
|
7774
|
+
return ['', ''];
|
|
7775
|
+
let parallelPath1Data = '';
|
|
7776
|
+
let parallelPath2Data = '';
|
|
7777
|
+
// 对每个线段分别计算平行线
|
|
7778
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
7779
|
+
const startPoint = points[i];
|
|
7780
|
+
const endPoint = points[i + 1];
|
|
7781
|
+
// 计算线段的方向向量
|
|
7782
|
+
const dx = endPoint[0] - startPoint[0];
|
|
7783
|
+
const dy = endPoint[1] - startPoint[1];
|
|
7784
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
7785
|
+
if (length === 0)
|
|
7786
|
+
continue; // 跳过零长度线段
|
|
7787
|
+
// 标准化方向向量
|
|
7788
|
+
const unitX = dx / length;
|
|
7789
|
+
const unitY = dy / length;
|
|
7790
|
+
// 计算垂直向量
|
|
7791
|
+
const perpendicularX = -unitY;
|
|
7792
|
+
const perpendicularY = unitX;
|
|
7793
|
+
// 计算平行线的起点和终点
|
|
7794
|
+
const start1X = startPoint[0] + perpendicularX * offsetDistance;
|
|
7795
|
+
const start1Y = startPoint[1] + perpendicularY * offsetDistance;
|
|
7796
|
+
const end1X = endPoint[0] + perpendicularX * offsetDistance;
|
|
7797
|
+
const end1Y = endPoint[1] + perpendicularY * offsetDistance;
|
|
7798
|
+
const start2X = startPoint[0] - perpendicularX * offsetDistance;
|
|
7799
|
+
const start2Y = startPoint[1] - perpendicularY * offsetDistance;
|
|
7800
|
+
const end2X = endPoint[0] - perpendicularX * offsetDistance;
|
|
7801
|
+
const end2Y = endPoint[1] - perpendicularY * offsetDistance;
|
|
7802
|
+
// 构建路径数据
|
|
7803
|
+
if (i === 0) {
|
|
7804
|
+
parallelPath1Data = `M ${start1X} ${start1Y}`;
|
|
7805
|
+
parallelPath2Data = `M ${start2X} ${start2Y}`;
|
|
7806
|
+
}
|
|
7807
|
+
else {
|
|
7808
|
+
parallelPath1Data += ` M ${start1X} ${start1Y}`;
|
|
7809
|
+
parallelPath2Data += ` M ${start2X} ${start2Y}`;
|
|
7810
|
+
}
|
|
7811
|
+
parallelPath1Data += ` L ${end1X} ${end1Y}`;
|
|
7812
|
+
parallelPath2Data += ` L ${end2X} ${end2Y}`;
|
|
7813
|
+
}
|
|
7814
|
+
return [parallelPath2Data, parallelPath1Data];
|
|
7750
7815
|
}
|
|
7751
7816
|
}
|
|
7817
|
+
|
|
7818
|
+
var chargingPileImage = "data:image/png;base64,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";
|
|
7819
|
+
|
|
7752
7820
|
/**
|
|
7753
|
-
*
|
|
7754
|
-
*
|
|
7755
|
-
* @param type 单位类型 'metric' | 'imperial'
|
|
7756
|
-
* @returns {{ originNum: number; numStr?: string, value: string, unit: UnitsAreaType }} 返回格式化后的面积值和单位
|
|
7757
|
-
* @example
|
|
7758
|
-
* convertMowingArea(500, 'metric') // 返回 { value: "500", unit: "m²" }
|
|
7759
|
-
* convertMowingArea(1500, 'imperial') // 返回 { value: "16.1K", unit: "ft²" }
|
|
7760
|
-
* convertMowingArea(10000, 'imperial') // 返回 { value: "2.5", unit: "ac" }
|
|
7821
|
+
* 充电桩图层
|
|
7822
|
+
* 专门处理充电桩元素的渲染
|
|
7761
7823
|
*/
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
value: `0 ${type === UnitsType.Metric
|
|
7768
|
-
? UnitsAreaType.SQUARE_METER
|
|
7769
|
-
: UnitsAreaType.SQUARE_FOOT}`,
|
|
7770
|
-
unit: type === UnitsType.Metric
|
|
7771
|
-
? UnitsAreaType.SQUARE_METER
|
|
7772
|
-
: UnitsAreaType.SQUARE_FOOT,
|
|
7773
|
-
};
|
|
7774
|
-
if (type === UnitsType.Metric) {
|
|
7775
|
-
return {
|
|
7776
|
-
originNum: area,
|
|
7777
|
-
numStr: formatNumberWithMetricPrefix(area, false),
|
|
7778
|
-
value: `${formatNumberWithMetricPrefix(area, false)} ${UnitsAreaType.SQUARE_METER}`,
|
|
7779
|
-
unit: UnitsAreaType.SQUARE_METER,
|
|
7780
|
-
};
|
|
7824
|
+
class ChargingPileLayer extends BaseLayer {
|
|
7825
|
+
constructor() {
|
|
7826
|
+
super();
|
|
7827
|
+
this.level = 8; // 中等层级
|
|
7828
|
+
this.type = LAYER_DEFAULT_TYPE.CHARGING_PILE;
|
|
7781
7829
|
}
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
|
|
7787
|
-
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7830
|
+
/**
|
|
7831
|
+
* SVG渲染方法
|
|
7832
|
+
*/
|
|
7833
|
+
drawSVG(svgGroup) {
|
|
7834
|
+
if (!this.visible || this.elements.length === 0) {
|
|
7835
|
+
return;
|
|
7836
|
+
}
|
|
7837
|
+
// 只渲染充电桩类型的元素
|
|
7838
|
+
for (const element of this.elements) {
|
|
7839
|
+
if (element.type === 'charging_pile') {
|
|
7840
|
+
this.renderChargingPile(svgGroup, element);
|
|
7841
|
+
}
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
/**
|
|
7845
|
+
* 渲染充电桩元素
|
|
7846
|
+
*/
|
|
7847
|
+
renderChargingPile(svgGroup, element) {
|
|
7848
|
+
const { coordinates, style } = element;
|
|
7849
|
+
if (coordinates.length === 0)
|
|
7850
|
+
return;
|
|
7851
|
+
const center = coordinates[0];
|
|
7852
|
+
const size = style.radius ? style.radius * 2 : 55; // 默认55px大小
|
|
7853
|
+
const direction = element.originalData?.direction || 0;
|
|
7854
|
+
// 将弧度转换为角度
|
|
7855
|
+
const angle = (direction * 180) / Math.PI;
|
|
7856
|
+
const rotationDegree = 270 - angle; // 坐标系转换
|
|
7857
|
+
const image = document.createElementNS('http://www.w3.org/2000/svg', 'image');
|
|
7858
|
+
// 图片居中定位
|
|
7859
|
+
const x = center[0];
|
|
7860
|
+
const y = center[1];
|
|
7861
|
+
image.setAttribute('x', x.toString());
|
|
7862
|
+
image.setAttribute('y', y.toString());
|
|
7863
|
+
image.setAttribute('width', `${size}px`);
|
|
7864
|
+
image.setAttribute('height', `${size}px`);
|
|
7865
|
+
image.setAttribute('href', chargingPileImage);
|
|
7866
|
+
image.setAttribute('opacity', '0'); // 初始透明
|
|
7867
|
+
// 添加SVG原生动画,传入默认角度
|
|
7868
|
+
this.addChargingPileAnimation(image, center, rotationDegree);
|
|
7869
|
+
image.classList.add('vector-charging-pile');
|
|
7870
|
+
svgGroup.appendChild(image);
|
|
7871
|
+
}
|
|
7872
|
+
/**
|
|
7873
|
+
* 添加充电桩SVG原生动画
|
|
7874
|
+
*/
|
|
7875
|
+
addChargingPileAnimation(image, center, defaultAngle) {
|
|
7876
|
+
// 透明度动画 - 先显示出来
|
|
7877
|
+
const animateOpacity = document.createElementNS('http://www.w3.org/2000/svg', 'animate');
|
|
7878
|
+
animateOpacity.setAttribute('attributeName', 'opacity');
|
|
7879
|
+
animateOpacity.setAttribute('values', '0;1');
|
|
7880
|
+
animateOpacity.setAttribute('dur', '0.5s');
|
|
7881
|
+
animateOpacity.setAttribute('fill', 'freeze');
|
|
7882
|
+
image.appendChild(animateOpacity);
|
|
7883
|
+
// 旋转动画 - 从180度旋转到默认角度
|
|
7884
|
+
const animateTransform = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
|
|
7885
|
+
animateTransform.setAttribute('attributeName', 'transform');
|
|
7886
|
+
animateTransform.setAttribute('type', 'rotate');
|
|
7887
|
+
animateTransform.setAttribute('values', `${180 + defaultAngle} ${center[0]} ${center[1]};${defaultAngle} ${center[0]} ${center[1]}`);
|
|
7888
|
+
animateTransform.setAttribute('dur', '1s');
|
|
7889
|
+
animateTransform.setAttribute('repeatCount', '1'); // 只播放一次
|
|
7890
|
+
animateTransform.setAttribute('begin', '0.5s'); // 延迟0.5秒开始,等透明度动画完成
|
|
7891
|
+
animateTransform.setAttribute('fill', 'freeze'); // 保持最终状态
|
|
7892
|
+
image.appendChild(animateTransform);
|
|
7791
7893
|
}
|
|
7792
|
-
const acres = ft2ToAcre(squareFeet);
|
|
7793
|
-
// 将 ft² 转换为 ac(1 ac = 43560 ft²)
|
|
7794
|
-
const result = formatNumberWithMetricPrefix(acres, false);
|
|
7795
|
-
return {
|
|
7796
|
-
originNum: acres,
|
|
7797
|
-
numStr: result,
|
|
7798
|
-
value: `${acres} ${UnitsAreaType.ACRE}`,
|
|
7799
|
-
unit: UnitsAreaType.ACRE,
|
|
7800
|
-
};
|
|
7801
7894
|
}
|
|
7802
7895
|
|
|
7803
7896
|
/**
|
|
7804
|
-
*
|
|
7805
|
-
*
|
|
7806
|
-
*/
|
|
7807
|
-
/**
|
|
7808
|
-
* 获取一周的开始日期(周日)
|
|
7809
|
-
* @param date 目标日期
|
|
7810
|
-
* @returns 该周的周日日期
|
|
7811
|
-
*/
|
|
7812
|
-
function getWeekStart(date) {
|
|
7813
|
-
const startOfWeek = new Date(date);
|
|
7814
|
-
const dayOfWeek = startOfWeek.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
|
|
7815
|
-
// 向前推移到周日
|
|
7816
|
-
startOfWeek.setDate(startOfWeek.getDate() - dayOfWeek);
|
|
7817
|
-
startOfWeek.setHours(0, 0, 0, 0);
|
|
7818
|
-
return startOfWeek;
|
|
7819
|
-
}
|
|
7820
|
-
/**
|
|
7821
|
-
* 获取一周的结束日期(周六)
|
|
7822
|
-
* @param date 目标日期
|
|
7823
|
-
* @returns 该周的周六日期
|
|
7824
|
-
*/
|
|
7825
|
-
function getWeekEnd(date) {
|
|
7826
|
-
const endOfWeek = new Date(date);
|
|
7827
|
-
const dayOfWeek = endOfWeek.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
|
|
7828
|
-
// 向后推移到周六
|
|
7829
|
-
endOfWeek.setDate(endOfWeek.getDate() + (6 - dayOfWeek));
|
|
7830
|
-
endOfWeek.setHours(23, 59, 59, 999);
|
|
7831
|
-
return endOfWeek;
|
|
7832
|
-
}
|
|
7833
|
-
/**
|
|
7834
|
-
* 判断两个日期是否为同一天
|
|
7835
|
-
* @param date1 第一个日期
|
|
7836
|
-
* @param date2 第二个日期
|
|
7837
|
-
* @returns 是否为同一天
|
|
7838
|
-
*/
|
|
7839
|
-
function isSameDay(date1, date2) {
|
|
7840
|
-
return date1.getFullYear() === date2.getFullYear() &&
|
|
7841
|
-
date1.getMonth() === date2.getMonth() &&
|
|
7842
|
-
date1.getDate() === date2.getDate();
|
|
7843
|
-
}
|
|
7844
|
-
/**
|
|
7845
|
-
* 判断是否为今天
|
|
7846
|
-
* @param date 目标日期
|
|
7847
|
-
* @returns 是否为今天
|
|
7848
|
-
*/
|
|
7849
|
-
function isToday(date) {
|
|
7850
|
-
const today = new Date();
|
|
7851
|
-
return isSameDay(date, today);
|
|
7852
|
-
}
|
|
7853
|
-
/**
|
|
7854
|
-
* 判断是否为昨天
|
|
7855
|
-
* @param date 目标日期
|
|
7856
|
-
* @returns 是否为昨天
|
|
7857
|
-
*/
|
|
7858
|
-
function isYesterday(date) {
|
|
7859
|
-
const yesterday = new Date();
|
|
7860
|
-
yesterday.setDate(yesterday.getDate() - 1);
|
|
7861
|
-
return isSameDay(date, yesterday);
|
|
7862
|
-
}
|
|
7863
|
-
/**
|
|
7864
|
-
* 判断是否为本周内(以周日为一周的开始)
|
|
7865
|
-
* @param date 目标日期
|
|
7866
|
-
* @returns 是否为本周内
|
|
7867
|
-
*/
|
|
7868
|
-
function isThisWeek(date) {
|
|
7869
|
-
const now = new Date();
|
|
7870
|
-
const weekStart = getWeekStart(now);
|
|
7871
|
-
const weekEnd = getWeekEnd(now);
|
|
7872
|
-
return date >= weekStart && date <= weekEnd;
|
|
7873
|
-
}
|
|
7874
|
-
/**
|
|
7875
|
-
* 格式化时间为 HH:mm 格式
|
|
7876
|
-
* @param date 目标日期
|
|
7877
|
-
* @returns 格式化后的时间字符串
|
|
7897
|
+
* 点图层
|
|
7898
|
+
* 专门处理点元素的渲染
|
|
7878
7899
|
*/
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7900
|
+
class PointLayer extends BaseLayer {
|
|
7901
|
+
constructor() {
|
|
7902
|
+
super();
|
|
7903
|
+
this.level = 11;
|
|
7904
|
+
this.type = LAYER_DEFAULT_TYPE.POINT;
|
|
7905
|
+
}
|
|
7906
|
+
/**
|
|
7907
|
+
* SVG渲染方法
|
|
7908
|
+
*/
|
|
7909
|
+
drawSVG(svgGroup) {
|
|
7910
|
+
if (!this.visible || this.elements.length === 0) {
|
|
7911
|
+
return;
|
|
7912
|
+
}
|
|
7913
|
+
// 只渲染点类型的元素
|
|
7914
|
+
for (const element of this.elements) {
|
|
7915
|
+
if (element.type === 'point') {
|
|
7916
|
+
this.renderPoint(svgGroup, element);
|
|
7917
|
+
}
|
|
7918
|
+
}
|
|
7919
|
+
}
|
|
7920
|
+
/**
|
|
7921
|
+
* 渲染点元素
|
|
7922
|
+
*/
|
|
7923
|
+
renderPoint(svgGroup, element) {
|
|
7924
|
+
const { coordinates, style } = element;
|
|
7925
|
+
if (coordinates.length === 0)
|
|
7926
|
+
return;
|
|
7927
|
+
const coordinate = coordinates[0];
|
|
7928
|
+
const radius = style.radius || 4;
|
|
7929
|
+
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
7930
|
+
circle.setAttribute('cx', coordinate[0].toString());
|
|
7931
|
+
circle.setAttribute('cy', coordinate[1].toString());
|
|
7932
|
+
circle.setAttribute('r', radius.toString());
|
|
7933
|
+
circle.setAttribute('fill', style.fillColor || style.strokeColor || '#000000');
|
|
7934
|
+
circle.setAttribute('stroke', style.strokeColor || '#000000');
|
|
7935
|
+
// 确保最小线条宽度
|
|
7936
|
+
const lineWidth = Math.max(style.lineWidth || 1, 0.5);
|
|
7937
|
+
circle.setAttribute('stroke-width', lineWidth.toString());
|
|
7938
|
+
circle.setAttribute('opacity', (style.opacity || 1).toString());
|
|
7939
|
+
circle.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
7940
|
+
circle.classList.add('vector-point');
|
|
7941
|
+
svgGroup.appendChild(circle);
|
|
7942
|
+
}
|
|
7883
7943
|
}
|
|
7944
|
+
|
|
7884
7945
|
/**
|
|
7885
|
-
*
|
|
7886
|
-
*
|
|
7887
|
-
* @returns 星期几的英文缩写
|
|
7946
|
+
* SVG元素图层
|
|
7947
|
+
* 专门处理SVG元素的渲染
|
|
7888
7948
|
*/
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7949
|
+
class SvgElementLayer extends BaseLayer {
|
|
7950
|
+
constructor() {
|
|
7951
|
+
super();
|
|
7952
|
+
this.level = 6;
|
|
7953
|
+
this.scale = 1;
|
|
7954
|
+
this.type = LAYER_DEFAULT_TYPE.SVG;
|
|
7955
|
+
}
|
|
7956
|
+
/**
|
|
7957
|
+
* SVG渲染方法
|
|
7958
|
+
*/
|
|
7959
|
+
drawSVG(svgGroup) {
|
|
7960
|
+
if (!this.visible || this.elements.length === 0) {
|
|
7961
|
+
return;
|
|
7962
|
+
}
|
|
7963
|
+
// 只渲染SVG类型的元素
|
|
7964
|
+
for (const element of this.elements) {
|
|
7965
|
+
const expirationTs = element.originalData?.expiration_ts;
|
|
7966
|
+
const current = Date.now() / 1000;
|
|
7967
|
+
if (expirationTs && current > expirationTs) {
|
|
7968
|
+
continue;
|
|
7969
|
+
}
|
|
7970
|
+
if (element.type === 'svg') {
|
|
7971
|
+
this.renderSvgElement(svgGroup, element);
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
}
|
|
7975
|
+
/**
|
|
7976
|
+
* 渲染SVG元素
|
|
7977
|
+
*/
|
|
7978
|
+
renderSvgElement(svgGroup, element) {
|
|
7979
|
+
const { coordinates, style, metadata } = element;
|
|
7980
|
+
if (coordinates.length === 0)
|
|
7981
|
+
return;
|
|
7982
|
+
const center = coordinates[0];
|
|
7983
|
+
if (!metadata || !metadata.svg) {
|
|
7984
|
+
this.renderSvgPlaceholder(svgGroup, center, metadata, style);
|
|
7985
|
+
return;
|
|
7986
|
+
}
|
|
7987
|
+
try {
|
|
7988
|
+
// 解析SVG字符串
|
|
7989
|
+
const svgString = metadata.svg.replace(/\\n/g, '\n').replace(/\\"/g, '"');
|
|
7990
|
+
const parser = new DOMParser();
|
|
7991
|
+
const svgDoc = parser.parseFromString(svgString, 'image/svg+xml');
|
|
7992
|
+
const svgElement = svgDoc.documentElement;
|
|
7993
|
+
if (svgElement.tagName === 'svg') {
|
|
7994
|
+
// 获取原始SVG尺寸
|
|
7995
|
+
const originalWidth = parseFloat(svgElement.getAttribute('width') || '139');
|
|
7996
|
+
const originalHeight = parseFloat(svgElement.getAttribute('height') || '138');
|
|
7997
|
+
// 计算变换参数
|
|
7998
|
+
const userScale = metadata.scale || 1;
|
|
7999
|
+
const direction = metadata.direction || 0;
|
|
8000
|
+
// 设置原始SVG的实际尺寸(限制大小)
|
|
8001
|
+
svgElement.setAttribute('width', originalWidth.toString());
|
|
8002
|
+
svgElement.setAttribute('height', originalHeight.toString());
|
|
8003
|
+
// 创建变换组
|
|
8004
|
+
const transformGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
|
|
8005
|
+
// 在transformGroup上应用变换:平移到中心,旋转,缩放,然后居中SVG
|
|
8006
|
+
const transform = [
|
|
8007
|
+
`translate(${center[0]}, ${center[1]})`,
|
|
8008
|
+
`rotate(${-(direction * 180) / Math.PI})`,
|
|
8009
|
+
`scale(${userScale})`,
|
|
8010
|
+
`translate(${-originalWidth / 2}, ${-originalHeight / 2})`,
|
|
8011
|
+
].join(' ');
|
|
8012
|
+
transformGroup.setAttribute('transform', transform);
|
|
8013
|
+
// 设置样式
|
|
8014
|
+
if (style.opacity !== undefined) {
|
|
8015
|
+
transformGroup.setAttribute('opacity', style.opacity.toString());
|
|
8016
|
+
}
|
|
8017
|
+
// 将限制好尺寸的原始SVG添加到transformGroup中
|
|
8018
|
+
transformGroup.appendChild(svgElement);
|
|
8019
|
+
// 将transformGroup添加到svgGroup中
|
|
8020
|
+
svgGroup.appendChild(transformGroup);
|
|
8021
|
+
}
|
|
8022
|
+
else {
|
|
8023
|
+
this.renderSvgPlaceholder(svgGroup, center, metadata, style);
|
|
8024
|
+
}
|
|
8025
|
+
}
|
|
8026
|
+
catch (error) {
|
|
8027
|
+
console.warn('Failed to parse SVG:', error);
|
|
8028
|
+
this.renderSvgPlaceholder(svgGroup, center, metadata, style);
|
|
8029
|
+
}
|
|
8030
|
+
}
|
|
8031
|
+
/**
|
|
8032
|
+
* 渲染SVG占位符
|
|
8033
|
+
*/
|
|
8034
|
+
renderSvgPlaceholder(svgGroup, center, metadata, style) {
|
|
8035
|
+
const size = (metadata?.scale || 1) * 20;
|
|
8036
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
8037
|
+
rect.setAttribute('x', ((center[0] - size / 2) / 50).toString());
|
|
8038
|
+
rect.setAttribute('y', ((center[1] - size / 2) / 50).toString());
|
|
8039
|
+
rect.setAttribute('width', size.toString());
|
|
8040
|
+
rect.setAttribute('height', size.toString());
|
|
8041
|
+
rect.setAttribute('fill', style.fillColor);
|
|
8042
|
+
rect.setAttribute('stroke', style.lineColor);
|
|
8043
|
+
rect.setAttribute('stroke-width', style.lineWidth.toString());
|
|
8044
|
+
rect.setAttribute('opacity', style.opacity.toString());
|
|
8045
|
+
svgGroup.appendChild(rect);
|
|
8046
|
+
}
|
|
7892
8047
|
}
|
|
8048
|
+
|
|
7893
8049
|
/**
|
|
7894
|
-
*
|
|
7895
|
-
*
|
|
7896
|
-
* - 今天:Today HH:mm
|
|
7897
|
-
* - 昨天:Yesterday HH:mm
|
|
7898
|
-
* - 本周内:Tue HH:mm
|
|
7899
|
-
* - 其他:MM/dd/yyyy HH:mm
|
|
7900
|
-
*
|
|
7901
|
-
* @param timestamp 时间戳(秒)
|
|
7902
|
-
* @returns 格式化后的日期文本
|
|
8050
|
+
* 边界图层
|
|
8051
|
+
* 专门处理边界元素的渲染
|
|
7903
8052
|
*/
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
const timeStr = formatTime(date);
|
|
7910
|
-
// 判断是否为今天
|
|
7911
|
-
if (isToday(date)) {
|
|
7912
|
-
return `Today ${timeStr}`;
|
|
8053
|
+
class VisionOffLayer extends BaseLayer {
|
|
8054
|
+
constructor() {
|
|
8055
|
+
super();
|
|
8056
|
+
this.type = LAYER_DEFAULT_TYPE.VISION_OFF_AREA;
|
|
8057
|
+
this.level = 7; // 中等层级
|
|
7913
8058
|
}
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
8059
|
+
/**
|
|
8060
|
+
* SVG渲染方法
|
|
8061
|
+
*/
|
|
8062
|
+
drawSVG(svgGroup) {
|
|
8063
|
+
if (!this.visible || this.elements.length === 0) {
|
|
8064
|
+
return;
|
|
8065
|
+
}
|
|
8066
|
+
// 只渲染边界类型的元素
|
|
8067
|
+
for (const element of this.elements) {
|
|
8068
|
+
if (element.type === 'vision_off_area') {
|
|
8069
|
+
this.renderVisionOffArea(svgGroup, element);
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
7917
8072
|
}
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
8073
|
+
/**
|
|
8074
|
+
* 渲染边界元素
|
|
8075
|
+
*/
|
|
8076
|
+
renderVisionOffArea(svgGroup, element) {
|
|
8077
|
+
const { coordinates, style } = element;
|
|
8078
|
+
if (coordinates.length < 3)
|
|
8079
|
+
return;
|
|
8080
|
+
const polygon = document.createElementNS('http://www.w3.org/2000/svg', 'polygon');
|
|
8081
|
+
// 构建点集合,使用整数坐标
|
|
8082
|
+
const points = coordinates.map((coord) => `${coord[0]},${coord[1]}`).join(' ');
|
|
8083
|
+
const fillColor = style.fillColor || 'rgba(0, 255, 0, 0.4)';
|
|
8084
|
+
polygon.setAttribute('points', points);
|
|
8085
|
+
polygon.setAttribute('fill', fillColor);
|
|
8086
|
+
polygon.setAttribute('stroke', style.lineColor);
|
|
8087
|
+
// 确保最小线条宽度
|
|
8088
|
+
const lineWidth = Math.max(style.lineWidth || 2, 0.5);
|
|
8089
|
+
polygon.setAttribute('stroke-width', lineWidth.toString());
|
|
8090
|
+
polygon.setAttribute('stroke-linecap', 'round');
|
|
8091
|
+
polygon.setAttribute('stroke-linejoin', 'round');
|
|
8092
|
+
polygon.setAttribute('opacity', (style.opacity || 1).toString());
|
|
8093
|
+
polygon.setAttribute('vector-effect', 'non-scaling-stroke');
|
|
8094
|
+
polygon.classList.add('vector-boundary');
|
|
8095
|
+
svgGroup.appendChild(polygon);
|
|
7922
8096
|
}
|
|
7923
|
-
// 其他情况显示完整日期
|
|
7924
|
-
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
7925
|
-
const day = String(date.getDate()).padStart(2, '0');
|
|
7926
|
-
const year = date.getFullYear();
|
|
7927
|
-
return `${month}/${day}/${year} ${timeStr}`;
|
|
7928
8097
|
}
|
|
7929
8098
|
|
|
7930
8099
|
/**
|
|
@@ -9174,7 +9343,12 @@ class BoundaryLabelsManager {
|
|
|
9174
9343
|
labelDiv.style.whiteSpace = 'nowrap';
|
|
9175
9344
|
labelDiv.style.maxWidth = '220px';
|
|
9176
9345
|
labelDiv.style.transform = `translate(-50%, -50%) rotate(${-this.rotation}deg)`;
|
|
9177
|
-
|
|
9346
|
+
if (this.onlyRead) {
|
|
9347
|
+
labelDiv.style.pointerEvents = 'none';
|
|
9348
|
+
}
|
|
9349
|
+
else {
|
|
9350
|
+
labelDiv.style.pointerEvents = 'auto';
|
|
9351
|
+
}
|
|
9178
9352
|
labelDiv.style.boxShadow = '0 2px 8px rgba(0,0,0,0.4)';
|
|
9179
9353
|
labelDiv.style.cursor = 'pointer';
|
|
9180
9354
|
labelDiv.style.transition = 'background-color 0.2s ease';
|
|
@@ -9260,6 +9434,9 @@ class BoundaryLabelsManager {
|
|
|
9260
9434
|
* 展开标签
|
|
9261
9435
|
*/
|
|
9262
9436
|
expandLabel(boundaryId) {
|
|
9437
|
+
if (this.onlyRead) {
|
|
9438
|
+
return;
|
|
9439
|
+
}
|
|
9263
9440
|
const labelDiv = this.getLabelElement(boundaryId);
|
|
9264
9441
|
if (!labelDiv)
|
|
9265
9442
|
return;
|
|
@@ -9268,12 +9445,6 @@ class BoundaryLabelsManager {
|
|
|
9268
9445
|
return;
|
|
9269
9446
|
// 关闭其他展开的标签
|
|
9270
9447
|
this.collapseOtherLabels(boundaryId);
|
|
9271
|
-
if (this.onlyRead) {
|
|
9272
|
-
extendedContent.style.pointerEvents = 'none';
|
|
9273
|
-
return;
|
|
9274
|
-
}
|
|
9275
|
-
// 展开当前标签
|
|
9276
|
-
extendedContent.style.pointerEvents = 'auto';
|
|
9277
9448
|
extendedContent.style.display = 'block';
|
|
9278
9449
|
this.currentExpandedBoundaryId = boundaryId;
|
|
9279
9450
|
}
|
|
@@ -9473,6 +9644,16 @@ class BoundaryLabelsManager {
|
|
|
9473
9644
|
const pixelY = relativeY * divHeight;
|
|
9474
9645
|
return { x: pixelX, y: pixelY };
|
|
9475
9646
|
}
|
|
9647
|
+
updateReadOnlyMode(onlyRead) {
|
|
9648
|
+
this.onlyRead = onlyRead;
|
|
9649
|
+
if (!this.container)
|
|
9650
|
+
return;
|
|
9651
|
+
const allLabels = this.container.querySelectorAll('.boundary-label');
|
|
9652
|
+
allLabels.forEach((label) => {
|
|
9653
|
+
const labelElement = label;
|
|
9654
|
+
labelElement.style.pointerEvents = onlyRead ? 'none' : 'auto';
|
|
9655
|
+
});
|
|
9656
|
+
}
|
|
9476
9657
|
/**
|
|
9477
9658
|
* 更新边界数据
|
|
9478
9659
|
*/
|
|
@@ -9867,7 +10048,12 @@ class AntennaManager {
|
|
|
9867
10048
|
antennaContainer.className = 'antenna-container-item';
|
|
9868
10049
|
antennaContainer.style.position = 'absolute';
|
|
9869
10050
|
antennaContainer.style.transform = `translate(-50%, -50%) rotate(${-this.rotation}deg)`;
|
|
9870
|
-
|
|
10051
|
+
if (this.onlyRead) {
|
|
10052
|
+
antennaContainer.style.pointerEvents = 'none';
|
|
10053
|
+
}
|
|
10054
|
+
else {
|
|
10055
|
+
antennaContainer.style.pointerEvents = 'auto';
|
|
10056
|
+
}
|
|
9871
10057
|
antennaContainer.style.zIndex = AntennaManager.Z_INDEX.DEFAULT.toString();
|
|
9872
10058
|
antennaContainer.setAttribute('data-antenna-id', antennaData.type.toString());
|
|
9873
10059
|
// 创建天线图标
|
|
@@ -9895,9 +10081,6 @@ class AntennaManager {
|
|
|
9895
10081
|
// 添加点击事件
|
|
9896
10082
|
antennaDiv.addEventListener('click', (e) => {
|
|
9897
10083
|
e.stopPropagation();
|
|
9898
|
-
if (this.onlyRead) {
|
|
9899
|
-
return;
|
|
9900
|
-
}
|
|
9901
10084
|
// 关闭其他展开的tooltip
|
|
9902
10085
|
this.collapseOtherTooltips(antennaContainer);
|
|
9903
10086
|
this.elevateAntennaZIndex();
|
|
@@ -9913,15 +10096,9 @@ class AntennaManager {
|
|
|
9913
10096
|
});
|
|
9914
10097
|
// 添加悬停效果
|
|
9915
10098
|
antennaDiv.addEventListener('mouseenter', () => {
|
|
9916
|
-
if (this.onlyRead) {
|
|
9917
|
-
return;
|
|
9918
|
-
}
|
|
9919
10099
|
antennaDiv.style.transform = 'scale(1.1)';
|
|
9920
10100
|
});
|
|
9921
10101
|
antennaDiv.addEventListener('mouseleave', () => {
|
|
9922
|
-
if (this.onlyRead) {
|
|
9923
|
-
return;
|
|
9924
|
-
}
|
|
9925
10102
|
antennaDiv.style.transform = 'scale(1)';
|
|
9926
10103
|
});
|
|
9927
10104
|
antennaContainer.appendChild(antennaDiv);
|
|
@@ -9974,10 +10151,8 @@ class AntennaManager {
|
|
|
9974
10151
|
if (!tooltip)
|
|
9975
10152
|
return;
|
|
9976
10153
|
if (this.onlyRead) {
|
|
9977
|
-
tooltip.style.pointerEvents = 'none';
|
|
9978
10154
|
return;
|
|
9979
10155
|
}
|
|
9980
|
-
tooltip.style.pointerEvents = 'auto';
|
|
9981
10156
|
// 展开当前tooltip
|
|
9982
10157
|
tooltip.style.display = 'block';
|
|
9983
10158
|
const extendedContent = tooltip.querySelector('.antenna-tooltip-extended');
|
|
@@ -10023,6 +10198,16 @@ class AntennaManager {
|
|
|
10023
10198
|
}
|
|
10024
10199
|
});
|
|
10025
10200
|
}
|
|
10201
|
+
updateReadOnlyMode(onlyRead) {
|
|
10202
|
+
this.onlyRead = onlyRead;
|
|
10203
|
+
if (!this.container)
|
|
10204
|
+
return;
|
|
10205
|
+
const allAntennas = this.container.querySelectorAll('.antenna-container-item');
|
|
10206
|
+
allAntennas.forEach((antenna) => {
|
|
10207
|
+
const antennaElement = antenna;
|
|
10208
|
+
antennaElement.style.pointerEvents = onlyRead ? 'none' : 'auto';
|
|
10209
|
+
});
|
|
10210
|
+
}
|
|
10026
10211
|
/**
|
|
10027
10212
|
* 添加主天线
|
|
10028
10213
|
*/
|
|
@@ -10763,6 +10948,7 @@ class MowerMapOverlay {
|
|
|
10763
10948
|
this.overlayView.hide = this.hide.bind(this);
|
|
10764
10949
|
// 添加编辑模式相关方法
|
|
10765
10950
|
this.overlayView.setEditMode = this.setEditMode.bind(this);
|
|
10951
|
+
this.overlayView.setReadOnlyMode = this.setReadOnlyMode.bind(this);
|
|
10766
10952
|
this.overlayView.getEditData = this.getEditData.bind(this);
|
|
10767
10953
|
this.overlayView.handleSave = this.handleSave.bind(this);
|
|
10768
10954
|
this.overlayView.setCustomIcons = this.setCustomIcons.bind(this);
|
|
@@ -10984,6 +11170,15 @@ class MowerMapOverlay {
|
|
|
10984
11170
|
this.chargingPileManager.updatePositions();
|
|
10985
11171
|
}
|
|
10986
11172
|
}
|
|
11173
|
+
// 设置编辑模式
|
|
11174
|
+
setReadOnlyMode(enabled) {
|
|
11175
|
+
if (this.boundaryLabelsManager) {
|
|
11176
|
+
this.boundaryLabelsManager.updateReadOnlyMode(enabled);
|
|
11177
|
+
}
|
|
11178
|
+
if (this.antennaManager) {
|
|
11179
|
+
this.antennaManager.updateReadOnlyMode(enabled);
|
|
11180
|
+
}
|
|
11181
|
+
}
|
|
10987
11182
|
// 创建编辑界面
|
|
10988
11183
|
createEditInterface() {
|
|
10989
11184
|
if (!this.div)
|
|
@@ -12170,6 +12365,12 @@ sn, edger = false, unitType = UnitsType.Imperial, language = 'en', onlyRead = fa
|
|
|
12170
12365
|
overlayRef.current.setEditMode(isEditMode);
|
|
12171
12366
|
}
|
|
12172
12367
|
}, [isEditMode]);
|
|
12368
|
+
// 监听只读模式变化
|
|
12369
|
+
React.useEffect(() => {
|
|
12370
|
+
if (overlayRef.current) {
|
|
12371
|
+
overlayRef.current.setReadOnlyMode(onlyRead);
|
|
12372
|
+
}
|
|
12373
|
+
}, [onlyRead]);
|
|
12173
12374
|
// 监听路径信息的更新,需要同步更新boundaary的进度信息
|
|
12174
12375
|
// useEffect(() => {
|
|
12175
12376
|
// if (!mapJson) return;
|