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.
@@ -0,0 +1,155 @@
1
+ /**
2
+ * 工具基类
3
+ * 所有编辑工具的抽象基类
4
+ */
5
+ export class BaseTool {
6
+ constructor(editorModule, options = {}) {
7
+ this._editor = editorModule;
8
+ this._viewer = editorModule.viewer;
9
+ this._graphicModule = editorModule.graphicModule;
10
+ this._eventModule = editorModule.eventModule;
11
+ this._options = options;
12
+ this._isActive = false;
13
+ this._handlers = []; // 注册的事件处理器 ID
14
+ }
15
+
16
+ /**
17
+ * 激活工具
18
+ */
19
+ activate() {
20
+ if (this._isActive) return;
21
+ this._isActive = true;
22
+ this.onActivate();
23
+ }
24
+
25
+ /**
26
+ * 停用工具
27
+ */
28
+ deactivate() {
29
+ if (!this._isActive) return;
30
+ this._isActive = false;
31
+ this.onDeactivate();
32
+ this.clearHandlers();
33
+ }
34
+
35
+ /**
36
+ * 激活时的回调,子类重写
37
+ */
38
+ onActivate() { }
39
+
40
+ /**
41
+ * 停用时的回调,子类重写
42
+ */
43
+ onDeactivate() { }
44
+
45
+ /**
46
+ * 注册事件观察者
47
+ * @param {string} eventType 事件类型
48
+ * @param {Function} callback 回调函数
49
+ * @returns {string} 观察者 ID
50
+ */
51
+ registerObserver(eventType, callback) {
52
+ const observerId = this._eventModule.registerObserver(eventType, (lngLat, entity, movement) => {
53
+ // mouse:move 事件没有 lngLat,直接传递 movement 对象
54
+ if (eventType === 'mouse:move') {
55
+ callback(movement);
56
+ } else {
57
+ callback(lngLat, entity, movement);
58
+ }
59
+ });
60
+ this._handlers.push({ eventType, observerId });
61
+ return observerId;
62
+ }
63
+
64
+ /**
65
+ * 清除所有已注册的事件处理器
66
+ */
67
+ clearHandlers() {
68
+ this._handlers.forEach(({ eventType, observerId }) => {
69
+ this._eventModule.unregisterObserver(eventType, observerId);
70
+ });
71
+ this._handlers = [];
72
+ }
73
+
74
+ /**
75
+ * 获取屏幕坐标对应的地理坐标
76
+ * @param {object} screenPos 屏幕坐标 {x, y}
77
+ * @returns {Array} 经纬度高度 [lon, lat, height]
78
+ */
79
+ screenToLonLat(screenPos) {
80
+ if (!screenPos || screenPos.x == null || screenPos.y == null) return null;
81
+
82
+ let cartesian = null;
83
+
84
+ // 优先尝试拾取地形/物体表面位置
85
+ try {
86
+ cartesian = this._viewer.scene.pickPosition(screenPos);
87
+ } catch (e) {
88
+ // ignore
89
+ }
90
+
91
+ // 如果拾取失败(空白区域),fallback 到椭球体表面
92
+ if (!cartesian) {
93
+ cartesian = this._viewer.camera.pickEllipsoid(
94
+ new Cesium.Cartesian3(screenPos.x, screenPos.y),
95
+ this._viewer.scene.globe.ellipsoid
96
+ );
97
+ }
98
+
99
+ if (!cartesian) return null;
100
+
101
+ const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
102
+ return [
103
+ Cesium.Math.toDegrees(cartographic.longitude),
104
+ Cesium.Math.toDegrees(cartographic.latitude),
105
+ cartographic.height
106
+ ];
107
+ }
108
+
109
+ /**
110
+ * 获取地形高度
111
+ * @param {Array} lonLat 经纬度 [lon, lat]
112
+ * @returns {Promise<number>} 地形高度
113
+ */
114
+ async getTerrainHeight(lonLat) {
115
+ const cartographic = Cesium.Cartographic.fromDegrees(lonLat[0], lonLat[1]);
116
+ const result = await Cesium.sampleTerrainMostDetailed(
117
+ this._viewer.terrainProvider,
118
+ [cartographic]
119
+ );
120
+ return result[0].height;
121
+ }
122
+
123
+ /**
124
+ * 创建临时图元(不被 GraphicModule 管理,工具内部使用)
125
+ * @param {string} id 临时 ID
126
+ * @param {object} options 图元配置
127
+ * @returns {Cesium.Entity}
128
+ */
129
+ createTempEntity(id, options) {
130
+ const entity = this._viewer.entities.add({
131
+ id: `${id}`,
132
+ ...options
133
+ });
134
+ return entity;
135
+ }
136
+
137
+ /**
138
+ * 移除临时图元
139
+ * @param {Cesium.Entity} entity
140
+ */
141
+ removeTempEntity(entity) {
142
+ if (entity) {
143
+ this._viewer.entities.remove(entity);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * 获取唯一 ID
149
+ * @param {string} prefix 前缀
150
+ * @returns {string}
151
+ */
152
+ generateId(prefix = 'tool') {
153
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
154
+ }
155
+ }
@@ -0,0 +1,252 @@
1
+ import { BaseTool } from './BaseTool.js';
2
+
3
+ /**
4
+ * 删除模式枚举
5
+ */
6
+ export const DeleteMode = {
7
+ SINGLE: 'single', // 单个删除(点击删除)
8
+ SELECTED: 'selected' // 删除已选择的图元
9
+ };
10
+
11
+ /**
12
+ * 删除工具
13
+ * 支持单个删除和批量删除已选择的图元
14
+ */
15
+ export class DeleteTool extends BaseTool {
16
+ constructor(editorModule, options = {}) {
17
+ super(editorModule, options);
18
+ this._deleteMode = options.deleteMode || DeleteMode.SINGLE;
19
+ this._hoveredEntity = null;
20
+ this._hoverEntity = null; // 悬停高亮
21
+ this._originalStyles = new Map(); // 记录悬停前的原始样式
22
+ }
23
+
24
+ /**
25
+ * 设置删除模式
26
+ * @param {string} mode DeleteMode
27
+ */
28
+ setDeleteMode(mode) {
29
+ this._deleteMode = mode;
30
+ return this;
31
+ }
32
+
33
+ getDeleteMode() {
34
+ return this._deleteMode;
35
+ }
36
+
37
+ onActivate() {
38
+ this.registerObserver('mouse:click', this._onClick.bind(this));
39
+ this.registerObserver('mouse:move', this._onMove.bind(this));
40
+ }
41
+
42
+ onDeactivate() {
43
+ this._clearHoverHighlight();
44
+ }
45
+
46
+ _onClick(_lngLat, _entity, movement) {
47
+ const screenPos = movement.position || (movement && movement.endPosition);
48
+ if (!screenPos) return;
49
+
50
+ const picked = this._viewer.scene.pick({ x: screenPos.x, y: screenPos.y });
51
+
52
+ if (Cesium.defined(picked) && Cesium.defined(picked.id)) {
53
+ const entityId = picked.id.id;
54
+
55
+ // 忽略临时图元
56
+ if (entityId && entityId.startsWith('__temp_')) return;
57
+ if (entityId && entityId.startsWith('__edit_handle_')) return;
58
+
59
+ // 忽略编辑器自身的控制点
60
+ if (entityId && entityId.startsWith('__sel_')) return;
61
+
62
+ if (this._deleteMode === DeleteMode.SINGLE) {
63
+ this._deleteSingle(entityId);
64
+ }
65
+ }
66
+ }
67
+
68
+ _onMove(data) {
69
+ const screenPos = data.position || data.endPosition;
70
+ if (!screenPos) return;
71
+
72
+ const picked = this._viewer.scene.pick({ x: screenPos.x, y: screenPos.y });
73
+
74
+ let hoveredId = null;
75
+
76
+ if (Cesium.defined(picked) && Cesium.defined(picked.id)) {
77
+ const entityId = picked.id.id;
78
+ if (!entityId.startsWith('__temp_') && !entityId.startsWith('__edit_handle_')) {
79
+ hoveredId = entityId;
80
+ }
81
+ }
82
+
83
+ if (hoveredId !== this._hoveredEntity) {
84
+ this._clearHoverHighlight();
85
+ this._hoveredEntity = hoveredId;
86
+
87
+ if (hoveredId) {
88
+ this._highlightAsDeletable(hoveredId);
89
+ }
90
+ }
91
+ }
92
+
93
+ _deleteSingle(entityId) {
94
+ const entity = this._graphicModule.getGraphic(entityId);
95
+ if (!entity) return;
96
+
97
+ // 发送删除前事件(可撤销)
98
+ this._emitBeforeDelete({
99
+ entityId,
100
+ entity: entity
101
+ });
102
+
103
+ // 执行删除
104
+ this._graphicModule.removeGraphic(entityId);
105
+
106
+ // 发送删除完成事件
107
+ this._emitDelete({
108
+ entityId
109
+ });
110
+ }
111
+
112
+ /**
113
+ * 删除多个图元
114
+ * @param {string[]} entityIds 图元 ID 数组
115
+ */
116
+ deleteMultiple(entityIds) {
117
+ const deleted = [];
118
+ const failed = [];
119
+
120
+ entityIds.forEach(id => {
121
+ const success = this._graphicModule.removeGraphic(id);
122
+ if (success) {
123
+ deleted.push(id);
124
+ } else {
125
+ failed.push(id);
126
+ }
127
+ });
128
+
129
+ this._emitDeleteMultiple({ deleted, failed });
130
+
131
+ return { deleted, failed };
132
+ }
133
+
134
+ /**
135
+ * 删除所有图元
136
+ */
137
+ deleteAll() {
138
+ const allIds = [];
139
+ this._graphicModule.graphicMap.forEach((entity, id) => {
140
+ if (!id.startsWith('__temp_') && !id.startsWith('__edit_handle_')) {
141
+ allIds.push(id);
142
+ }
143
+ });
144
+
145
+ return this.deleteMultiple(allIds);
146
+ }
147
+
148
+ _highlightAsDeletable(entityId) {
149
+ const entity = this._graphicModule.getGraphic(entityId);
150
+ if (!entity) return;
151
+
152
+ this._hoverEntity = entity;
153
+ const styleKey = entityId;
154
+
155
+ // 红色高亮表示可删除,先保存原始样式
156
+ if (Cesium.defined(entity.point)) {
157
+ if (!this._originalStyles.has(styleKey)) {
158
+ this._originalStyles.set(styleKey, {
159
+ point: {
160
+ outlineColor: entity.point.outlineColor,
161
+ outlineWidth: entity.point.outlineWidth
162
+ }
163
+ });
164
+ }
165
+ entity.point.outlineColor = Cesium.Color.RED;
166
+ entity.point.outlineWidth = 3;
167
+ }
168
+
169
+ if (Cesium.defined(entity.polyline)) {
170
+ if (!this._originalStyles.has(styleKey)) {
171
+ this._originalStyles.set(styleKey, {
172
+ polyline: {
173
+ material: entity.polyline.material
174
+ }
175
+ });
176
+ }
177
+ entity.polyline.material = Cesium.Color.RED;
178
+ }
179
+
180
+ if (Cesium.defined(entity.polygon)) {
181
+ if (!this._originalStyles.has(styleKey)) {
182
+ this._originalStyles.set(styleKey, {
183
+ polygon: {
184
+ outlineColor: entity.polygon.outlineColor
185
+ }
186
+ });
187
+ }
188
+ entity.polygon.outlineColor = Cesium.Color.RED;
189
+ }
190
+
191
+ // 更改鼠标样式
192
+ if (this._viewer.container) {
193
+ this._viewer.container.style.cursor = 'pointer';
194
+ }
195
+ }
196
+
197
+ _clearHoverHighlight() {
198
+ if (!this._hoverEntity) return;
199
+
200
+ const entity = this._hoverEntity;
201
+ const styleKey = this._hoveredEntity;
202
+ const original = styleKey ? this._originalStyles.get(styleKey) : null;
203
+
204
+ // 恢复原始样式
205
+ if (Cesium.defined(entity.point)) {
206
+ if (original?.point) {
207
+ entity.point.outlineColor = original.point.outlineColor;
208
+ entity.point.outlineWidth = original.point.outlineWidth;
209
+ } else {
210
+ entity.point.outlineColor = Cesium.Color.WHITE;
211
+ entity.point.outlineWidth = 1;
212
+ }
213
+ }
214
+
215
+ if (Cesium.defined(entity.polyline)) {
216
+ entity.polyline.material = original?.polyline?.material || Cesium.Color.BLUE;
217
+ }
218
+
219
+ if (Cesium.defined(entity.polygon)) {
220
+ entity.polygon.outlineColor = original?.polygon?.outlineColor || Cesium.Color.WHITE;
221
+ }
222
+
223
+ // 恢复鼠标样式
224
+ if (this._viewer.container) {
225
+ this._viewer.container.style.cursor = 'default';
226
+ }
227
+
228
+ if (styleKey) {
229
+ this._originalStyles.delete(styleKey);
230
+ }
231
+ this._hoverEntity = null;
232
+ this._hoveredEntity = null;
233
+ }
234
+
235
+ _emitBeforeDelete(data) {
236
+ if (this._options.onBeforeDelete) {
237
+ this._options.onBeforeDelete(data);
238
+ }
239
+ }
240
+
241
+ _emitDelete(data) {
242
+ if (this._options.onDelete) {
243
+ this._options.onDelete(data);
244
+ }
245
+ }
246
+
247
+ _emitDeleteMultiple(data) {
248
+ if (this._options.onDeleteMultiple) {
249
+ this._options.onDeleteMultiple(data);
250
+ }
251
+ }
252
+ }