cesium-xs-sdk 1.0.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/README.md +104 -0
- package/dist/cesium-xs-sdk.cjs.js +7387 -0
- package/dist/cesium-xs-sdk.cjs.js.map +1 -0
- package/dist/cesium-xs-sdk.esm.js +7383 -0
- package/dist/cesium-xs-sdk.esm.js.map +1 -0
- package/dist/cesium-xs-sdk.umd.js +2 -0
- package/dist/cesium-xs-sdk.umd.js.map +1 -0
- package/package.json +78 -0
- package/src/core/Config.js +176 -0
- package/src/core/ModuleManager.js +157 -0
- package/src/index.d.ts +358 -0
- package/src/index.js +59 -0
- package/src/modules/EditorModule.js +370 -0
- package/src/modules/EventModule.js +211 -0
- package/src/modules/GraphicModule.js +1037 -0
- package/src/modules/PlotGraphicType.js +43 -0
- package/src/modules/SceneModule.js +600 -0
- package/src/modules/tools/BaseTool.js +155 -0
- package/src/modules/tools/DeleteTool.js +252 -0
- package/src/modules/tools/DrawTool.js +961 -0
- package/src/modules/tools/EditTool.js +1585 -0
- package/src/modules/tools/SelectTool.js +526 -0
- package/src/react.d.ts +18 -0
- package/src/react.js +49 -0
- package/src/utils/CoordinateUtil.js +63 -0
- package/src/utils/GeoCalcUtil.js +95 -0
- package/src/utils/PlotGeometryUtil.js +1134 -0
- package/src/vue.d.ts +18 -0
- package/src/vue.js +76 -0
- package/src/vue2.d.ts +20 -0
- package/src/vue2.js +75 -0
|
@@ -0,0 +1,1585 @@
|
|
|
1
|
+
import { BaseTool } from './BaseTool.js';
|
|
2
|
+
import { PlotGraphicType } from '../PlotGraphicType.js';
|
|
3
|
+
import { PlotGeometryUtil } from '../../utils/PlotGeometryUtil.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 编辑模式枚举
|
|
7
|
+
*/
|
|
8
|
+
export const EditMode = {
|
|
9
|
+
VERTEX: 'vertex' // 顶点编辑模式(同时支持平移)
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 编辑工具
|
|
14
|
+
* 支持选中图元的顶点编辑、平移、缩放
|
|
15
|
+
*/
|
|
16
|
+
export class EditTool extends BaseTool {
|
|
17
|
+
// 静态常量
|
|
18
|
+
static CLOSE_POINT_TOLERANCE = 0.001; // 闭合点检测容差(1毫米)
|
|
19
|
+
static DEFAULT_PIXEL_SIZE = 12;
|
|
20
|
+
static DEFAULT_OUTLINE_WIDTH = 2;
|
|
21
|
+
static MOVE_THROTTLE_MS = 16; // 拖拽节流间隔(~60fps)
|
|
22
|
+
|
|
23
|
+
constructor(editorModule, options = {}) {
|
|
24
|
+
super(editorModule, options);
|
|
25
|
+
this._editMode = options.editMode || EditMode.VERTEX;
|
|
26
|
+
this._selectedEntity = null; // 当前选中的图元
|
|
27
|
+
this._selectedEntityId = null;
|
|
28
|
+
this._editHandles = []; // 编辑控制点
|
|
29
|
+
this._positionOutline = null; // 位置辅助线
|
|
30
|
+
this._polygonPreview = null; // 多边形预览
|
|
31
|
+
this._tempEntities = []; // 临时图元(如包围盒)
|
|
32
|
+
this._isDragging = false; // 是否正在拖拽
|
|
33
|
+
this._dragHandle = null; // 当前拖拽的控制点
|
|
34
|
+
this._originalPositions = null; // 原始位置(用于撤销)
|
|
35
|
+
this._handleVertexIndices = null; // 控制点对应的实际顶点索引
|
|
36
|
+
this._rectangleOriginalPositions = null; // 矩形原始顶点位置
|
|
37
|
+
this._originalEnableInputs = true; // 原始相机控制状态
|
|
38
|
+
this._lastMoveTime = 0; // 上次移动时间(用于节流)
|
|
39
|
+
this._originalCircleCenter = null; // 圆形原始圆心
|
|
40
|
+
this._originalCircleRadius = null; // 圆形原始半径
|
|
41
|
+
this._originalEntityCenter = null; // 原始中心点(用于平移)
|
|
42
|
+
this._radiusLine = null; // 半径线引用
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 设置编辑模式
|
|
47
|
+
* @param {string} mode EditMode
|
|
48
|
+
*/
|
|
49
|
+
setEditMode(mode) {
|
|
50
|
+
this._editMode = mode;
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getEditMode() {
|
|
55
|
+
return this._editMode;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 手动选中图元进行编辑
|
|
60
|
+
* @param {string} entityId 图元 ID
|
|
61
|
+
*/
|
|
62
|
+
selectEntity(entityId) {
|
|
63
|
+
const entity = this._graphicModule.getGraphic(entityId);
|
|
64
|
+
if (entity) {
|
|
65
|
+
this._selectEntity(entity);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
onActivate() {
|
|
70
|
+
this.registerObserver('mouse:click', this._onClick.bind(this));
|
|
71
|
+
this.registerObserver('mouse:move', this._onMove.bind(this));
|
|
72
|
+
this.registerObserver('mouse:down', this._onMouseDown.bind(this));
|
|
73
|
+
this.registerObserver('mouse:up', this._onMouseUp.bind(this));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
onDeactivate() {
|
|
77
|
+
this._deselectEntity();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_onClick(_lngLat, _entity, movement) {
|
|
81
|
+
// movement.position 包含屏幕坐标 {x, y}
|
|
82
|
+
const screenPos = movement.position || (movement && movement.endPosition);
|
|
83
|
+
if (!screenPos) return;
|
|
84
|
+
|
|
85
|
+
const picked = this._viewer.scene.pick({ x: screenPos.x, y: screenPos.y });
|
|
86
|
+
console.log('EditTool _onClick:', { picked, screenPos });
|
|
87
|
+
|
|
88
|
+
if (Cesium.defined(picked) && Cesium.defined(picked.id)) {
|
|
89
|
+
const entityId = picked.id.id;
|
|
90
|
+
console.log('EditTool picked entity:', entityId);
|
|
91
|
+
|
|
92
|
+
// 忽略临时图元
|
|
93
|
+
if (entityId && entityId.startsWith('__temp_')) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 忽略控制点(拖拽在 _onMouseDown 中处理)
|
|
98
|
+
if (entityId && (entityId.startsWith('__edit_handle_') || entityId.includes('__edit_handle_'))) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const entity = this._graphicModule.getGraphic(entityId);
|
|
103
|
+
if (entity) {
|
|
104
|
+
console.log('EditTool selecting entity');
|
|
105
|
+
this._selectEntity(entity);
|
|
106
|
+
} else {
|
|
107
|
+
this._deselectEntity();
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
// 点击空白处取消选择
|
|
111
|
+
console.log('EditTool click on empty');
|
|
112
|
+
this._deselectEntity();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_onMouseDown(_lngLat, _entity, movement) {
|
|
117
|
+
// movement 包含屏幕坐标 {position: {x, y}}
|
|
118
|
+
const screenPos = movement && (movement.position || movement.endPosition);
|
|
119
|
+
if (!screenPos) return;
|
|
120
|
+
|
|
121
|
+
// 如果已经选中了图元,检查是否点击了控制点
|
|
122
|
+
if (this._selectedEntity && !this._isDragging) {
|
|
123
|
+
const picked = this._viewer.scene.pick({ x: screenPos.x, y: screenPos.y });
|
|
124
|
+
if (Cesium.defined(picked) && Cesium.defined(picked.id)) {
|
|
125
|
+
const entityId = picked.id.id;
|
|
126
|
+
if (entityId && (entityId.startsWith('__edit_handle_') || entityId.includes('__edit_handle_'))) {
|
|
127
|
+
// 直接在 _editHandles 中查找被点击的控制点
|
|
128
|
+
const clickedHandle = this._editHandles.find(handle => handle.id === entityId);
|
|
129
|
+
if (clickedHandle) {
|
|
130
|
+
this._dragHandle = clickedHandle;
|
|
131
|
+
this._isDragging = true;
|
|
132
|
+
this._originalPositions = this._getEntityPositions(this._selectedEntity);
|
|
133
|
+
// 禁用相机控制,防止拖拽时相机移动
|
|
134
|
+
this._viewer.scene.screenSpaceCameraController.enableRotate = false;
|
|
135
|
+
this._viewer.scene.screenSpaceCameraController.enableTranslate = false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_onMouseUp(_data) {
|
|
143
|
+
if (this._isDragging) {
|
|
144
|
+
this._isDragging = false;
|
|
145
|
+
// 恢复相机控制
|
|
146
|
+
this._viewer.scene.screenSpaceCameraController.enableRotate = true;
|
|
147
|
+
this._viewer.scene.screenSpaceCameraController.enableTranslate = true;
|
|
148
|
+
this._setCursorStyle('default');
|
|
149
|
+
this._emitEditComplete();
|
|
150
|
+
this._dragHandle = null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 设置鼠标样式
|
|
156
|
+
* @param {string} style CSS cursor 样式
|
|
157
|
+
*/
|
|
158
|
+
_setCursorStyle(style) {
|
|
159
|
+
if (this._viewer && this._viewer.canvas) {
|
|
160
|
+
this._viewer.canvas.style.cursor = style;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_onMove(data) {
|
|
165
|
+
// _onMove receives movement object from BaseTool
|
|
166
|
+
const screenPos = data.position || data.endPosition;
|
|
167
|
+
if (!screenPos) return;
|
|
168
|
+
|
|
169
|
+
// 非拖拽时做节流,避免 hover 检测过于频繁
|
|
170
|
+
if (!this._isDragging) {
|
|
171
|
+
const now = Date.now();
|
|
172
|
+
if (now - this._lastMoveTime < EditTool.MOVE_THROTTLE_MS) return;
|
|
173
|
+
this._lastMoveTime = now;
|
|
174
|
+
|
|
175
|
+
// 检测是否悬停在控制点上
|
|
176
|
+
const picked = this._viewer.scene.pick({ x: screenPos.x, y: screenPos.y });
|
|
177
|
+
const isOverHandle = Cesium.defined(picked) && Cesium.defined(picked.id) &&
|
|
178
|
+
picked.id.id && picked.id.id.startsWith('__edit_handle_');
|
|
179
|
+
this._setCursorStyle(isOverHandle ? 'move' : 'default');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this._isDragging && this._dragHandle) {
|
|
183
|
+
this._setCursorStyle('grabbing');
|
|
184
|
+
|
|
185
|
+
// 拖拽时优先使用椭球面拾取,避免拾取到控制点句柄本身导致图元跟随滞后
|
|
186
|
+
let lonLat = null;
|
|
187
|
+
const cartesian = this._viewer.camera.pickEllipsoid(
|
|
188
|
+
new Cesium.Cartesian2(screenPos.x, screenPos.y),
|
|
189
|
+
this._viewer.scene.globe.ellipsoid
|
|
190
|
+
);
|
|
191
|
+
if (cartesian) {
|
|
192
|
+
const c = Cesium.Cartographic.fromCartesian(cartesian);
|
|
193
|
+
lonLat = [
|
|
194
|
+
Cesium.Math.toDegrees(c.longitude),
|
|
195
|
+
Cesium.Math.toDegrees(c.latitude),
|
|
196
|
+
c.height || 0
|
|
197
|
+
];
|
|
198
|
+
} else {
|
|
199
|
+
lonLat = this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (lonLat) {
|
|
203
|
+
this._updateEntityPosition(lonLat);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 平移标绘图元的所有控制点
|
|
210
|
+
* @param {Cesium.Cartesian3} newAnchorCartesian 新锚点位置
|
|
211
|
+
* @param {Cesium.Cartesian3} oldAnchorCartesian 旧锚点位置
|
|
212
|
+
*/
|
|
213
|
+
_translatePlotControlPoints(newAnchorCartesian, oldAnchorCartesian) {
|
|
214
|
+
const deltaX = newAnchorCartesian.x - oldAnchorCartesian.x;
|
|
215
|
+
const deltaY = newAnchorCartesian.y - oldAnchorCartesian.y;
|
|
216
|
+
const deltaZ = newAnchorCartesian.z - oldAnchorCartesian.z;
|
|
217
|
+
|
|
218
|
+
const controlPoints = this._selectedEntity._controlPoints.map(cp => {
|
|
219
|
+
const cpCart = Cesium.Cartesian3.fromDegrees(cp[0], cp[1], cp[2] || 0);
|
|
220
|
+
const newCpCart = new Cesium.Cartesian3(
|
|
221
|
+
cpCart.x + deltaX,
|
|
222
|
+
cpCart.y + deltaY,
|
|
223
|
+
cpCart.z + deltaZ
|
|
224
|
+
);
|
|
225
|
+
const newCpCarto = Cesium.Cartographic.fromCartesian(newCpCart);
|
|
226
|
+
return [
|
|
227
|
+
Cesium.Math.toDegrees(newCpCarto.longitude),
|
|
228
|
+
Cesium.Math.toDegrees(newCpCarto.latitude),
|
|
229
|
+
newCpCarto.height || 0
|
|
230
|
+
];
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
this._selectedEntity._controlPoints = controlPoints;
|
|
234
|
+
|
|
235
|
+
// 更新包装器几何
|
|
236
|
+
const wrapper = this._graphicModule.wrapperMap.get(this._selectedEntity.id);
|
|
237
|
+
if (wrapper) {
|
|
238
|
+
wrapper.setControlPoints(controlPoints);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 更新所有控制点位置
|
|
242
|
+
this._editHandles.forEach((handle) => {
|
|
243
|
+
if (handle.id && handle.id.includes('center')) {
|
|
244
|
+
// 扇形/椭圆:中心控制点固定在圆心/中心;其他类型使用重心
|
|
245
|
+
const plotType = this._selectedEntity._plotType;
|
|
246
|
+
const center = (plotType === PlotGraphicType.SECTOR || plotType === PlotGraphicType.ELLIPSE)
|
|
247
|
+
? controlPoints[0]
|
|
248
|
+
: this._getPlotControlPointsCenter(controlPoints);
|
|
249
|
+
handle.position = new Cesium.ConstantPositionProperty(
|
|
250
|
+
this._clampPositionToGround(Cesium.Cartesian3.fromDegrees(center[0], center[1], center[2] || 0))
|
|
251
|
+
);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
// 按句柄 ID 解析真实控制点索引(扇形跳过了索引 0,不能直接使用 forEach 索引)
|
|
255
|
+
const match = handle.id.match(/__edit_handle_(\d+)$/);
|
|
256
|
+
if (match) {
|
|
257
|
+
const cpIndex = parseInt(match[1], 10);
|
|
258
|
+
const cp = controlPoints[cpIndex];
|
|
259
|
+
handle.position = new Cesium.ConstantPositionProperty(
|
|
260
|
+
this._clampPositionToGround(Cesium.Cartesian3.fromDegrees(cp[0], cp[1], cp[2] || 0))
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
this._originalPositions = this._getEntityPositions(this._selectedEntity);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_selectEntity(entity) {
|
|
269
|
+
// 先取消之前的选中
|
|
270
|
+
this._deselectEntity();
|
|
271
|
+
|
|
272
|
+
this._selectedEntity = entity;
|
|
273
|
+
this._selectedEntityId = entity.id;
|
|
274
|
+
|
|
275
|
+
// 创建编辑控制点
|
|
276
|
+
this._createEditHandles();
|
|
277
|
+
|
|
278
|
+
// 高亮选中图元
|
|
279
|
+
this._highlightEntity(entity, true);
|
|
280
|
+
|
|
281
|
+
this._emitSelect(entity.id);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
_deselectEntity() {
|
|
285
|
+
if (this._selectedEntity) {
|
|
286
|
+
this._highlightEntity(this._selectedEntity, false);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this._clearEditHandles();
|
|
290
|
+
this._selectedEntity = null;
|
|
291
|
+
this._selectedEntityId = null;
|
|
292
|
+
this._isDragging = false;
|
|
293
|
+
this._dragHandle = null;
|
|
294
|
+
// 确保取消选择时也恢复相机控制
|
|
295
|
+
this._viewer.scene.screenSpaceCameraController.enableRotate = true;
|
|
296
|
+
this._viewer.scene.screenSpaceCameraController.enableTranslate = true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
_createEditHandles() {
|
|
300
|
+
this._clearEditHandles();
|
|
301
|
+
|
|
302
|
+
// 标绘图元:显示控制点并重新生成几何
|
|
303
|
+
if (this._selectedEntity && this._selectedEntity._isPlotGraphic) {
|
|
304
|
+
this._createPlotControlHandles(this._selectedEntity);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const positions = this._getEditablePositions(this._selectedEntity);
|
|
309
|
+
if (!positions || positions.length === 0) return;
|
|
310
|
+
|
|
311
|
+
// 创建顶点编辑控制点
|
|
312
|
+
this._createVertexHandles(positions);
|
|
313
|
+
|
|
314
|
+
// 如果是多顶点图元(非圆形),创建中心控制点用于平移
|
|
315
|
+
// 圆形在 _createVertexHandles 中已经创建了中心控制点
|
|
316
|
+
if (positions.length > 1 && !this._isCircle(this._selectedEntity)) {
|
|
317
|
+
this._createCenterHandleForVertexMode(positions);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 将位置转换为贴地位置(高度设为0)
|
|
323
|
+
* @param {Cesium.Cartesian3} position
|
|
324
|
+
* @returns {Cesium.Cartesian3}
|
|
325
|
+
*/
|
|
326
|
+
_clampPositionToGround(position) {
|
|
327
|
+
if (!position) return position;
|
|
328
|
+
const cartographic = Cesium.Cartographic.fromCartesian(position);
|
|
329
|
+
return Cesium.Cartesian3.fromDegrees(
|
|
330
|
+
Cesium.Math.toDegrees(cartographic.longitude),
|
|
331
|
+
Cesium.Math.toDegrees(cartographic.latitude),
|
|
332
|
+
0
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 创建标绘图元的控制点
|
|
338
|
+
* @param {Cesium.Entity} entity 标绘图元实体
|
|
339
|
+
*/
|
|
340
|
+
_createPlotControlHandles(entity) {
|
|
341
|
+
const controlPoints = entity._controlPoints || [];
|
|
342
|
+
if (controlPoints.length === 0) return;
|
|
343
|
+
|
|
344
|
+
// 扇形:圆心作为平移控制点,不创建黄色顶点句柄
|
|
345
|
+
const plotType = entity._plotType;
|
|
346
|
+
const isSector = plotType === PlotGraphicType.SECTOR;
|
|
347
|
+
const isEllipse = plotType === PlotGraphicType.ELLIPSE;
|
|
348
|
+
|
|
349
|
+
controlPoints.forEach((cp, index) => {
|
|
350
|
+
if ((isSector || isEllipse) && index === 0) return; // 扇形/椭圆圆心/中心由中心控制点处理
|
|
351
|
+
|
|
352
|
+
const clampedPos = this._clampPositionToGround(
|
|
353
|
+
Cesium.Cartesian3.fromDegrees(cp[0], cp[1], cp[2] || 0)
|
|
354
|
+
);
|
|
355
|
+
const handle = this.createTempEntity(`__edit_handle_${index}`, {
|
|
356
|
+
position: clampedPos,
|
|
357
|
+
point: {
|
|
358
|
+
color: Cesium.Color.YELLOW,
|
|
359
|
+
pixelSize: 12,
|
|
360
|
+
outlineColor: Cesium.Color.RED,
|
|
361
|
+
outlineWidth: 2,
|
|
362
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
363
|
+
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
this._editHandles.push(handle);
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// 显示轮廓辅助线
|
|
370
|
+
if (controlPoints.length > 1) {
|
|
371
|
+
this._createPlotPositionOutline();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 椭圆:显示两条半轴辅助线
|
|
375
|
+
if (isEllipse) {
|
|
376
|
+
this._createEllipseAxisLines();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// 创建中心平移控制点
|
|
380
|
+
if (controlPoints.length > 1) {
|
|
381
|
+
if (isSector || isEllipse) {
|
|
382
|
+
// 扇形/椭圆:中心点即为中心平移控制点
|
|
383
|
+
this._createPlotCenterHandle(controlPoints, controlPoints[0]);
|
|
384
|
+
} else {
|
|
385
|
+
this._createPlotCenterHandle(controlPoints);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 创建标绘图元的中心平移控制点
|
|
392
|
+
* @param {Array} controlPoints 控制点
|
|
393
|
+
* @param {Array|null} centerPoint 指定中心点位置;为空时使用控制点重心
|
|
394
|
+
*/
|
|
395
|
+
_createPlotCenterHandle(controlPoints, centerPoint = null) {
|
|
396
|
+
const center = centerPoint || this._getPlotControlPointsCenter(controlPoints);
|
|
397
|
+
if (!center) return;
|
|
398
|
+
|
|
399
|
+
this._originalEntityCenter = this._clampPositionToGround(
|
|
400
|
+
Cesium.Cartesian3.fromDegrees(center[0], center[1], center[2] || 0)
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const handle = this.createTempEntity('__edit_handle_center_plot', {
|
|
404
|
+
position: this._originalEntityCenter,
|
|
405
|
+
point: {
|
|
406
|
+
color: Cesium.Color.CYAN,
|
|
407
|
+
pixelSize: 14,
|
|
408
|
+
outlineColor: Cesium.Color.BLUE,
|
|
409
|
+
outlineWidth: 2,
|
|
410
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
411
|
+
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
this._editHandles.push(handle);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* 计算标绘控制点的中心点
|
|
419
|
+
* @param {Array} controlPoints 控制点 [lon, lat, height][]
|
|
420
|
+
*/
|
|
421
|
+
_getPlotControlPointsCenter(controlPoints) {
|
|
422
|
+
if (!controlPoints || controlPoints.length === 0) return null;
|
|
423
|
+
|
|
424
|
+
let x = 0, y = 0, z = 0;
|
|
425
|
+
controlPoints.forEach(cp => {
|
|
426
|
+
x += cp[0];
|
|
427
|
+
y += cp[1];
|
|
428
|
+
z += cp[2] || 0;
|
|
429
|
+
});
|
|
430
|
+
return [x / controlPoints.length, y / controlPoints.length, z / controlPoints.length];
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* 创建标绘图元轮廓辅助线
|
|
435
|
+
*/
|
|
436
|
+
_createPlotPositionOutline() {
|
|
437
|
+
const self = this;
|
|
438
|
+
const entity = this._selectedEntity;
|
|
439
|
+
const isLine = Cesium.defined(entity.polyline);
|
|
440
|
+
|
|
441
|
+
// 以下标绘图元的控制点连线会干扰真实几何,不绘制轮廓辅助线
|
|
442
|
+
const skipOutlineTypes = new Set([
|
|
443
|
+
PlotGraphicType.FINE_ARROW,
|
|
444
|
+
PlotGraphicType.ASSAULT_DIRECTION,
|
|
445
|
+
PlotGraphicType.ATTACK_ARROW,
|
|
446
|
+
PlotGraphicType.SWALLOWTAIL_ATTACK_ARROW,
|
|
447
|
+
PlotGraphicType.SQUAD_COMBAT,
|
|
448
|
+
PlotGraphicType.SWALLOWTAIL_SQUAD_COMBAT,
|
|
449
|
+
PlotGraphicType.DOUBLE_ARROW,
|
|
450
|
+
PlotGraphicType.CURVED_ARROW,
|
|
451
|
+
PlotGraphicType.CURVE
|
|
452
|
+
]);
|
|
453
|
+
|
|
454
|
+
this._positionOutline = this.createTempEntity(this.generateId('temp_plot_outline'), {
|
|
455
|
+
polyline: {
|
|
456
|
+
positions: new Cesium.CallbackProperty(function () {
|
|
457
|
+
const handles = self._editHandles;
|
|
458
|
+
if (!handles || handles.length === 0) return [];
|
|
459
|
+
|
|
460
|
+
const plotType = entity._plotType;
|
|
461
|
+
if (skipOutlineTypes.has(plotType)) {
|
|
462
|
+
return [];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// 扇形/椭圆:直接沿实际几何边界绘制辅助线
|
|
466
|
+
if (plotType === PlotGraphicType.SECTOR || plotType === PlotGraphicType.ELLIPSE) {
|
|
467
|
+
const hierarchy = entity.polygon?.hierarchy;
|
|
468
|
+
let positions = null;
|
|
469
|
+
if (hierarchy) {
|
|
470
|
+
if (typeof hierarchy.getValue === 'function') {
|
|
471
|
+
const value = hierarchy.getValue(Cesium.JulianDate.now());
|
|
472
|
+
positions = value ? value.positions : null;
|
|
473
|
+
} else if (hierarchy.positions) {
|
|
474
|
+
positions = hierarchy.positions;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (positions && positions.length > 2) {
|
|
478
|
+
// 复制并显式闭合,避免 polygon 顶点重复导致 polyline 退化
|
|
479
|
+
const closed = positions.slice();
|
|
480
|
+
closed.push(positions[0]);
|
|
481
|
+
return closed;
|
|
482
|
+
}
|
|
483
|
+
return positions || [];
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const degrees = [];
|
|
487
|
+
handles.forEach(handle => {
|
|
488
|
+
// 跳过中心平移控制点
|
|
489
|
+
if (handle.id && handle.id.includes('center')) return;
|
|
490
|
+
|
|
491
|
+
const pos = handle.position.getValue(Cesium.JulianDate.now());
|
|
492
|
+
if (pos) {
|
|
493
|
+
const c = Cesium.Cartographic.fromCartesian(pos);
|
|
494
|
+
degrees.push(Cesium.Math.toDegrees(c.longitude));
|
|
495
|
+
degrees.push(Cesium.Math.toDegrees(c.latitude));
|
|
496
|
+
degrees.push(c.height || 0);
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
if (degrees.length === 0) return [];
|
|
501
|
+
|
|
502
|
+
const cartesians = Cesium.Cartesian3.fromDegreesArrayHeights(degrees);
|
|
503
|
+
if (isLine) {
|
|
504
|
+
return cartesians;
|
|
505
|
+
}
|
|
506
|
+
return [...cartesians, cartesians[0]];
|
|
507
|
+
}, false),
|
|
508
|
+
material: Cesium.Color.YELLOW,
|
|
509
|
+
width: 2,
|
|
510
|
+
clampToGround: true
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
this._positionOutline._isPreview = true;
|
|
514
|
+
this._tempEntities.push(this._positionOutline);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* 创建椭圆半轴辅助线(中心到长/短半轴端点)
|
|
519
|
+
*/
|
|
520
|
+
_createEllipseAxisLines() {
|
|
521
|
+
const self = this;
|
|
522
|
+
const axisLine = this.createTempEntity(this.generateId('temp_ellipse_axis'), {
|
|
523
|
+
polyline: {
|
|
524
|
+
positions: new Cesium.CallbackProperty(function () {
|
|
525
|
+
const entity = self._selectedEntity;
|
|
526
|
+
const cps = entity?._controlPoints;
|
|
527
|
+
if (!cps || cps.length < 3) return [];
|
|
528
|
+
|
|
529
|
+
const center = Cesium.Cartesian3.fromDegrees(cps[0][0], cps[0][1], cps[0][2] || 0);
|
|
530
|
+
const major = Cesium.Cartesian3.fromDegrees(cps[1][0], cps[1][1], cps[1][2] || 0);
|
|
531
|
+
const minor = Cesium.Cartesian3.fromDegrees(cps[2][0], cps[2][1], cps[2][2] || 0);
|
|
532
|
+
return [center, major, center, minor];
|
|
533
|
+
}, false),
|
|
534
|
+
material: Cesium.Color.YELLOW,
|
|
535
|
+
width: 2,
|
|
536
|
+
clampToGround: true
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
axisLine._isPreview = true;
|
|
540
|
+
this._tempEntities.push(axisLine);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* 创建顶点编辑控制点
|
|
545
|
+
* @param {Array} positions 可编辑位置数组
|
|
546
|
+
*/
|
|
547
|
+
_createVertexHandles(positions) {
|
|
548
|
+
// 判断是否是矩形(4个顶点形成矩形)
|
|
549
|
+
const isRectangle = Cesium.defined(this._selectedEntity.polygon) && positions.length === 4 &&
|
|
550
|
+
this._isRectangle(positions);
|
|
551
|
+
|
|
552
|
+
if (isRectangle) {
|
|
553
|
+
this._rectangleOriginalPositions = positions.slice();
|
|
554
|
+
|
|
555
|
+
const vertexIndices = [0, 1, 2, 3];
|
|
556
|
+
|
|
557
|
+
vertexIndices.forEach((vertexIdx, handleIdx) => {
|
|
558
|
+
// 修复:位置贴地
|
|
559
|
+
const clampedPos = this._clampPositionToGround(positions[vertexIdx]);
|
|
560
|
+
const handle = this.createTempEntity(`__edit_handle_${handleIdx}`, {
|
|
561
|
+
position: clampedPos,
|
|
562
|
+
point: {
|
|
563
|
+
color: Cesium.Color.YELLOW,
|
|
564
|
+
pixelSize: 12,
|
|
565
|
+
outlineColor: Cesium.Color.RED,
|
|
566
|
+
outlineWidth: 2,
|
|
567
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
568
|
+
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
this._editHandles.push(handle);
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
this._handleVertexIndices = [0, 1, 2, 3];
|
|
575
|
+
} else if (this._isCircle(this._selectedEntity)) {
|
|
576
|
+
const circleInfo = this._getCircleInfo(this._selectedEntity);
|
|
577
|
+
if (!circleInfo) return;
|
|
578
|
+
|
|
579
|
+
const { center, radius } = circleInfo;
|
|
580
|
+
// 修复:圆心位置贴地
|
|
581
|
+
const centerCartesian = Cesium.Cartesian3.fromDegrees(center[0], center[1], 0);
|
|
582
|
+
const radiusPoint = this._getPointOnCircle(center, radius, 0);
|
|
583
|
+
const radiusCartesian = Cesium.Cartesian3.fromDegrees(radiusPoint[0], radiusPoint[1], 0);
|
|
584
|
+
|
|
585
|
+
// 圆心控制点(可平移)- 使用与其他类型不同的ID格式
|
|
586
|
+
const centerHandle = this.createTempEntity('__edit_handle_center', {
|
|
587
|
+
position: centerCartesian,
|
|
588
|
+
point: {
|
|
589
|
+
color: Cesium.Color.CYAN,
|
|
590
|
+
pixelSize: 14,
|
|
591
|
+
outlineColor: Cesium.Color.BLUE,
|
|
592
|
+
outlineWidth: 2,
|
|
593
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
594
|
+
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
this._editHandles.push(centerHandle);
|
|
598
|
+
|
|
599
|
+
// 半径控制点
|
|
600
|
+
const radiusHandle = this.createTempEntity('__edit_handle_radius', {
|
|
601
|
+
position: radiusCartesian,
|
|
602
|
+
point: {
|
|
603
|
+
color: Cesium.Color.RED,
|
|
604
|
+
pixelSize: 12,
|
|
605
|
+
outlineColor: Cesium.Color.YELLOW,
|
|
606
|
+
outlineWidth: 2,
|
|
607
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
608
|
+
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
this._editHandles.push(radiusHandle);
|
|
612
|
+
|
|
613
|
+
this._handleVertexIndices = ['center', 'radius'];
|
|
614
|
+
this._originalCircleCenter = center;
|
|
615
|
+
this._originalCircleRadius = radius;
|
|
616
|
+
this._rectangleOriginalPositions = null;
|
|
617
|
+
|
|
618
|
+
this._createCircleOutline();
|
|
619
|
+
} else {
|
|
620
|
+
this._rectangleOriginalPositions = null;
|
|
621
|
+
this._handleVertexIndices = null;
|
|
622
|
+
|
|
623
|
+
positions.forEach((pos, index) => {
|
|
624
|
+
const clampedPos = this._clampPositionToGround(pos);
|
|
625
|
+
const handle = this.createTempEntity(`__edit_handle_${index}`, {
|
|
626
|
+
position: clampedPos,
|
|
627
|
+
point: {
|
|
628
|
+
color: Cesium.Color.YELLOW,
|
|
629
|
+
pixelSize: 12,
|
|
630
|
+
outlineColor: Cesium.Color.RED,
|
|
631
|
+
outlineWidth: 2,
|
|
632
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
633
|
+
// heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
this._editHandles.push(handle);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (positions.length > 1) {
|
|
641
|
+
this._createPositionOutline();
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* 在 VERTEX 模式下创建中心控制点用于平移
|
|
647
|
+
* @param {Array} positions 顶点位置数组
|
|
648
|
+
*/
|
|
649
|
+
_createCenterHandleForVertexMode(positions) {
|
|
650
|
+
const center = this._getEntityCenter(this._selectedEntity);
|
|
651
|
+
if (!center) return;
|
|
652
|
+
|
|
653
|
+
// 修复:中心点贴地
|
|
654
|
+
const clampedCenter = this._clampPositionToGround(center);
|
|
655
|
+
this._originalEntityCenter = clampedCenter;
|
|
656
|
+
|
|
657
|
+
// 圆形特殊处理:保存圆心和半径用于 undo
|
|
658
|
+
if (this._isCircle(this._selectedEntity)) {
|
|
659
|
+
const circleInfo = this._getCircleInfo(this._selectedEntity);
|
|
660
|
+
if (circleInfo) {
|
|
661
|
+
this._originalCircleCenter = circleInfo.center.slice();
|
|
662
|
+
this._originalCircleRadius = circleInfo.radius;
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// 创建中心控制点(使用不同的 ID 区分)
|
|
667
|
+
const handle = this.createTempEntity('__edit_handle_center_vertex', {
|
|
668
|
+
position: clampedCenter,
|
|
669
|
+
point: {
|
|
670
|
+
color: Cesium.Color.CYAN,
|
|
671
|
+
pixelSize: 14,
|
|
672
|
+
outlineColor: Cesium.Color.BLUE,
|
|
673
|
+
outlineWidth: 2,
|
|
674
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
675
|
+
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
this._editHandles.push(handle);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* 更新中心控制点位置(在顶点编辑后重新计算)
|
|
683
|
+
*/
|
|
684
|
+
_updateCenterHandlePosition() {
|
|
685
|
+
// 圆形不需要更新中心点,因为圆心就是中心
|
|
686
|
+
if (this._isCircle(this._selectedEntity)) return;
|
|
687
|
+
|
|
688
|
+
const centerHandle = this._editHandles.find(h => h.id && h.id.includes('center'));
|
|
689
|
+
if (!centerHandle) return;
|
|
690
|
+
|
|
691
|
+
// 重新计算当前几何中心
|
|
692
|
+
const newCenter = this._getEntityCenter(this._selectedEntity);
|
|
693
|
+
if (!newCenter) return;
|
|
694
|
+
|
|
695
|
+
// 修复:中心点贴地
|
|
696
|
+
const clampedCenter = this._clampPositionToGround(newCenter);
|
|
697
|
+
|
|
698
|
+
// 更新中心控制点位置
|
|
699
|
+
centerHandle.position = new Cesium.ConstantPositionProperty(clampedCenter);
|
|
700
|
+
// 更新原始中心点缓存(用于下次平移计算)
|
|
701
|
+
this._originalEntityCenter = clampedCenter;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
_createPositionOutline() {
|
|
705
|
+
// 从控制点获取位置,性能更好(避免每次从实体重新获取和转换)
|
|
706
|
+
const self = this;
|
|
707
|
+
const isPolyline = Cesium.defined(this._selectedEntity.polyline);
|
|
708
|
+
|
|
709
|
+
this._positionOutline = this.createTempEntity(this.generateId('temp_outline'), {
|
|
710
|
+
polyline: {
|
|
711
|
+
positions: new Cesium.CallbackProperty(function () {
|
|
712
|
+
// 从控制点获取位置(已被更新),排除中心控制点
|
|
713
|
+
const handles = self._editHandles;
|
|
714
|
+
if (!handles || handles.length === 0) return [];
|
|
715
|
+
|
|
716
|
+
// 收集所有控制点的当前位置,排除中心控制点
|
|
717
|
+
const degrees = [];
|
|
718
|
+
handles.forEach(handle => {
|
|
719
|
+
// 跳过所有中心控制点(用于平移)- 修复:统一使用 includes('center')
|
|
720
|
+
if (handle.id && handle.id.includes('center')) return;
|
|
721
|
+
|
|
722
|
+
const pos = handle.position.getValue(Cesium.JulianDate.now());
|
|
723
|
+
if (pos) {
|
|
724
|
+
const c = Cesium.Cartographic.fromCartesian(pos);
|
|
725
|
+
degrees.push(Cesium.Math.toDegrees(c.longitude));
|
|
726
|
+
degrees.push(Cesium.Math.toDegrees(c.latitude));
|
|
727
|
+
degrees.push(c.height || 0);
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
if (degrees.length === 0) return [];
|
|
732
|
+
|
|
733
|
+
// 线不闭合,面需要闭合
|
|
734
|
+
if (isPolyline) {
|
|
735
|
+
return Cesium.Cartesian3.fromDegreesArrayHeights(degrees);
|
|
736
|
+
}
|
|
737
|
+
// 闭合多边形(首尾点相同)
|
|
738
|
+
return Cesium.Cartesian3.fromDegreesArrayHeights([...degrees, degrees[0], degrees[1], degrees[2] || 0]);
|
|
739
|
+
}, false),
|
|
740
|
+
material: Cesium.Color.YELLOW,
|
|
741
|
+
width: 2,
|
|
742
|
+
clampToGround: true
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
this._positionOutline._isPreview = true;
|
|
746
|
+
this._tempEntities.push(this._positionOutline);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
_clearEditHandles() {
|
|
750
|
+
this._editHandles.forEach(handle => {
|
|
751
|
+
this.removeTempEntity(handle);
|
|
752
|
+
});
|
|
753
|
+
this._editHandles = [];
|
|
754
|
+
this._positionOutline = null;
|
|
755
|
+
this._polygonPreview = null;
|
|
756
|
+
this._handleVertexIndices = null;
|
|
757
|
+
this._rectangleOriginalPositions = null;
|
|
758
|
+
this._originalCircleCenter = null;
|
|
759
|
+
this._originalCircleRadius = null;
|
|
760
|
+
this._originalEntityCenter = null;
|
|
761
|
+
this._radiusLine = null;
|
|
762
|
+
|
|
763
|
+
this._tempEntities.forEach(entity => {
|
|
764
|
+
this.removeTempEntity(entity);
|
|
765
|
+
});
|
|
766
|
+
this._tempEntities = [];
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
_getEditablePositions(entity) {
|
|
770
|
+
if (!entity) return [];
|
|
771
|
+
|
|
772
|
+
if (Cesium.defined(entity.polyline) && Cesium.defined(entity.polyline.positions)) {
|
|
773
|
+
return entity.polyline.positions.getValue(Cesium.JulianDate.now());
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if (Cesium.defined(entity.polygon) && Cesium.defined(entity.polygon.hierarchy)) {
|
|
777
|
+
const hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now());
|
|
778
|
+
if (hierarchy && hierarchy.positions) {
|
|
779
|
+
let positions = hierarchy.positions;
|
|
780
|
+
|
|
781
|
+
// ✅ 检查并移除闭合点(首尾相同)
|
|
782
|
+
if (positions.length >= 2) {
|
|
783
|
+
const first = positions[0];
|
|
784
|
+
const last = positions[positions.length - 1];
|
|
785
|
+
// 使用距离判断而不是精确相等(避免浮点误差)
|
|
786
|
+
const distance = Cesium.Cartesian3.distance(first, last);
|
|
787
|
+
if (distance < EditTool.CLOSE_POINT_TOLERANCE) {
|
|
788
|
+
positions = positions.slice(0, -1); // 移除最后一个点
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
return positions;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (Cesium.defined(entity.position)) {
|
|
797
|
+
return [entity.position.getValue(Cesium.JulianDate.now())];
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
_getEntityPositions(entity) {
|
|
804
|
+
return this._getEditablePositions(entity);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
/**
|
|
808
|
+
* 获取图元中心点
|
|
809
|
+
*/
|
|
810
|
+
_getEntityCenter(entity) {
|
|
811
|
+
const positions = this._getEditablePositions(entity);
|
|
812
|
+
if (!positions || positions.length === 0) return null;
|
|
813
|
+
|
|
814
|
+
let x = 0, y = 0, z = 0;
|
|
815
|
+
positions.forEach(p => {
|
|
816
|
+
x += p.x;
|
|
817
|
+
y += p.y;
|
|
818
|
+
z += p.z;
|
|
819
|
+
});
|
|
820
|
+
const count = positions.length;
|
|
821
|
+
return new Cesium.Cartesian3(x / count, y / count, z / count);
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* 平移整个图元
|
|
826
|
+
* @param {Cesium.Cartesian3} newCenter 新中心点
|
|
827
|
+
*/
|
|
828
|
+
_translateEntity(newCenter) {
|
|
829
|
+
if (!this._selectedEntity || !this._originalEntityCenter) {
|
|
830
|
+
console.log('Translate skipped: no entity or no original center', {
|
|
831
|
+
hasEntity: !!this._selectedEntity,
|
|
832
|
+
originalCenter: this._originalEntityCenter
|
|
833
|
+
});
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// 修复:新中心点贴地
|
|
838
|
+
const clampedNewCenter = this._clampPositionToGround(newCenter);
|
|
839
|
+
|
|
840
|
+
// 计算偏移量
|
|
841
|
+
const deltaX = clampedNewCenter.x - this._originalEntityCenter.x;
|
|
842
|
+
const deltaY = clampedNewCenter.y - this._originalEntityCenter.y;
|
|
843
|
+
const deltaZ = clampedNewCenter.z - this._originalEntityCenter.z;
|
|
844
|
+
|
|
845
|
+
// 计算真正的新中心位置(不是鼠标位置)
|
|
846
|
+
const actualNewCenter = new Cesium.Cartesian3(
|
|
847
|
+
this._originalEntityCenter.x + deltaX,
|
|
848
|
+
this._originalEntityCenter.y + deltaY,
|
|
849
|
+
this._originalEntityCenter.z + deltaZ
|
|
850
|
+
);
|
|
851
|
+
|
|
852
|
+
// 更新原始中心(用于下次平移计算)
|
|
853
|
+
this._originalEntityCenter = actualNewCenter;
|
|
854
|
+
|
|
855
|
+
// 更新中心控制点位置
|
|
856
|
+
this._dragHandle.position = new Cesium.ConstantPositionProperty(actualNewCenter);
|
|
857
|
+
|
|
858
|
+
if (Cesium.defined(this._selectedEntity.polyline)) {
|
|
859
|
+
// 线图元:平移所有位置
|
|
860
|
+
const newPositions = this._originalPositions.map(p => {
|
|
861
|
+
const newPos = new Cesium.Cartesian3(p.x + deltaX, p.y + deltaY, p.z + deltaZ);
|
|
862
|
+
// 修复:贴地
|
|
863
|
+
return this._clampPositionToGround(newPos);
|
|
864
|
+
});
|
|
865
|
+
this._selectedEntity.polyline.positions = newPositions;
|
|
866
|
+
|
|
867
|
+
// 修复:更新所有顶点控制点位置,同时更新中心控制点位置
|
|
868
|
+
this._editHandles.forEach((handle) => {
|
|
869
|
+
// 修复:统一使用 includes('center') 来判断中心控制点
|
|
870
|
+
if (handle.id && handle.id.includes('center')) {
|
|
871
|
+
// 中心控制点已经更新过了
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
// 找到对应的顶点索引
|
|
875
|
+
const match = handle.id.match(/__edit_handle_(\d+)$/);
|
|
876
|
+
if (match) {
|
|
877
|
+
const index = parseInt(match[1], 10);
|
|
878
|
+
if (newPositions[index]) {
|
|
879
|
+
handle.position = new Cesium.ConstantPositionProperty(newPositions[index]);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
// 更新原始位置缓存
|
|
885
|
+
this._originalPositions = newPositions;
|
|
886
|
+
} else if (Cesium.defined(this._selectedEntity.polygon)) {
|
|
887
|
+
if (this._isCircle(this._selectedEntity)) {
|
|
888
|
+
// 圆形平移
|
|
889
|
+
const oldCenter = this._selectedEntity._circleCenter; // [lon, lat]
|
|
890
|
+
|
|
891
|
+
// 将旧的圆心经纬度转为 Cartesian3
|
|
892
|
+
const oldCenterCartesian = Cesium.Cartesian3.fromDegrees(oldCenter[0], oldCenter[1], 0);
|
|
893
|
+
|
|
894
|
+
// 应用偏移量计算新的 Cartesian3 位置
|
|
895
|
+
const newCenterCartesian = new Cesium.Cartesian3(
|
|
896
|
+
oldCenterCartesian.x + deltaX,
|
|
897
|
+
oldCenterCartesian.y + deltaY,
|
|
898
|
+
oldCenterCartesian.z + deltaZ
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
// 将新的 Cartesian3 转回经纬度
|
|
902
|
+
const newCenterCartographic = Cesium.Cartographic.fromCartesian(newCenterCartesian);
|
|
903
|
+
const newCenterLon = Cesium.Math.toDegrees(newCenterCartographic.longitude);
|
|
904
|
+
const newCenterLat = Cesium.Math.toDegrees(newCenterCartographic.latitude);
|
|
905
|
+
|
|
906
|
+
this._selectedEntity._circleCenter = [newCenterLon, newCenterLat];
|
|
907
|
+
|
|
908
|
+
// 更新多边形顶点
|
|
909
|
+
const positions = this._generateCirclePositions(
|
|
910
|
+
this._selectedEntity._circleCenter,
|
|
911
|
+
this._selectedEntity._circleRadius,
|
|
912
|
+
256
|
|
913
|
+
);
|
|
914
|
+
const flat = positions.flatMap(c => [c[0], c[1], 0]);
|
|
915
|
+
this._selectedEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(
|
|
916
|
+
Cesium.Cartesian3.fromDegreesArrayHeights(flat)
|
|
917
|
+
);
|
|
918
|
+
|
|
919
|
+
// 更新圆心控制点位置
|
|
920
|
+
const centerHandle = this._editHandles.find(h => h.id && h.id.includes('__edit_handle_center'));
|
|
921
|
+
if (centerHandle) {
|
|
922
|
+
centerHandle.position = new Cesium.ConstantPositionProperty(newCenterCartesian);
|
|
923
|
+
}
|
|
924
|
+
} else {
|
|
925
|
+
// 普通多边形:平移所有顶点
|
|
926
|
+
const newPositions = this._originalPositions.map(p => {
|
|
927
|
+
const newPos = new Cesium.Cartesian3(p.x + deltaX, p.y + deltaY, p.z + deltaZ);
|
|
928
|
+
// 修复:贴地
|
|
929
|
+
return this._clampPositionToGround(newPos);
|
|
930
|
+
});
|
|
931
|
+
// 添加闭合点
|
|
932
|
+
newPositions.push(newPositions[0].clone());
|
|
933
|
+
this._selectedEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(newPositions);
|
|
934
|
+
|
|
935
|
+
// 修复:更新顶点控制点位置,使用更可靠的ID匹配
|
|
936
|
+
this._editHandles.forEach((handle) => {
|
|
937
|
+
// 修复:统一使用 includes('center') 来判断中心控制点
|
|
938
|
+
if (handle.id && handle.id.includes('center')) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
// 提取数字索引
|
|
942
|
+
const match = handle.id.match(/__edit_handle_(\d+)$/);
|
|
943
|
+
if (match) {
|
|
944
|
+
const index = parseInt(match[1], 10);
|
|
945
|
+
if (newPositions[index]) {
|
|
946
|
+
handle.position = new Cesium.ConstantPositionProperty(newPositions[index]);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
// 更新原始位置缓存
|
|
952
|
+
this._originalPositions = newPositions.slice(0, -1);
|
|
953
|
+
}
|
|
954
|
+
} else if (Cesium.defined(this._selectedEntity.position)) {
|
|
955
|
+
// 点/文本图元:平移位置
|
|
956
|
+
this._selectedEntity.position = new Cesium.ConstantPositionProperty(actualNewCenter);
|
|
957
|
+
this._originalPositions = [actualNewCenter];
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
_isRectangle(positions) {
|
|
962
|
+
if (positions.length !== 4) return false;
|
|
963
|
+
|
|
964
|
+
const relativeTolerance = 0.001; // 0.1% 相对误差
|
|
965
|
+
|
|
966
|
+
// 提取XY坐标(忽略Z)
|
|
967
|
+
const xy = positions.map(p => ({ x: p.x, y: p.y }));
|
|
968
|
+
|
|
969
|
+
const [v0, v1, v2, v3] = xy;
|
|
970
|
+
|
|
971
|
+
// 计算XY平面上的距离
|
|
972
|
+
const dist = (p1, p2) => Math.sqrt(
|
|
973
|
+
Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
const d01 = dist(v0, v1);
|
|
977
|
+
const d12 = dist(v1, v2);
|
|
978
|
+
const d23 = dist(v2, v3);
|
|
979
|
+
const d30 = dist(v3, v0);
|
|
980
|
+
const d02 = dist(v0, v2);
|
|
981
|
+
const d13 = dist(v1, v3);
|
|
982
|
+
|
|
983
|
+
const oppositeSidesEqual =
|
|
984
|
+
Math.abs(d01 - d23) / Math.max(d01, d23) < relativeTolerance &&
|
|
985
|
+
Math.abs(d12 - d30) / Math.max(d12, d30) < relativeTolerance;
|
|
986
|
+
|
|
987
|
+
const diagonalsEqual = Math.abs(d02 - d13) / Math.max(d02, d13) < relativeTolerance;
|
|
988
|
+
|
|
989
|
+
return oppositeSidesEqual && diagonalsEqual;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* 检测 entity 是否为圆
|
|
994
|
+
*/
|
|
995
|
+
_isCircle(entity) {
|
|
996
|
+
return entity && entity._isCircle === true;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* 获取圆的中心点和半径
|
|
1001
|
+
*/
|
|
1002
|
+
_getCircleInfo(entity) {
|
|
1003
|
+
if (!this._isCircle(entity)) return null;
|
|
1004
|
+
return {
|
|
1005
|
+
center: entity._circleCenter, // [lon, lat]
|
|
1006
|
+
radius: entity._circleRadius // 米
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
/**
|
|
1011
|
+
* 生成圆的多边形顶点(用于更新圆的形状)
|
|
1012
|
+
* @param {Array} center [lon, lat]
|
|
1013
|
+
* @param {number} radius 半径(米)
|
|
1014
|
+
* @param {number} segments 顶点数
|
|
1015
|
+
* @returns {Array} 经纬度数组
|
|
1016
|
+
*/
|
|
1017
|
+
_generateCirclePositions(center, radius, segments = 256) {
|
|
1018
|
+
const [lon, lat] = center;
|
|
1019
|
+
const radPerDeg = Math.PI / 180;
|
|
1020
|
+
const positions = [];
|
|
1021
|
+
|
|
1022
|
+
// 当前纬度的经度米数(随纬度变化)
|
|
1023
|
+
const metersPerLonDeg = 111320 * Math.cos(lat * radPerDeg);
|
|
1024
|
+
const metersPerLatDeg = 110540; // 纬度方向相对固定
|
|
1025
|
+
|
|
1026
|
+
for (let i = 0; i <= segments; i++) {
|
|
1027
|
+
const angle = (i / segments) * Math.PI * 2;
|
|
1028
|
+
const dLon = (radius * Math.cos(angle) / metersPerLonDeg);
|
|
1029
|
+
const dLat = (radius * Math.sin(angle) / metersPerLatDeg);
|
|
1030
|
+
positions.push([lon + dLon, lat + dLat]);
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
return positions;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
/**
|
|
1037
|
+
* 计算圆周上的点
|
|
1038
|
+
* @param {Array} center [lon, lat]
|
|
1039
|
+
* @param {number} radius 半径(米)
|
|
1040
|
+
* @param {number} angleRadians 角度(弧度)
|
|
1041
|
+
* @returns {Array} [lon, lat]
|
|
1042
|
+
*/
|
|
1043
|
+
_getPointOnCircle(center, radius, angleRadians) {
|
|
1044
|
+
const [lon, lat] = center;
|
|
1045
|
+
const radPerDeg = Math.PI / 180;
|
|
1046
|
+
const metersPerLonDeg = 111320 * Math.cos(lat * radPerDeg);
|
|
1047
|
+
const metersPerLatDeg = 110540;
|
|
1048
|
+
|
|
1049
|
+
const dLon = (radius * Math.cos(angleRadians) / metersPerLonDeg);
|
|
1050
|
+
const dLat = (radius * Math.sin(angleRadians) / metersPerLatDeg);
|
|
1051
|
+
return [lon + dLon, lat + dLat];
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* 将 oldAxis 绕原点旋转到与 newAxis 垂直,并保持原长度和相对方向(顺/逆时针)
|
|
1056
|
+
* @param {Array} newAxis [dx, dy]
|
|
1057
|
+
* @param {Array} oldAxis [dx, dy]
|
|
1058
|
+
* @returns {Array} [rx, ry]
|
|
1059
|
+
*/
|
|
1060
|
+
_rotatePerpendicular(newAxis, oldAxis) {
|
|
1061
|
+
const newLen = Math.hypot(newAxis[0], newAxis[1]);
|
|
1062
|
+
const oldLen = Math.hypot(oldAxis[0], oldAxis[1]);
|
|
1063
|
+
if (newLen === 0 || oldLen === 0) return [0, 0];
|
|
1064
|
+
|
|
1065
|
+
const ux = newAxis[0] / newLen;
|
|
1066
|
+
const uy = newAxis[1] / newLen;
|
|
1067
|
+
|
|
1068
|
+
// 叉积判断 oldAxis 在 newAxis 的左侧还是右侧
|
|
1069
|
+
const cross = newAxis[0] * oldAxis[1] - newAxis[1] * oldAxis[0];
|
|
1070
|
+
const sign = cross >= 0 ? 1 : -1;
|
|
1071
|
+
|
|
1072
|
+
// 将 newAxis 旋转 ±90° 得到新的垂直方向,并保持 oldAxis 长度
|
|
1073
|
+
return [-sign * uy * oldLen, sign * ux * oldLen];
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
/**
|
|
1077
|
+
* 创建圆形辅助线
|
|
1078
|
+
*/
|
|
1079
|
+
_createCircleOutline() {
|
|
1080
|
+
const self = this;
|
|
1081
|
+
|
|
1082
|
+
// 圆周辅助线(黄色)
|
|
1083
|
+
this._positionOutline = this.createTempEntity(this.generateId('temp_circle_outline'), {
|
|
1084
|
+
polyline: {
|
|
1085
|
+
positions: new Cesium.CallbackProperty(function () {
|
|
1086
|
+
const circleInfo = self._getCircleInfo(self._selectedEntity);
|
|
1087
|
+
if (!circleInfo) return [];
|
|
1088
|
+
|
|
1089
|
+
const { center, radius } = circleInfo;
|
|
1090
|
+
const positions = self._generateCirclePositions(center, radius, 256);
|
|
1091
|
+
const flat = positions.flatMap(c => [c[0], c[1], 0]);
|
|
1092
|
+
return Cesium.Cartesian3.fromDegreesArrayHeights(flat);
|
|
1093
|
+
}, false),
|
|
1094
|
+
material: Cesium.Color.YELLOW,
|
|
1095
|
+
width: 2,
|
|
1096
|
+
clampToGround: true
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
this._positionOutline._isPreview = true;
|
|
1100
|
+
this._tempEntities.push(this._positionOutline);
|
|
1101
|
+
|
|
1102
|
+
// 半径连线(红色,从圆心到编辑点)
|
|
1103
|
+
// 修复:正确获取半径控制点的位置
|
|
1104
|
+
this._radiusLine = this.createTempEntity(this.generateId('temp_radius_line'), {
|
|
1105
|
+
polyline: {
|
|
1106
|
+
positions: new Cesium.CallbackProperty(function () {
|
|
1107
|
+
const circleInfo = self._getCircleInfo(self._selectedEntity);
|
|
1108
|
+
if (!circleInfo) return [];
|
|
1109
|
+
|
|
1110
|
+
const { center } = circleInfo;
|
|
1111
|
+
// 修复:通过ID查找半径控制点,而不是固定索引
|
|
1112
|
+
const radiusHandle = self._editHandles.find(h => h.id && h.id.includes('__edit_handle_radius'));
|
|
1113
|
+
if (!radiusHandle) return [];
|
|
1114
|
+
|
|
1115
|
+
const radiusPos = radiusHandle.position.getValue(Cesium.JulianDate.now());
|
|
1116
|
+
if (!radiusPos) return [];
|
|
1117
|
+
|
|
1118
|
+
const centerCart = Cesium.Cartesian3.fromDegrees(center[0], center[1], 0);
|
|
1119
|
+
return [centerCart, radiusPos];
|
|
1120
|
+
}, false),
|
|
1121
|
+
material: Cesium.Color.RED,
|
|
1122
|
+
width: 3,
|
|
1123
|
+
clampToGround: true
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
this._radiusLine._isPreview = true;
|
|
1127
|
+
this._tempEntities.push(this._radiusLine);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* 计算两点之间的距离(米)
|
|
1132
|
+
* @param {Array} coord1 [lon, lat]
|
|
1133
|
+
* @param {Array} coord2 [lon, lat]
|
|
1134
|
+
* @returns {number} 距离(米)
|
|
1135
|
+
*/
|
|
1136
|
+
_calcDistance(coord1, coord2) {
|
|
1137
|
+
const earthRadius = 6371000; // 米
|
|
1138
|
+
const [lon1, lat1] = coord1.map(d => d * Math.PI / 180);
|
|
1139
|
+
const [lon2, lat2] = coord2.map(d => d * Math.PI / 180);
|
|
1140
|
+
|
|
1141
|
+
const dLat = lat2 - lat1;
|
|
1142
|
+
const dLon = lon2 - lon1;
|
|
1143
|
+
|
|
1144
|
+
const a = Math.sin(dLat / 2) ** 2 +
|
|
1145
|
+
Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
|
|
1146
|
+
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
1147
|
+
|
|
1148
|
+
return earthRadius * c;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* 计算矩形拖拽后的新顶点位置
|
|
1153
|
+
* @param {Cesium.Cartesian3} newPos - 拖拽的新位置
|
|
1154
|
+
* @param {Array} originalPositions - 原始4个顶点位置
|
|
1155
|
+
* @param {number} draggedIndex - 被拖拽的顶点索引 (0,1,2,3)
|
|
1156
|
+
* @returns {Array} 新的4个顶点位置数组
|
|
1157
|
+
*/
|
|
1158
|
+
_computeRectangleNewPositions(newPos, originalPositions, draggedIndex) {
|
|
1159
|
+
// 对角顶点索引:0->2, 1->3, 2->0, 3->1
|
|
1160
|
+
const oppositeIndex = (draggedIndex + 2) % 4;
|
|
1161
|
+
const newP = Cesium.Cartographic.fromCartesian(newPos);
|
|
1162
|
+
const currentPos = originalPositions.map(p => Cesium.Cartographic.fromCartesian(p));
|
|
1163
|
+
const oppositePos = currentPos[oppositeIndex];
|
|
1164
|
+
|
|
1165
|
+
const newPositions = new Array(4);
|
|
1166
|
+
newPositions[draggedIndex] = newPos; // 拖拽点为新位置
|
|
1167
|
+
newPositions[oppositeIndex] = originalPositions[oppositeIndex]; // 对角点固定
|
|
1168
|
+
|
|
1169
|
+
// 计算相邻两个顶点
|
|
1170
|
+
// draggedIndex 的相邻顶点是 (draggedIndex + 1) % 4 和 (draggedIndex + 3) % 4
|
|
1171
|
+
const neighbor1 = (draggedIndex + 1) % 4;
|
|
1172
|
+
const neighbor2 = (draggedIndex + 3) % 4;
|
|
1173
|
+
|
|
1174
|
+
// neighbor1 使用 newPos 的 longitude 和 opposite 的 latitude
|
|
1175
|
+
newPositions[neighbor1] = Cesium.Cartesian3.fromRadians(
|
|
1176
|
+
newP.longitude,
|
|
1177
|
+
oppositePos.latitude,
|
|
1178
|
+
oppositePos.height
|
|
1179
|
+
);
|
|
1180
|
+
|
|
1181
|
+
// neighbor2 使用 opposite 的 longitude 和 newPos 的 latitude
|
|
1182
|
+
newPositions[neighbor2] = Cesium.Cartesian3.fromRadians(
|
|
1183
|
+
oppositePos.longitude,
|
|
1184
|
+
newP.latitude,
|
|
1185
|
+
oppositePos.height
|
|
1186
|
+
);
|
|
1187
|
+
|
|
1188
|
+
return newPositions;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
_updateEntityPosition(newLonLat) {
|
|
1192
|
+
if (!this._selectedEntity) return;
|
|
1193
|
+
|
|
1194
|
+
const handleIndex = this._editHandles.indexOf(this._dragHandle);
|
|
1195
|
+
if (handleIndex === -1) return;
|
|
1196
|
+
|
|
1197
|
+
// 修复:新位置贴地(高度设为0)
|
|
1198
|
+
const newCartesian = Cesium.Cartesian3.fromDegrees(newLonLat[0], newLonLat[1], 0);
|
|
1199
|
+
|
|
1200
|
+
// 标绘图元:拖拽控制点重新生成几何
|
|
1201
|
+
if (this._selectedEntity._isPlotGraphic) {
|
|
1202
|
+
const handleId = this._dragHandle.id;
|
|
1203
|
+
|
|
1204
|
+
// 拖拽中心控制点:平移整个标绘图元
|
|
1205
|
+
if (handleId && handleId.includes('center') && this._originalEntityCenter) {
|
|
1206
|
+
const clampedNewCenter = this._clampPositionToGround(newCartesian);
|
|
1207
|
+
this._translatePlotControlPoints(clampedNewCenter, this._originalEntityCenter);
|
|
1208
|
+
this._originalEntityCenter = clampedNewCenter;
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const handleMatch = handleId.match(/__edit_handle_(\d+)$/);
|
|
1213
|
+
if (!handleMatch) return;
|
|
1214
|
+
|
|
1215
|
+
const index = parseInt(handleMatch[1], 10);
|
|
1216
|
+
const plotType = this._selectedEntity._plotType;
|
|
1217
|
+
|
|
1218
|
+
// 扇形:拖拽圆心(索引0)时整体平移,保持两半径等长
|
|
1219
|
+
if (plotType === PlotGraphicType.SECTOR && index === 0) {
|
|
1220
|
+
const oldCenter = this._selectedEntity._controlPoints[0];
|
|
1221
|
+
const oldCenterCart = Cesium.Cartesian3.fromDegrees(oldCenter[0], oldCenter[1], oldCenter[2] || 0);
|
|
1222
|
+
const clampedNewCenter = this._clampPositionToGround(newCartesian);
|
|
1223
|
+
this._translatePlotControlPoints(clampedNewCenter, oldCenterCart);
|
|
1224
|
+
// 中心控制点固定在圆心
|
|
1225
|
+
this._originalEntityCenter = this._clampPositionToGround(
|
|
1226
|
+
Cesium.Cartesian3.fromDegrees(
|
|
1227
|
+
this._selectedEntity._controlPoints[0][0],
|
|
1228
|
+
this._selectedEntity._controlPoints[0][1],
|
|
1229
|
+
this._selectedEntity._controlPoints[0][2] || 0
|
|
1230
|
+
)
|
|
1231
|
+
);
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
const controlPoints = this._selectedEntity._controlPoints.map((cp, i) =>
|
|
1236
|
+
i === index ? [newLonLat[0], newLonLat[1], cp[2] || 0] : cp
|
|
1237
|
+
);
|
|
1238
|
+
|
|
1239
|
+
// 扇形:两个半径控制点同步缩放,保持半径一致
|
|
1240
|
+
if (plotType === PlotGraphicType.SECTOR && index > 0) {
|
|
1241
|
+
const center = controlPoints[0];
|
|
1242
|
+
// 使用球面距离(米)作为新半径,与 createSector 保持一致
|
|
1243
|
+
const newRadius = PlotGeometryUtil._calcSphericalDistance(controlPoints[index], center);
|
|
1244
|
+
const otherIndex = index === 1 ? 2 : 1;
|
|
1245
|
+
const otherCp = controlPoints[otherIndex];
|
|
1246
|
+
// 保持另一个控制点的真实地理方位角,按新半径在球面圆上重新定位
|
|
1247
|
+
const otherBearing = PlotGeometryUtil._getBearing(center, otherCp);
|
|
1248
|
+
const newOtherPoint = PlotGeometryUtil._getPointAtBearingAndDistance(center, newRadius, otherBearing);
|
|
1249
|
+
controlPoints[otherIndex] = [newOtherPoint[0], newOtherPoint[1], otherCp[2] || 0];
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// 椭圆:长短半轴端点控制,保持两轴垂直
|
|
1253
|
+
if (plotType === PlotGraphicType.ELLIPSE && (index === 1 || index === 2)) {
|
|
1254
|
+
const center = controlPoints[0];
|
|
1255
|
+
if (index === 1) {
|
|
1256
|
+
// 长轴端点移动:改变长轴长度/方向,短轴端点跟随旋转并保持长度
|
|
1257
|
+
const newMajor = [controlPoints[1][0] - center[0], controlPoints[1][1] - center[1]];
|
|
1258
|
+
const oldMinor = [
|
|
1259
|
+
this._selectedEntity._controlPoints[2][0] - center[0],
|
|
1260
|
+
this._selectedEntity._controlPoints[2][1] - center[1]
|
|
1261
|
+
];
|
|
1262
|
+
const newMinor = this._rotatePerpendicular(newMajor, oldMinor);
|
|
1263
|
+
controlPoints[2] = [center[0] + newMinor[0], center[1] + newMinor[1], center[2] || 0];
|
|
1264
|
+
} else if (index === 2) {
|
|
1265
|
+
// 短轴端点移动:只改变短轴长度,投影到过中心且与长轴垂直的直线上
|
|
1266
|
+
const major = [controlPoints[1][0] - center[0], controlPoints[1][1] - center[1]];
|
|
1267
|
+
const majorLen = Math.hypot(major[0], major[1]);
|
|
1268
|
+
if (majorLen > 0) {
|
|
1269
|
+
const ux = major[0] / majorLen;
|
|
1270
|
+
const uy = major[1] / majorLen;
|
|
1271
|
+
// 长轴逆时针旋转 90° 得到垂直方向
|
|
1272
|
+
const perpUx = -uy;
|
|
1273
|
+
const perpUy = ux;
|
|
1274
|
+
const mouseVec = [newLonLat[0] - center[0], newLonLat[1] - center[1]];
|
|
1275
|
+
const projectedLen = mouseVec[0] * perpUx + mouseVec[1] * perpUy;
|
|
1276
|
+
controlPoints[2] = [
|
|
1277
|
+
center[0] + perpUx * projectedLen,
|
|
1278
|
+
center[1] + perpUy * projectedLen,
|
|
1279
|
+
center[2] || 0
|
|
1280
|
+
];
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// 更新控制点缓存
|
|
1286
|
+
this._selectedEntity._controlPoints = controlPoints;
|
|
1287
|
+
|
|
1288
|
+
// 重新生成几何
|
|
1289
|
+
const wrapper = this._graphicModule.wrapperMap.get(this._selectedEntity.id);
|
|
1290
|
+
if (wrapper) {
|
|
1291
|
+
wrapper.setControlPoints(controlPoints);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// 更新控制点位置(不含中心控制点)
|
|
1295
|
+
this._editHandles.forEach((handle) => {
|
|
1296
|
+
if (handle.id && handle.id.includes('center')) return;
|
|
1297
|
+
// 按句柄 ID 解析真实控制点索引(扇形跳过了索引 0)
|
|
1298
|
+
const match = handle.id.match(/__edit_handle_(\d+)$/);
|
|
1299
|
+
if (match) {
|
|
1300
|
+
const cpIndex = parseInt(match[1], 10);
|
|
1301
|
+
const cp = controlPoints[cpIndex];
|
|
1302
|
+
handle.position = new Cesium.ConstantPositionProperty(
|
|
1303
|
+
this._clampPositionToGround(Cesium.Cartesian3.fromDegrees(cp[0], cp[1], cp[2] || 0))
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
// 更新中心控制点位置
|
|
1309
|
+
const centerHandle = this._editHandles.find(h => h.id && h.id.includes('center'));
|
|
1310
|
+
if (centerHandle) {
|
|
1311
|
+
// 扇形/椭圆:中心控制点固定在中心;其他类型使用重心
|
|
1312
|
+
const newCenter = (plotType === PlotGraphicType.SECTOR || plotType === PlotGraphicType.ELLIPSE)
|
|
1313
|
+
? controlPoints[0]
|
|
1314
|
+
: this._getPlotControlPointsCenter(controlPoints);
|
|
1315
|
+
centerHandle.position = new Cesium.ConstantPositionProperty(
|
|
1316
|
+
this._clampPositionToGround(Cesium.Cartesian3.fromDegrees(newCenter[0], newCenter[1], newCenter[2] || 0))
|
|
1317
|
+
);
|
|
1318
|
+
this._originalEntityCenter = this._clampPositionToGround(
|
|
1319
|
+
Cesium.Cartesian3.fromDegrees(newCenter[0], newCenter[1], newCenter[2] || 0)
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// 更新原始位置缓存
|
|
1324
|
+
this._originalPositions = this._getEntityPositions(this._selectedEntity);
|
|
1325
|
+
return;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
const hierarchy = this._selectedEntity.polygon?.hierarchy?.getValue(Cesium.JulianDate.now());
|
|
1329
|
+
|
|
1330
|
+
// 拖动中心控制点:平移整个图元
|
|
1331
|
+
// 修复:统一使用 includes('center') 来判断
|
|
1332
|
+
if (this._dragHandle.id && this._dragHandle.id.includes('center') && this._originalEntityCenter) {
|
|
1333
|
+
this._translateEntity(newCartesian);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
// 矩形特殊情况:重新计算所有4个顶点
|
|
1338
|
+
// 确保是数字索引(矩形用 [0,1,2,3],圆用 ['center','radius'])
|
|
1339
|
+
if (this._handleVertexIndices &&
|
|
1340
|
+
Array.isArray(this._handleVertexIndices) &&
|
|
1341
|
+
typeof this._handleVertexIndices[0] === 'number' &&
|
|
1342
|
+
Cesium.defined(this._selectedEntity.polygon)) {
|
|
1343
|
+
if (!hierarchy || !hierarchy.positions || hierarchy.positions.length < 4) return;
|
|
1344
|
+
|
|
1345
|
+
const draggedVertexIndex = this._handleVertexIndices[handleIndex];
|
|
1346
|
+
const newPositions = this._computeRectangleNewPositions(newCartesian, hierarchy.positions, draggedVertexIndex);
|
|
1347
|
+
|
|
1348
|
+
// 修复:所有新位置贴地
|
|
1349
|
+
const clampedPositions = newPositions.map(pos => this._clampPositionToGround(pos));
|
|
1350
|
+
|
|
1351
|
+
// 更新多边形
|
|
1352
|
+
this._selectedEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(clampedPositions);
|
|
1353
|
+
|
|
1354
|
+
// 更新所有控制点位置(使用 ConstantPositionProperty 确保正确更新)
|
|
1355
|
+
this._editHandles.forEach((handle, i) => {
|
|
1356
|
+
// 修复:跳过中心控制点
|
|
1357
|
+
if (handle.id && handle.id.includes('center')) return;
|
|
1358
|
+
handle.position = new Cesium.ConstantPositionProperty(clampedPositions[i]);
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
// 修复:顶点编辑后重新计算并更新中心控制点位置
|
|
1362
|
+
this._updateCenterHandlePosition();
|
|
1363
|
+
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// 圆形特殊情况:编辑半径或圆心
|
|
1368
|
+
if (this._handleVertexIndices &&
|
|
1369
|
+
Array.isArray(this._handleVertexIndices) &&
|
|
1370
|
+
this._isCircle(this._selectedEntity)) {
|
|
1371
|
+
|
|
1372
|
+
const circleInfo = this._getCircleInfo(this._selectedEntity);
|
|
1373
|
+
if (!circleInfo) return;
|
|
1374
|
+
|
|
1375
|
+
const handleId = this._dragHandle.id;
|
|
1376
|
+
|
|
1377
|
+
// 拖拽圆心:平移整个圆
|
|
1378
|
+
if (handleId && handleId.includes('__edit_handle_center')) {
|
|
1379
|
+
// 计算新圆心
|
|
1380
|
+
const newCenterLonLat = newLonLat;
|
|
1381
|
+
|
|
1382
|
+
// 更新圆心控制点位置
|
|
1383
|
+
this._dragHandle.position = newCartesian;
|
|
1384
|
+
|
|
1385
|
+
// 更新圆的实体属性
|
|
1386
|
+
this._selectedEntity._circleCenter = newCenterLonLat;
|
|
1387
|
+
|
|
1388
|
+
// 更新多边形顶点
|
|
1389
|
+
const positions = this._generateCirclePositions(newCenterLonLat, circleInfo.radius, 256);
|
|
1390
|
+
const flat = positions.flatMap(c => [c[0], c[1], 0]);
|
|
1391
|
+
this._selectedEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(
|
|
1392
|
+
Cesium.Cartesian3.fromDegreesArrayHeights(flat)
|
|
1393
|
+
);
|
|
1394
|
+
|
|
1395
|
+
// 更新半径控制点位置
|
|
1396
|
+
const radiusHandle = this._editHandles.find(h => h.id && h.id.includes('radius'));
|
|
1397
|
+
if (radiusHandle) {
|
|
1398
|
+
const radiusPoint = this._getPointOnCircle(newCenterLonLat, circleInfo.radius, 0);
|
|
1399
|
+
radiusHandle.position = new Cesium.ConstantPositionProperty(
|
|
1400
|
+
Cesium.Cartesian3.fromDegrees(radiusPoint[0], radiusPoint[1], 0)
|
|
1401
|
+
);
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// 拖拽半径:计算新半径
|
|
1408
|
+
let newRadius = this._calcDistance(circleInfo.center, newLonLat);
|
|
1409
|
+
// 更新半径控制点位置(在圆周上)
|
|
1410
|
+
const angle = Math.atan2(newLonLat[1] - circleInfo.center[1], newLonLat[0] - circleInfo.center[0]);
|
|
1411
|
+
const radiusPoint = this._getPointOnCircle(circleInfo.center, newRadius, angle);
|
|
1412
|
+
this._dragHandle.position = new Cesium.ConstantPositionProperty(
|
|
1413
|
+
Cesium.Cartesian3.fromDegrees(radiusPoint[0], radiusPoint[1], 0)
|
|
1414
|
+
);
|
|
1415
|
+
|
|
1416
|
+
// 更新圆的实体属性
|
|
1417
|
+
this._selectedEntity._circleCenter = circleInfo.center;
|
|
1418
|
+
this._selectedEntity._circleRadius = newRadius;
|
|
1419
|
+
|
|
1420
|
+
// 更新多边形顶点
|
|
1421
|
+
const positions = this._generateCirclePositions(circleInfo.center, newRadius, 256);
|
|
1422
|
+
const flat = positions.flatMap(c => [c[0], c[1], 0]);
|
|
1423
|
+
this._selectedEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(
|
|
1424
|
+
Cesium.Cartesian3.fromDegreesArrayHeights(flat)
|
|
1425
|
+
);
|
|
1426
|
+
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (Cesium.defined(this._selectedEntity.polyline)) {
|
|
1431
|
+
const positions = this._selectedEntity.polyline.positions.getValue(Cesium.JulianDate.now());
|
|
1432
|
+
if (positions && positions[handleIndex]) {
|
|
1433
|
+
// 直接修改数组元素触发更新
|
|
1434
|
+
positions[handleIndex] = newCartesian;
|
|
1435
|
+
this._selectedEntity.polyline.positions = positions;
|
|
1436
|
+
this._dragHandle.position = new Cesium.ConstantPositionProperty(newCartesian);
|
|
1437
|
+
|
|
1438
|
+
// 修复:线编辑后重新计算并更新中心控制点位置
|
|
1439
|
+
this._updateCenterHandlePosition();
|
|
1440
|
+
}
|
|
1441
|
+
} else if (Cesium.defined(this._selectedEntity.polygon)) {
|
|
1442
|
+
// 获取当前实际顶点数(不包含闭合点)
|
|
1443
|
+
const actualVertexCount = this._editHandles.filter(h => !h.id.includes('center')).length;
|
|
1444
|
+
|
|
1445
|
+
// 构建新位置数组
|
|
1446
|
+
const newPositions = [];
|
|
1447
|
+
let vertexIndex = 0;
|
|
1448
|
+
for (let i = 0; i < this._editHandles.length; i++) {
|
|
1449
|
+
const handle = this._editHandles[i];
|
|
1450
|
+
// 修复:跳过中心控制点
|
|
1451
|
+
if (handle.id && handle.id.includes('center')) continue;
|
|
1452
|
+
|
|
1453
|
+
if (handle === this._dragHandle) {
|
|
1454
|
+
newPositions.push(newCartesian);
|
|
1455
|
+
} else {
|
|
1456
|
+
// 从控制点获取当前位置
|
|
1457
|
+
const pos = handle.position.getValue(Cesium.JulianDate.now());
|
|
1458
|
+
// 修复:确保位置贴地
|
|
1459
|
+
newPositions.push(this._clampPositionToGround(pos));
|
|
1460
|
+
}
|
|
1461
|
+
vertexIndex++;
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
// ✅ 添加闭合点
|
|
1465
|
+
newPositions.push(newPositions[0].clone());
|
|
1466
|
+
|
|
1467
|
+
// 更新多边形
|
|
1468
|
+
this._selectedEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(newPositions);
|
|
1469
|
+
|
|
1470
|
+
// 更新控制点
|
|
1471
|
+
this._dragHandle.position = new Cesium.ConstantPositionProperty(newCartesian);
|
|
1472
|
+
|
|
1473
|
+
// 修复:多边形编辑后重新计算并更新中心控制点位置
|
|
1474
|
+
this._updateCenterHandlePosition();
|
|
1475
|
+
|
|
1476
|
+
return;
|
|
1477
|
+
} else if (Cesium.defined(this._selectedEntity.position)) {
|
|
1478
|
+
this._selectedEntity.position = new Cesium.ConstantPositionProperty(newCartesian);
|
|
1479
|
+
this._dragHandle.position = new Cesium.ConstantPositionProperty(newCartesian);
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
_highlightEntity(entity, highlight) {
|
|
1484
|
+
if (!entity) return;
|
|
1485
|
+
|
|
1486
|
+
if (Cesium.defined(entity.point)) {
|
|
1487
|
+
if (highlight) {
|
|
1488
|
+
entity.point.outlineColor = Cesium.Color.CYAN;
|
|
1489
|
+
entity.point.outlineWidth = 3;
|
|
1490
|
+
} else {
|
|
1491
|
+
entity.point.outlineColor = Cesium.Color.WHITE;
|
|
1492
|
+
entity.point.outlineWidth = 1;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
if (Cesium.defined(entity.polyline)) {
|
|
1497
|
+
if (highlight) {
|
|
1498
|
+
entity.polyline.material = Cesium.Color.CYAN;
|
|
1499
|
+
} else {
|
|
1500
|
+
entity.polyline.material = Cesium.Color.BLUE;
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
if (Cesium.defined(entity.polygon)) {
|
|
1505
|
+
if (highlight) {
|
|
1506
|
+
entity.polygon.outlineColor = Cesium.Color.CYAN;
|
|
1507
|
+
} else {
|
|
1508
|
+
entity.polygon.outlineColor = Cesium.Color.WHITE;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* 获取当前选中的图元 ID
|
|
1515
|
+
*/
|
|
1516
|
+
getSelectedEntityId() {
|
|
1517
|
+
return this._selectedEntityId;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/**
|
|
1521
|
+
* 获取编辑前的原始位置(用于撤销)
|
|
1522
|
+
*/
|
|
1523
|
+
getOriginalPositions() {
|
|
1524
|
+
return this._originalPositions;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* 撤销编辑,恢复原始位置
|
|
1529
|
+
*/
|
|
1530
|
+
undo() {
|
|
1531
|
+
if (!this._selectedEntity) return;
|
|
1532
|
+
|
|
1533
|
+
// 圆形特殊处理:恢复圆心和半径
|
|
1534
|
+
if (this._isCircle(this._selectedEntity) && this._originalCircleCenter && this._originalCircleRadius) {
|
|
1535
|
+
const center = this._originalCircleCenter;
|
|
1536
|
+
const radius = this._originalCircleRadius;
|
|
1537
|
+
|
|
1538
|
+
// 恢复圆的实体属性
|
|
1539
|
+
this._selectedEntity._circleCenter = center;
|
|
1540
|
+
this._selectedEntity._circleRadius = radius;
|
|
1541
|
+
|
|
1542
|
+
// 更新多边形顶点
|
|
1543
|
+
const positions = this._generateCirclePositions(center, radius, 256);
|
|
1544
|
+
const flat = positions.flatMap(c => [c[0], c[1], 0]);
|
|
1545
|
+
this._selectedEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(
|
|
1546
|
+
Cesium.Cartesian3.fromDegreesArrayHeights(flat)
|
|
1547
|
+
);
|
|
1548
|
+
|
|
1549
|
+
this._createEditHandles();
|
|
1550
|
+
this._emitEditComplete();
|
|
1551
|
+
return;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// 优先使用矩形原始位置,其次使用一般原始位置
|
|
1555
|
+
const originalPositions = this._rectangleOriginalPositions || this._originalPositions;
|
|
1556
|
+
if (!originalPositions) return;
|
|
1557
|
+
|
|
1558
|
+
// 拷贝一份避免直接修改原始数据
|
|
1559
|
+
const positionsCopy = originalPositions.map(p => p.clone ? p.clone() : p);
|
|
1560
|
+
|
|
1561
|
+
if (Cesium.defined(this._selectedEntity.polyline)) {
|
|
1562
|
+
this._selectedEntity.polyline.positions = positionsCopy;
|
|
1563
|
+
} else if (Cesium.defined(this._selectedEntity.polygon)) {
|
|
1564
|
+
this._selectedEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(positionsCopy);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
this._createEditHandles();
|
|
1568
|
+
this._emitEditComplete();
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
_emitSelect(entityId) {
|
|
1572
|
+
if (this._options.onSelect) {
|
|
1573
|
+
this._options.onSelect({ entityId });
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
_emitEditComplete() {
|
|
1578
|
+
if (this._options.onEditComplete) {
|
|
1579
|
+
this._options.onEditComplete({
|
|
1580
|
+
entityId: this._selectedEntityId,
|
|
1581
|
+
positions: this._getEntityPositions(this._selectedEntity)
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
}
|