cesium-xs-sdk 1.0.12 → 1.0.14

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.
@@ -9198,17 +9198,38 @@ class ControlModule {
9198
9198
  }
9199
9199
  }
9200
9200
 
9201
- const toDataUri = (svg) => `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
9202
-
9203
- /**
9204
- * 军标内置默认图标。
9205
- * SDK 内置常见军标类型的默认图标,业务层可通过 imgUrl / useDefaultIcon 参数覆盖或追加。
9206
- */
9207
- const DEFAULT_SYMBOL_IMAGES = {
9208
- 'small-radio': toDataUri(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80"><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><circle cx="36.93" cy="48.5" r="22" style="fill:none;stroke:red;stroke-miterlimit:10;stroke-width:4.535433070866142px"/><polyline points="42.56 25.87 53.06 9.51 54.06 27.5 65.07 10.37" style="fill:none;stroke:red;stroke-linejoin:bevel;stroke-width:4.535433070866142px"/><rect width="80" height="80" style="fill:red;opacity:0"/></g></g></svg>`),
9209
- 'large-radio': toDataUri(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80"><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><polygon points="39.96 28.87 51.18 48.3 62.4 67.73 39.96 67.73 17.53 67.73 28.75 48.3 39.96 28.87" style="fill:none;stroke:red;stroke-miterlimit:10;stroke-width:4.535433070866142px"/><polyline points="39.96 28.64 50.46 12.27 51.46 30.27 62.47 13.14" style="fill:none;stroke:red;stroke-linejoin:bevel;stroke-width:4.535433070866142px"/><rect width="80" height="80" style="fill:red;opacity:0"/></g></g></svg>`),
9201
+ /**
9202
+ * 军标内置默认图标。
9203
+ * SDK 内置常见军标类型的默认图标,业务层可通过 imgUrl / useDefaultIcon 参数覆盖或追加。
9204
+ */
9205
+ const DEFAULT_SYMBOL_SVGS = {
9206
+ 'small-radio': `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80"><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><circle cx="36.93" cy="48.5" r="22" style="fill:none;stroke:red;stroke-miterlimit:10;stroke-width:4.535433070866142px"/><polyline points="42.56 25.87 53.06 9.51 54.06 27.5 65.07 10.37" style="fill:none;stroke:red;stroke-linejoin:bevel;stroke-width:4.535433070866142px"/><rect width="80" height="80" style="fill:red;opacity:0"/></g></g></svg>`,
9207
+ 'large-radio': `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" width="80" height="80"><g id="图层_2" data-name="图层 2"><g id="图层_1-2" data-name="图层 1"><polygon points="39.96 28.87 51.18 48.3 62.4 67.73 39.96 67.73 17.53 67.73 28.75 48.3 39.96 28.87" style="fill:none;stroke:red;stroke-miterlimit:10;stroke-width:4.535433070866142px"/><polyline points="39.96 28.64 50.46 12.27 51.46 30.27 62.47 13.14" style="fill:none;stroke:red;stroke-linejoin:bevel;stroke-width:4.535433070866142px"/><rect width="80" height="80" style="fill:red;opacity:0"/></g></g></svg>`,
9210
9208
  };
9211
9209
 
9210
+ /** Blob URL 缓存,避免重复创建 */
9211
+ const blobUrlCache = new Map();
9212
+
9213
+ /**
9214
+ * 获取内置军标图标的 Blob URL(比 data URI 更利于 Cesium 材质加载)
9215
+ * @param {string} type 军标类型
9216
+ * @returns {string | null}
9217
+ */
9218
+ function getDefaultSymbolImage(type) {
9219
+ const svg = DEFAULT_SYMBOL_SVGS[type];
9220
+ if (!svg) return null;
9221
+ if (blobUrlCache.has(type)) return blobUrlCache.get(type);
9222
+ try {
9223
+ const blob = new Blob([svg], { type: 'image/svg+xml;charset=utf-8' });
9224
+ const url = URL.createObjectURL(blob);
9225
+ blobUrlCache.set(type, url);
9226
+ return url;
9227
+ } catch (e) {
9228
+ console.warn('生成军标图标 Blob URL 失败', e);
9229
+ return null;
9230
+ }
9231
+ }
9232
+
9212
9233
  const Cesium$2 = getCesium();
9213
9234
 
9214
9235
  /**
@@ -9227,10 +9248,21 @@ class MilitarySymbolModule {
9227
9248
  this.eventModule = null;
9228
9249
 
9229
9250
  this._editMode = false;
9251
+ this._editSymbolId = null;
9252
+ /** @type {Map<string, {move: Cesium.Entity, scale: Cesium.Entity, rotate: Cesium.Entity}>} */
9253
+ this._editHandles = new Map();
9230
9254
  this._draggingId = null;
9255
+ this._draggingHandle = null;
9256
+ this._dragStartScreen = null;
9257
+ this._dragStartScale = 1;
9258
+ this._dragStartRotation = 0;
9259
+ this._dragStartDist = 0;
9260
+ this._dragStartAngle = 0;
9231
9261
  this._dragObserverIds = [];
9232
9262
  /** @type {Set<Function>} */
9233
9263
  this._positionChangedCallbacks = new Set();
9264
+ /** @type {Set<Function>} */
9265
+ this._transformChangedCallbacks = new Set();
9234
9266
  }
9235
9267
 
9236
9268
  /**
@@ -9287,7 +9319,7 @@ class MilitarySymbolModule {
9287
9319
  _resolveImage(id, options) {
9288
9320
  const globalMode = this._defaultIconMode || this.config.militarySymbol?.defaultIconMode;
9289
9321
  const mode = options.iconMode || globalMode || 'default';
9290
- const defaultImage = DEFAULT_SYMBOL_IMAGES[options.type];
9322
+ const defaultImage = getDefaultSymbolImage(options.type);
9291
9323
  const registeredImage = this.defaultImageMap.get(options.type);
9292
9324
  const imgUrl = options.imgUrl || registeredImage || null;
9293
9325
 
@@ -9330,7 +9362,10 @@ class MilitarySymbolModule {
9330
9362
  _loadImage(url) {
9331
9363
  return new Promise((resolve, reject) => {
9332
9364
  const img = new Image();
9333
- img.crossOrigin = 'anonymous';
9365
+ // data URI / blob URL 等同源资源不需要 crossOrigin;跨域 http 资源需要
9366
+ if (url && /^https?:\/\//.test(url)) {
9367
+ img.crossOrigin = 'anonymous';
9368
+ }
9334
9369
  img.onload = () => resolve(img);
9335
9370
  img.onerror = (e) => reject(e);
9336
9371
  img.src = url;
@@ -9384,6 +9419,131 @@ class MilitarySymbolModule {
9384
9419
  return this.symbolMap.has(id) ? id : null;
9385
9420
  }
9386
9421
 
9422
+ /**
9423
+ * 根据鼠标拾取结果判断是否为编辑控制点
9424
+ * @private
9425
+ */
9426
+ _getHandleType(pickedEntity) {
9427
+ if (!pickedEntity) return null;
9428
+ const id = typeof pickedEntity.id === 'string' ? pickedEntity.id : pickedEntity.id?.id;
9429
+ if (!id) return null;
9430
+ if (id.endsWith('_move')) return 'move';
9431
+ if (id.endsWith('_scale')) return 'scale';
9432
+ if (id.endsWith('_rotate')) return 'rotate';
9433
+ return null;
9434
+ }
9435
+
9436
+ /**
9437
+ * 计算编辑控制点的三维坐标
9438
+ * @private
9439
+ */
9440
+ _getHandlePositions(position) {
9441
+ const center = Cesium$2.Cartesian3.fromDegrees(position[0], position[1], position[2] || 0);
9442
+ const matrix = Cesium$2.Transforms.eastNorthUpToFixedFrame(center);
9443
+ const offsetMeters = 120;
9444
+ const scaleOffset = Cesium$2.Matrix4.multiplyByPoint(
9445
+ matrix,
9446
+ new Cesium$2.Cartesian3(offsetMeters, 0, 0),
9447
+ new Cesium$2.Cartesian3()
9448
+ );
9449
+ const rotateOffset = Cesium$2.Matrix4.multiplyByPoint(
9450
+ matrix,
9451
+ new Cesium$2.Cartesian3(0, offsetMeters, 0),
9452
+ new Cesium$2.Cartesian3()
9453
+ );
9454
+ return { center, scale: scaleOffset, rotate: rotateOffset };
9455
+ }
9456
+
9457
+ /**
9458
+ * 为指定军标创建编辑控制点
9459
+ * @private
9460
+ */
9461
+ _createEditHandles(symbolId) {
9462
+ this._removeEditHandles(symbolId);
9463
+ const symbol = this.symbolMap.get(symbolId);
9464
+ if (!symbol) return;
9465
+
9466
+ const positions = this._getHandlePositions(symbol.metadata.position);
9467
+ const commonPoint = {
9468
+ pixelSize: 14,
9469
+ outlineColor: Cesium$2.Color.WHITE,
9470
+ outlineWidth: 2,
9471
+ heightReference: Cesium$2.HeightReference.CLAMP_TO_GROUND,
9472
+ };
9473
+ const commonLabel = {
9474
+ font: '12px Microsoft YaHei',
9475
+ fillColor: Cesium$2.Color.WHITE,
9476
+ outlineColor: Cesium$2.Color.BLACK,
9477
+ outlineWidth: 2,
9478
+ style: Cesium$2.LabelStyle.FILL_AND_OUTLINE,
9479
+ verticalOrigin: Cesium$2.VerticalOrigin.BOTTOM,
9480
+ horizontalOrigin: Cesium$2.HorizontalOrigin.CENTER,
9481
+ pixelOffset: new Cesium$2.Cartesian2(0, -10),
9482
+ heightReference: Cesium$2.HeightReference.CLAMP_TO_GROUND,
9483
+ };
9484
+
9485
+ const handles = {};
9486
+ handles.move = this.viewer.entities.add({
9487
+ id: `${symbolId}_move`,
9488
+ position: positions.center,
9489
+ point: { ...commonPoint, color: Cesium$2.Color.LIME },
9490
+ label: { ...commonLabel, text: '移动' },
9491
+ });
9492
+ handles.scale = this.viewer.entities.add({
9493
+ id: `${symbolId}_scale`,
9494
+ position: positions.scale,
9495
+ point: { ...commonPoint, color: Cesium$2.Color.DEEPSKYBLUE },
9496
+ label: { ...commonLabel, text: '缩放' },
9497
+ });
9498
+ handles.rotate = this.viewer.entities.add({
9499
+ id: `${symbolId}_rotate`,
9500
+ position: positions.rotate,
9501
+ point: { ...commonPoint, color: Cesium$2.Color.RED },
9502
+ label: { ...commonLabel, text: '旋转' },
9503
+ });
9504
+
9505
+ this._editHandles.set(symbolId, handles);
9506
+ }
9507
+
9508
+ /**
9509
+ * 移除指定军标的编辑控制点
9510
+ * @private
9511
+ */
9512
+ _removeEditHandles(symbolId) {
9513
+ const handles = this._editHandles.get(symbolId);
9514
+ if (!handles) return;
9515
+ Object.values(handles).forEach((entity) => {
9516
+ if (entity) this.viewer.entities.remove(entity);
9517
+ });
9518
+ this._editHandles.delete(symbolId);
9519
+ }
9520
+
9521
+ /**
9522
+ * 更新控制点位置以跟随军标
9523
+ * @private
9524
+ */
9525
+ _updateHandlePositions(symbolId) {
9526
+ const symbol = this.symbolMap.get(symbolId);
9527
+ const handles = this._editHandles.get(symbolId);
9528
+ if (!symbol || !handles) return;
9529
+ const positions = this._getHandlePositions(symbol.metadata.position);
9530
+ handles.move.position = positions.center;
9531
+ handles.scale.position = positions.scale;
9532
+ handles.rotate.position = positions.rotate;
9533
+ }
9534
+
9535
+ /**
9536
+ * 将 WGS84 坐标转换为屏幕坐标
9537
+ * @private
9538
+ */
9539
+ _cartesianToScreen(cartesian) {
9540
+ if (!cartesian) return null;
9541
+ try {
9542
+ return Cesium$2.SceneTransforms.wgs84ToWindowCoordinates(this.viewer.scene, cartesian);
9543
+ } catch (e) {
9544
+ return null;
9545
+ }
9546
+ }
9387
9547
  /**
9388
9548
  * 创建军标图元
9389
9549
  * @param {string} id 军标唯一ID
@@ -9394,6 +9554,7 @@ class MilitarySymbolModule {
9394
9554
  * @param {string} [options.imgUrl] 自定义图标 URL
9395
9555
  * @param {'default'|'custom'|'overlay'} [options.iconMode] 图标模式,默认 'default'
9396
9556
  * @param {number} [options.scale] billboard 缩放,默认 0.6
9557
+ * @param {number} [options.rotation] billboard 旋转角度(度),默认 0
9397
9558
  * @param {boolean} [options.visible] 是否可见,默认 true
9398
9559
  * @returns {{id: string, entity: Cesium.Entity, metadata: object}}
9399
9560
  */
@@ -9406,6 +9567,7 @@ class MilitarySymbolModule {
9406
9567
  const position = options.position || [0, 0, 0];
9407
9568
  const image = this._resolveImage(id, options);
9408
9569
  const scale = options.scale ?? 0.6;
9570
+ const rotation = options.rotation ?? 0;
9409
9571
  const visible = options.visible !== false;
9410
9572
  const name = options.name ?? id;
9411
9573
  const iconMode = options.iconMode || 'default';
@@ -9416,10 +9578,10 @@ class MilitarySymbolModule {
9416
9578
  billboard: {
9417
9579
  image,
9418
9580
  scale,
9581
+ rotation: Cesium$2.Math.toRadians(rotation),
9419
9582
  verticalOrigin: Cesium$2.VerticalOrigin.BOTTOM,
9420
9583
  horizontalOrigin: Cesium$2.HorizontalOrigin.CENTER,
9421
9584
  heightReference: Cesium$2.HeightReference.CLAMP_TO_GROUND,
9422
- disableDepthTestDistance: 100000,
9423
9585
  },
9424
9586
  label: {
9425
9587
  text: name,
@@ -9432,7 +9594,6 @@ class MilitarySymbolModule {
9432
9594
  horizontalOrigin: Cesium$2.HorizontalOrigin.CENTER,
9433
9595
  pixelOffset: new Cesium$2.Cartesian2(0, 4),
9434
9596
  heightReference: Cesium$2.HeightReference.CLAMP_TO_GROUND,
9435
- disableDepthTestDistance: 100000,
9436
9597
  show: true,
9437
9598
  },
9438
9599
  show: visible,
@@ -9444,6 +9605,7 @@ class MilitarySymbolModule {
9444
9605
  name,
9445
9606
  position: [position[0], position[1], position[2] || 0],
9446
9607
  scale,
9608
+ rotation,
9447
9609
  visible,
9448
9610
  iconMode,
9449
9611
  imgUrl: options.imgUrl || null,
@@ -9465,6 +9627,10 @@ class MilitarySymbolModule {
9465
9627
  return false;
9466
9628
  }
9467
9629
  this.viewer.entities.remove(symbol.entity);
9630
+ this._removeEditHandles(id);
9631
+ if (this._editSymbolId === id) {
9632
+ this.disableEditMode();
9633
+ }
9468
9634
  this.symbolMap.delete(id);
9469
9635
  if (this._draggingId === id) {
9470
9636
  this._draggingId = null;
@@ -9477,6 +9643,11 @@ class MilitarySymbolModule {
9477
9643
  * 删除所有军标
9478
9644
  */
9479
9645
  removeAll() {
9646
+ this._editHandles.forEach((_, id) => this._removeEditHandles(id));
9647
+ this._editHandles.clear();
9648
+ if (this._editMode) {
9649
+ this.disableEditMode();
9650
+ }
9480
9651
  this.symbolMap.forEach((symbol) => {
9481
9652
  this.viewer.entities.remove(symbol.entity);
9482
9653
  });
@@ -9488,7 +9659,7 @@ class MilitarySymbolModule {
9488
9659
  /**
9489
9660
  * 更新军标属性
9490
9661
  * @param {string} id 军标ID
9491
- * @param {Object} fields 支持 position / name / scale / imgUrl / visible
9662
+ * @param {Object} fields 支持 position / name / scale / rotation / imgUrl / visible
9492
9663
  * @returns {boolean} 是否更新成功
9493
9664
  */
9494
9665
  update(id, fields = {}) {
@@ -9516,6 +9687,11 @@ class MilitarySymbolModule {
9516
9687
  metadata.scale = fields.scale;
9517
9688
  }
9518
9689
 
9690
+ if (fields.rotation !== undefined) {
9691
+ entity.billboard.rotation = Cesium$2.Math.toRadians(fields.rotation);
9692
+ metadata.rotation = fields.rotation;
9693
+ }
9694
+
9519
9695
  if (fields.imgUrl !== undefined || fields.iconMode !== undefined) {
9520
9696
  const newIconMode = fields.iconMode || metadata.iconMode;
9521
9697
  const newImgUrl = fields.imgUrl !== undefined ? fields.imgUrl : metadata.imgUrl;
@@ -9595,13 +9771,14 @@ class MilitarySymbolModule {
9595
9771
  const pitch = options.pitch ?? -Cesium$2.Math.PI_OVER_TWO;
9596
9772
  const range = options.range ?? 5000;
9597
9773
 
9598
- return this.viewer.camera.flyTo({
9599
- destination: Cesium$2.Cartesian3.fromDegrees(position[0], position[1], (position[2] || 0) + range),
9600
- orientation: { heading: 0, pitch, roll: 0 },
9601
- duration,
9602
- }).then(() => true).catch((err) => {
9603
- console.error('军标定位失败', err);
9604
- return false;
9774
+ return new Promise((resolve) => {
9775
+ this.viewer.camera.flyTo({
9776
+ destination: Cesium$2.Cartesian3.fromDegrees(position[0], position[1], (position[2] || 0) + range),
9777
+ orientation: { heading: 0, pitch, roll: 0 },
9778
+ duration,
9779
+ complete: () => resolve(true),
9780
+ cancel: () => resolve(false),
9781
+ });
9605
9782
  });
9606
9783
  }
9607
9784
 
@@ -9627,49 +9804,115 @@ class MilitarySymbolModule {
9627
9804
  }
9628
9805
 
9629
9806
  /**
9630
- * 启用拖拽编辑模式
9631
- * 鼠标在军标上按下即可拖拽,拖拽结束后触发 onPositionChanged 回调。
9807
+ * 启用军标编辑模式(生成移动/缩放/旋转控制点)
9808
+ * @param {string} id 要编辑的军标ID
9632
9809
  */
9633
- enableEditMode() {
9634
- if (this._editMode) return;
9810
+ enableEditMode(id) {
9811
+ if (this._editMode) {
9812
+ if (this._editSymbolId === id) return;
9813
+ this.disableEditMode();
9814
+ }
9815
+ if (!this.symbolMap.has(id)) {
9816
+ console.warn(`军标 ${id} 不存在,无法启用编辑`);
9817
+ return;
9818
+ }
9635
9819
  if (!this.eventModule) {
9636
- console.warn('MilitarySymbolModule 未注入 EventModule,无法启用拖拽编辑');
9820
+ console.warn('MilitarySymbolModule 未注入 EventModule,无法启用编辑');
9637
9821
  return;
9638
9822
  }
9639
9823
 
9640
9824
  this._editMode = true;
9825
+ this._editSymbolId = id;
9826
+ this._createEditHandles(id);
9827
+
9828
+ const downId = this.eventModule.registerObserver('mouse:down', (lngLat, pickedEntity, movement) => {
9829
+ const handleType = this._getHandleType(pickedEntity);
9830
+ let symbolId = null;
9831
+ if (handleType) {
9832
+ symbolId = this._editSymbolId;
9833
+ } else {
9834
+ symbolId = this._getSymbolIdFromPick(pickedEntity);
9835
+ }
9836
+ if (!symbolId || symbolId !== this._editSymbolId) return;
9837
+
9838
+ const symbol = this.symbolMap.get(symbolId);
9839
+ const centerScreen = this._cartesianToScreen(symbol.entity.position.getValue(Cesium$2.JulianDate.now()));
9840
+ const mouseScreen = movement.position;
9841
+ if (centerScreen && mouseScreen) {
9842
+ const dx = mouseScreen.x - centerScreen.x;
9843
+ const dy = mouseScreen.y - centerScreen.y;
9844
+ this._dragStartDist = Math.hypot(dx, dy);
9845
+ this._dragStartAngle = Math.atan2(dx, -dy);
9846
+ } else {
9847
+ this._dragStartDist = 0;
9848
+ this._dragStartAngle = 0;
9849
+ }
9641
9850
 
9642
- const downId = this.eventModule.registerObserver('mouse:down', (lngLat, pickedEntity) => {
9643
- const symbolId = this._getSymbolIdFromPick(pickedEntity);
9644
- if (!symbolId) return;
9645
9851
  this._draggingId = symbolId;
9852
+ this._draggingHandle = handleType || 'move';
9853
+ this._dragStartScale = symbol.metadata.scale;
9854
+ this._dragStartRotation = symbol.metadata.rotation || 0;
9646
9855
  this.viewer.scene.screenSpaceCameraController.enableInputs = false;
9647
9856
  });
9648
9857
 
9649
9858
  const moveId = this.eventModule.registerObserver('mouse:move', (lngLat, pickedEntity, movement) => {
9650
9859
  if (!this._draggingId || !movement?.endPosition) return;
9651
9860
 
9652
- const cartesian = this.viewer.scene.pickPosition(movement.endPosition)
9653
- || this.viewer.scene.camera.pickEllipsoid(movement.endPosition, this.viewer.scene.globe.ellipsoid);
9654
- if (!cartesian) return;
9655
-
9656
- const carto = Cesium$2.Cartographic.fromCartesian(cartesian);
9657
- const position = [
9658
- Cesium$2.Math.toDegrees(carto.longitude),
9659
- Cesium$2.Math.toDegrees(carto.latitude),
9660
- carto.height,
9661
- ];
9662
- this.update(this._draggingId, { position });
9861
+ const symbol = this.symbolMap.get(this._draggingId);
9862
+ if (!symbol) return;
9863
+
9864
+ if (this._draggingHandle === 'move') {
9865
+ const cartesian = this.viewer.scene.pickPosition(movement.endPosition)
9866
+ || this.viewer.scene.camera.pickEllipsoid(movement.endPosition, this.viewer.scene.globe.ellipsoid);
9867
+ if (!cartesian) return;
9868
+
9869
+ const carto = Cesium$2.Cartographic.fromCartesian(cartesian);
9870
+ const position = [
9871
+ Cesium$2.Math.toDegrees(carto.longitude),
9872
+ Cesium$2.Math.toDegrees(carto.latitude),
9873
+ carto.height,
9874
+ ];
9875
+ this.update(this._draggingId, { position });
9876
+ this._updateHandlePositions(this._draggingId);
9877
+ } else if (this._draggingHandle === 'scale') {
9878
+ const centerScreen = this._cartesianToScreen(symbol.entity.position.getValue(Cesium$2.JulianDate.now()));
9879
+ if (!centerScreen) return;
9880
+ const dx = movement.endPosition.x - centerScreen.x;
9881
+ const dy = movement.endPosition.y - centerScreen.y;
9882
+ const dist = Math.hypot(dx, dy);
9883
+ const ratio = this._dragStartDist > 0 ? dist / this._dragStartDist : 1;
9884
+ const newScale = Math.max(0.1, this._dragStartScale * ratio);
9885
+ this.update(this._draggingId, { scale: newScale });
9886
+ } else if (this._draggingHandle === 'rotate') {
9887
+ const centerScreen = this._cartesianToScreen(symbol.entity.position.getValue(Cesium$2.JulianDate.now()));
9888
+ if (!centerScreen) return;
9889
+ const dx = movement.endPosition.x - centerScreen.x;
9890
+ const dy = movement.endPosition.y - centerScreen.y;
9891
+ const angle = Math.atan2(dx, -dy);
9892
+ const deltaDeg = Cesium$2.Math.toDegrees(angle - this._dragStartAngle);
9893
+ let newRotation = this._dragStartRotation + deltaDeg;
9894
+ newRotation = ((newRotation % 360) + 360) % 360;
9895
+ this.update(this._draggingId, { rotation: newRotation });
9896
+ }
9663
9897
  });
9664
9898
 
9665
9899
  const upId = this.eventModule.registerObserver('mouse:up', () => {
9666
9900
  if (!this._draggingId) return;
9667
9901
  const symbol = this.symbolMap.get(this._draggingId);
9668
- const payload = symbol ? { id: this._draggingId, position: [...symbol.metadata.position] } : null;
9902
+ const payload = symbol
9903
+ ? {
9904
+ id: this._draggingId,
9905
+ position: [...symbol.metadata.position],
9906
+ scale: symbol.metadata.scale,
9907
+ rotation: symbol.metadata.rotation,
9908
+ }
9909
+ : null;
9669
9910
  this._draggingId = null;
9911
+ this._draggingHandle = null;
9670
9912
  this.viewer.scene.screenSpaceCameraController.enableInputs = true;
9671
9913
  if (payload) {
9672
- this._positionChangedCallbacks.forEach((callback) => callback(payload));
9914
+ this._positionChangedCallbacks.forEach((callback) => callback({ id: payload.id, position: payload.position }));
9915
+ this._transformChangedCallbacks.forEach((callback) => callback(payload));
9673
9916
  }
9674
9917
  });
9675
9918
 
@@ -9677,13 +9920,12 @@ class MilitarySymbolModule {
9677
9920
  }
9678
9921
 
9679
9922
  /**
9680
- * 禁用拖拽编辑模式
9923
+ * 禁用军标编辑模式
9681
9924
  */
9682
9925
  disableEditMode() {
9683
9926
  if (!this._editMode) return;
9684
9927
  if (this.eventModule) {
9685
9928
  this._dragObserverIds.forEach((observerId) => {
9686
- // 每个观察者注册时的事件类型固定,分别注销
9687
9929
  ['mouse:down', 'mouse:move', 'mouse:up'].forEach((eventType) => {
9688
9930
  try {
9689
9931
  this.eventModule.unregisterObserver(eventType, observerId);
@@ -9694,13 +9936,18 @@ class MilitarySymbolModule {
9694
9936
  });
9695
9937
  }
9696
9938
  this._dragObserverIds = [];
9939
+ if (this._editSymbolId) {
9940
+ this._removeEditHandles(this._editSymbolId);
9941
+ }
9942
+ this._editSymbolId = null;
9697
9943
  this._draggingId = null;
9944
+ this._draggingHandle = null;
9698
9945
  this.viewer.scene.screenSpaceCameraController.enableInputs = true;
9699
9946
  this._editMode = false;
9700
9947
  }
9701
9948
 
9702
9949
  /**
9703
- * 注册位置变化回调
9950
+ * 注册位置变化回调(仅位置)
9704
9951
  * @param {Function} callback 回调参数 { id, position }
9705
9952
  * @returns {Function} 注销函数
9706
9953
  */
@@ -9708,6 +9955,16 @@ class MilitarySymbolModule {
9708
9955
  this._positionChangedCallbacks.add(callback);
9709
9956
  return () => this._positionChangedCallbacks.delete(callback);
9710
9957
  }
9958
+
9959
+ /**
9960
+ * 注册变换变化回调(位置、缩放、旋转)
9961
+ * @param {Function} callback 回调参数 { id, position, scale, rotation }
9962
+ * @returns {Function} 注销函数
9963
+ */
9964
+ onTransformChanged(callback) {
9965
+ this._transformChangedCallbacks.add(callback);
9966
+ return () => this._transformChangedCallbacks.delete(callback);
9967
+ }
9711
9968
  }
9712
9969
 
9713
9970
  /**