cesium-xs-sdk 1.0.18 → 1.0.20

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.
@@ -845,6 +845,24 @@ class SceneModule {
845
845
  }
846
846
  });
847
847
  }
848
+
849
+ /**
850
+ * 是否开启南北极轴约束
851
+ * 开启时(Cesium 默认行为)鼠标旋转/缩放无法让相机越过南北极点;
852
+ * 关闭时(viewer.camera.constrainedAxis = undefined)相机可穿越极点。
853
+ * @param {boolean} [enabled=true] true 开启约束,false 关闭约束
854
+ */
855
+ setPolarAxisConstraint(enabled = true) {
856
+ this.viewer.camera.constrainedAxis = enabled ? Cesium$h.Cartesian3.UNIT_Z : undefined;
857
+ }
858
+
859
+ /**
860
+ * 查询南北极轴约束是否开启
861
+ * @returns {boolean} true 已开启(相机不可穿越极点),false 已关闭
862
+ */
863
+ isPolarAxisConstraintEnabled() {
864
+ return Cesium$h.defined(this.viewer.camera.constrainedAxis);
865
+ }
848
866
  }
849
867
 
850
868
  const Cesium$g = getCesium();
@@ -2032,6 +2050,22 @@ function isLinePlotType(plotType) {
2032
2050
  ].includes(plotType);
2033
2051
  }
2034
2052
 
2053
+ /**
2054
+ * 控制 SDK 内 LabelGraphics / BillboardGraphics / PointPrimitive 的
2055
+ * disableDepthTestDistance 默认值。
2056
+ *
2057
+ * 语义:相机距离图元小于等于 distance(米)时不进行深度测试,
2058
+ * 即近处观察时图元始终绘制在前景,避免被地形/建筑局部遮挡;
2059
+ * 大于 distance 时恢复正常深度测试,避免地球背面透视。
2060
+ *
2061
+ * 默认 8000 米;可在运行时改写:
2062
+ * import { LabelDepthConfig } from 'cesium-xs-sdk'
2063
+ * LabelDepthConfig.distance = 4000
2064
+ */
2065
+ const LabelDepthConfig = {
2066
+ distance: 8000,
2067
+ };
2068
+
2035
2069
  const Cesium$f = getCesium();
2036
2070
 
2037
2071
  /**
@@ -2069,7 +2103,7 @@ class PointWrapper {
2069
2103
  }
2070
2104
 
2071
2105
  setDisableDepthTestDistance(distance) {
2072
- this._entity.point.disableDepthTestDistance = distance;
2106
+ this._entity.point.disableDepthTestDistance = LabelDepthConfig.distance;
2073
2107
  return this;
2074
2108
  }
2075
2109
 
@@ -2105,7 +2139,7 @@ class PointWrapper {
2105
2139
  this._entity.label.text = text;
2106
2140
 
2107
2141
  // 应用配置选项
2108
- this._entity.label.disableDepthTestDistance = options.disableDepthTestDistance ?? Number.POSITIVE_INFINITY;
2142
+ this._entity.label.disableDepthTestDistance = options.disableDepthTestDistance ?? LabelDepthConfig.distance;
2109
2143
  this._entity.label.verticalOrigin = options.verticalOrigin ?? Cesium$f.VerticalOrigin.BOTTOM;
2110
2144
  this._entity.label.eyeOffset = options.eyeOffset ?? new Cesium$f.Cartesian3(0, 0, -10);
2111
2145
  this._entity.label.heightReference = options.heightReference ?? Cesium$f.HeightReference.RELATIVE_TO_GROUND;
@@ -2694,7 +2728,7 @@ class BillboardWrapper {
2694
2728
  }
2695
2729
 
2696
2730
  setDisableDepthTestDistance(distance) {
2697
- this._entity.billboard.disableDepthTestDistance = distance;
2731
+ this._entity.billboard.disableDepthTestDistance = LabelDepthConfig.distance;
2698
2732
  return this;
2699
2733
  }
2700
2734
 
@@ -3635,6 +3669,9 @@ class BaseTool {
3635
3669
  this._handlers = []; // 注册的事件处理器 ID
3636
3670
  // 自维护双击判定器(DOM dblclick 在移动端经常不合成,见 DoubleClickDetector 注释)
3637
3671
  this._doubleClickDetector = new DoubleClickDetector();
3672
+ // mousemove rAF 节流状态:多次 move 之间只保留最新一次,与 render 帧同步
3673
+ this._moveRafId = null;
3674
+ this._movePendingCb = null;
3638
3675
  }
3639
3676
 
3640
3677
  /**
@@ -3655,6 +3692,12 @@ class BaseTool {
3655
3692
  if (!this._isActive) return;
3656
3693
  this._isActive = false;
3657
3694
  this._doubleClickDetector.reset();
3695
+ // 取消 pending 的 mousemove rAF,避免停用后仍执行预览逻辑
3696
+ if (this._moveRafId != null) {
3697
+ cancelAnimationFrame(this._moveRafId);
3698
+ this._moveRafId = null;
3699
+ this._movePendingCb = null;
3700
+ }
3658
3701
  this.onDeactivate();
3659
3702
  this.clearHandlers();
3660
3703
  }
@@ -3690,17 +3733,36 @@ class BaseTool {
3690
3733
  */
3691
3734
  registerObserver(eventType, callback) {
3692
3735
  const observerId = this._eventModule.registerObserver(eventType, (lngLat, entity, movement) => {
3693
- // mouse:move 事件没有 lngLat,直接传递 movement 对象
3694
- if (eventType === 'mouse:move') {
3695
- callback(movement);
3696
- } else {
3697
- callback(lngLat, entity, movement);
3698
- }
3736
+ // 统一转发:mouse:move 也带上 EventModule 已计算的 lngLat / pickedEntity,
3737
+ // 避免工具层再调 scene.pickPosition / scene.pick 造成 2× 调用。
3738
+ callback(lngLat, entity, movement);
3699
3739
  });
3700
3740
  this._handlers.push({ eventType, observerId });
3701
3741
  return observerId;
3702
3742
  }
3703
3743
 
3744
+ /**
3745
+ * 将高频 mousemove 处理合并到下一帧:多次调用之间只保留最新一次的回调,
3746
+ * 与 Cesium render 帧对齐(~60Hz),避免全球视角下 pickPosition / CallbackProperty
3747
+ * 在 100+ Hz 浏览器原生频率下成为热点。
3748
+ *
3749
+ * 使用方式(仅对"非交互关键 + 计算密集"的预览路径):
3750
+ * this._scheduleMoveWork(() => this._updatePreview(this._mousePos));
3751
+ *
3752
+ * 注意:callback 应在闭包里读取 this 上的最新字段(_currentMousePos 等),
3753
+ * 不要传参;参数会被合并掉的多次调用的中间值覆盖。
3754
+ */
3755
+ _scheduleMoveWork(callback) {
3756
+ this._movePendingCb = callback;
3757
+ if (this._moveRafId != null) return;
3758
+ this._moveRafId = requestAnimationFrame(() => {
3759
+ const cb = this._movePendingCb;
3760
+ this._moveRafId = null;
3761
+ this._movePendingCb = null;
3762
+ if (cb) cb();
3763
+ });
3764
+ }
3765
+
3704
3766
  /**
3705
3767
  * 清除所有已注册的事件处理器
3706
3768
  */
@@ -4059,39 +4121,66 @@ class DrawTool extends BaseTool {
4059
4121
  }
4060
4122
  }
4061
4123
 
4062
- _onMove(data) {
4063
- // mouse:move 事件的 movement 对象使用 endPosition
4064
- const screenPos = data.position || data.endPosition;
4124
+ _onMove(lngLat, entity, movement) {
4125
+ const screenPos = movement.position || movement.endPosition;
4065
4126
  if (!screenPos) return;
4066
- const lonLat = this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
4127
+ // 优先使用 EventModule 已计算的 lngLat(避免重复 scene.pickPosition);
4128
+ // 拾取失败(如鼠标移到地球外)时回退到 screenToLonLat。
4129
+ const lonLat = lngLat || this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
4067
4130
  if (!lonLat) return;
4068
4131
 
4069
4132
  this._currentMousePos = lonLat;
4070
4133
 
4071
4134
  if (this._drawType === DrawType.POINT) return;
4072
4135
 
4136
+ // 预览几何更新是计算密集路径(CallbackProperty / 经纬度→ECEF / 标绘算法),
4137
+ // 在全球视角下每帧都跑成本高,合并到下一帧(~60Hz),丢弃中间 move 事件。
4138
+ this._scheduleMoveWork(() => this._applyPreviewUpdate());
4139
+
4140
+ // 鼠标光标类反馈保持同步(每次 mousemove 都要响应,不走 rAF 合并)
4141
+ if (
4142
+ (this._drawType === DrawType.LINE || this._drawType === DrawType.POLYGON) &&
4143
+ this._isDrawing
4144
+ ) {
4145
+ this._updatePreviewCursor();
4146
+ }
4147
+ }
4148
+
4149
+ /**
4150
+ * 真正执行预览更新:由 _scheduleMoveWork 在下一帧调用一次,读取
4151
+ * this._currentMousePos / _startPos / _tempPositions 等最新字段。
4152
+ */
4153
+ _applyPreviewUpdate() {
4154
+ const lonLat = this._currentMousePos;
4155
+ if (!lonLat) return;
4156
+
4073
4157
  // 标绘类型预览
4074
4158
  const plotConfig = PLOT_DRAW_CONFIG[this._drawType];
4075
4159
  if (plotConfig) {
4076
4160
  if (this._isDrawing && this._tempPositions.length >= plotConfig.minPoints - 1) {
4077
- this._updatePlotPreview(plotConfig, lonLat);
4161
+ this._updatePlotPreview(plotConfig);
4078
4162
  }
4079
4163
  return;
4080
4164
  }
4081
4165
 
4082
4166
  if (this._isDrawing) {
4083
4167
  if (this._drawType === DrawType.LINE) {
4084
- this._updateTempLine(lonLat);
4168
+ this._updateTempLine();
4085
4169
  } else if (this._drawType === DrawType.POLYGON) {
4086
- this._updateTempPolygon(lonLat);
4170
+ this._updateTempPolygon();
4087
4171
  }
4088
4172
  }
4089
4173
 
4090
4174
  if (this._startPos) {
4091
- this._updateRectOrCirclePreview(lonLat);
4175
+ this._updateRectOrCirclePreview();
4092
4176
  }
4093
4177
  }
4094
4178
 
4179
+ _updatePreviewCursor() {
4180
+ // 简化占位:原 _onMove 中没有显式 cursor 处理,保留 hook 以备后用。
4181
+ // 真正的鼠标光标样式由 _onMove 主流程维护,这里无副作用。
4182
+ }
4183
+
4095
4184
  _onKeyDown(data) {
4096
4185
  // ESC 取消绘制
4097
4186
  if (data.key === 'Escape') {
@@ -4238,7 +4327,7 @@ class DrawTool extends BaseTool {
4238
4327
  .setScale(0.5)
4239
4328
  .setVerticalOrigin(Cesium$c.VerticalOrigin.BOTTOM)
4240
4329
  .setHeightReference(Cesium$c.HeightReference.CLAMP_TO_GROUND)
4241
- .setDisableDepthTestDistance(Number.POSITIVE_INFINITY);
4330
+ .setDisableDepthTestDistance(LabelDepthConfig.distance);
4242
4331
 
4243
4332
  // 选择图片来源:自定义 URL 优先,否则使用 SDK 内置 base64 图标
4244
4333
  const imageSource = (typeof imgUrl === 'string' && imgUrl.trim() !== '')
@@ -4347,25 +4436,23 @@ class DrawTool extends BaseTool {
4347
4436
  }
4348
4437
  }
4349
4438
 
4350
- _updateTempLine(mousePos) {
4351
- // 构建预览线位置:已有位置 + 鼠标当前位置(跟随鼠标)
4352
- const previewPositions = [...this._tempPositions];
4353
- if (mousePos) {
4354
- previewPositions.push(mousePos);
4355
- }
4356
-
4357
- // 计算 Cartesian3 数组
4358
- const flatCoords = [];
4359
- for (const c of previewPositions) {
4360
- flatCoords.push(c[0], c[1], c[2] || 0);
4361
- }
4362
- const cartesians = Cesium$c.Cartesian3.fromDegreesArrayHeights(flatCoords);
4363
-
4439
+ _updateTempLine() {
4440
+ // 第一次创建时建立稳定的 CallbackProperty,回调内部读 this._tempPositions +
4441
+ // this._currentMousePos(与 _updateTempPolygon 同模式),避免每次 move 都重建。
4364
4442
  if (!this._previewLine) {
4365
4443
  // 首次创建预览线(黑白相间的斑马纹路)
4366
4444
  this._previewLine = this.createTempEntity(this.generateId('temp_line'), {
4367
4445
  polyline: {
4368
- positions: cartesians,
4446
+ positions: new Cesium$c.CallbackProperty(() => {
4447
+ const previewPositions = [...this._tempPositions];
4448
+ if (this._currentMousePos) previewPositions.push(this._currentMousePos);
4449
+ if (previewPositions.length < 2) return [];
4450
+ const flatCoords = [];
4451
+ for (const c of previewPositions) {
4452
+ flatCoords.push(c[0], c[1], c[2] || 0);
4453
+ }
4454
+ return Cesium$c.Cartesian3.fromDegreesArrayHeights(flatCoords);
4455
+ }, false),
4369
4456
  material: new Cesium$c.PolylineDashMaterialProperty({
4370
4457
  color: Cesium$c.Color.WHITE,
4371
4458
  gapColor: Cesium$c.Color.BLACK,
@@ -4376,12 +4463,6 @@ class DrawTool extends BaseTool {
4376
4463
  }
4377
4464
  });
4378
4465
  this._tempEntities.push(this._previewLine);
4379
- } else {
4380
- // 直接更新位置属性,避免重建实体
4381
- // this._previewLine.polyline.positions = cartesians;
4382
- this._previewLine.polyline.positions = new Cesium$c.CallbackProperty(() => {
4383
- return cartesians;
4384
- }, false);
4385
4466
  }
4386
4467
  }
4387
4468
 
@@ -4429,116 +4510,87 @@ class DrawTool extends BaseTool {
4429
4510
  }
4430
4511
  }
4431
4512
 
4432
- _updateRectOrCirclePreview(currentLonLat) {
4513
+ _updateRectOrCirclePreview() {
4433
4514
  if (!this._startPos) return;
4434
4515
 
4435
4516
  if (this._drawType === DrawType.RECTANGLE) {
4436
- this._updateRectPreview(currentLonLat);
4517
+ this._updateRectPreview();
4437
4518
  } else if (this._drawType === DrawType.CIRCLE) {
4438
- this._updateCirclePreview(currentLonLat);
4519
+ this._updateCirclePreview();
4439
4520
  }
4440
4521
  }
4441
4522
 
4442
- _updateRectPreview(currentLonLat) {
4443
- const corner1 = this._startPos;
4444
- const corner2 = currentLonLat;
4445
-
4446
- // 计算矩形四个角
4447
- const positions = [
4448
- [corner1[0], corner1[1]],
4449
- [corner2[0], corner1[1]],
4450
- [corner2[0], corner2[1]],
4451
- [corner1[0], corner2[1]],
4452
- [corner1[0], corner1[1]]
4453
- ];
4454
-
4455
- const flatCoords = [];
4456
- for (const c of positions) {
4457
- flatCoords.push(c[0], c[1], 0);
4458
- }
4459
- const cartesians = Cesium$c.Cartesian3.fromDegreesArrayHeights(flatCoords);
4460
-
4523
+ _updateRectPreview() {
4524
+ // 第一次创建时建立稳定的 CallbackProperty,回调内部读 this._startPos / _currentMousePos,
4525
+ // 避免每个 mousemove 都 new CallbackProperty + 重绑几何(参见 polygon preview 同模式)。
4461
4526
  if (!this._previewRect) {
4527
+ const polylinePositions = new Cesium$c.CallbackProperty(() => this._computeRectCornersFlat(2), false);
4528
+ const polygonHierarchy = new Cesium$c.CallbackProperty(() => {
4529
+ const cartesians = this._computeRectCornersFlat(2);
4530
+ return new Cesium$c.PolygonHierarchy(cartesians);
4531
+ }, false);
4462
4532
  this._previewRect = this.createTempEntity(this.generateId('temp_rect'), {
4463
4533
  polyline: {
4464
- positions: new Cesium$c.CallbackProperty(() => cartesians, false),
4534
+ positions: polylinePositions,
4465
4535
  material: Cesium$c.Color.YELLOW.withAlpha(0.7),
4466
4536
  width: 2
4467
4537
  },
4468
4538
  polygon: {
4469
- hierarchy: new Cesium$c.CallbackProperty(() => new Cesium$c.PolygonHierarchy(cartesians), false),
4539
+ hierarchy: polygonHierarchy,
4470
4540
  material: Cesium$c.Color.YELLOW.withAlpha(0.2),
4471
4541
  outline: false
4472
4542
  }
4473
4543
  });
4474
4544
  this._previewRect._isPreview = true;
4475
4545
  this._tempEntities.push(this._previewRect);
4476
- } else {
4477
- // 直接更新位置属性
4478
- this._previewRect.polyline.positions = new Cesium$c.CallbackProperty(() => {
4479
- const c1 = this._startPos;
4480
- const c2 = this._currentMousePos;
4481
- if (!c1 || !c2) return cartesians;
4482
- const pos = [
4483
- [c1[0], c1[1]],
4484
- [c2[0], c1[1]],
4485
- [c2[0], c2[1]],
4486
- [c1[0], c2[1]],
4487
- [c1[0], c1[1]]
4488
- ];
4489
- const flat = [];
4490
- for (const p of pos) {
4491
- flat.push(p[0], p[1], 0);
4492
- }
4493
- return Cesium$c.Cartesian3.fromDegreesArrayHeights(flat);
4494
- }, false);
4495
- this._previewRect.polygon.hierarchy = new Cesium$c.CallbackProperty(() => {
4496
- const c1 = this._startPos;
4497
- const c2 = this._currentMousePos;
4498
- if (!c1 || !c2) return new Cesium$c.PolygonHierarchy(cartesians);
4499
- const pos = [
4500
- [c1[0], c1[1]],
4501
- [c2[0], c1[1]],
4502
- [c2[0], c2[1]],
4503
- [c1[0], c2[1]],
4504
- [c1[0], c1[1]]
4505
- ];
4506
- const flat = [];
4507
- for (const p of pos) {
4508
- flat.push(p[0], p[1], 0);
4509
- }
4510
- return new Cesium$c.PolygonHierarchy(Cesium$c.Cartesian3.fromDegreesArrayHeights(flat));
4511
- }, false);
4512
4546
  }
4513
4547
  }
4514
4548
 
4515
- _updateCirclePreview(currentLonLat) {
4516
- const center = this._startPos;
4517
- const radius = this._calcDistance(center, currentLonLat);
4518
- const segments = 64;
4549
+ /**
4550
+ * 计算矩形四个角点的 Cartesian3[](含起末闭合点):
4551
+ * [start, (currLon, startLat), curr, (startLon, currLat), start]
4552
+ * 返回数组便于 polyline 与 polygon 共用;调用方负责包装 PolygonHierarchy。
4553
+ */
4554
+ _computeRectCornersFlat() {
4555
+ const c1 = this._startPos;
4556
+ const c2 = this._currentMousePos;
4557
+ if (!c1 || !c2) {
4558
+ return [Cesium$c.Cartesian3.fromDegrees(0, 0, 0)];
4559
+ }
4560
+ const flat = [
4561
+ c1[0], c1[1], 0,
4562
+ c2[0], c1[1], 0,
4563
+ c2[0], c2[1], 0,
4564
+ c1[0], c2[1], 0,
4565
+ c1[0], c1[1], 0
4566
+ ];
4567
+ return Cesium$c.Cartesian3.fromDegreesArrayHeights(flat);
4568
+ }
4519
4569
 
4570
+ _updateCirclePreview() {
4571
+ // 第一次创建时建立稳定的 CallbackProperty,回调内部读 this._startPos / _currentMousePos,
4572
+ // 避免每个 mousemove 都 new CallbackProperty 并重建几何。
4573
+ const segments = 64;
4520
4574
  if (!this._previewCircle) {
4575
+ const hierarchy = new Cesium$c.CallbackProperty(() => {
4576
+ const center = this._startPos;
4577
+ const r = this._calcDistance(center, this._currentMousePos);
4578
+ if (!center || !this._currentMousePos) {
4579
+ return new Cesium$c.PolygonHierarchy([]);
4580
+ }
4581
+ const pos = this._generateCirclePositions(center, r, segments);
4582
+ const flat = pos.flatMap(c => [c[0], c[1], 0]);
4583
+ return new Cesium$c.PolygonHierarchy(Cesium$c.Cartesian3.fromDegreesArrayHeights(flat));
4584
+ }, false);
4521
4585
  this._previewCircle = this.createTempEntity(this.generateId('temp_circle'), {
4522
4586
  polygon: {
4523
- hierarchy: new Cesium$c.CallbackProperty(() => {
4524
- const positions = this._generateCirclePositions(center, radius, segments);
4525
- const flat = positions.flatMap(c => [c[0], c[1], 0]);
4526
- return new Cesium$c.PolygonHierarchy(Cesium$c.Cartesian3.fromDegreesArrayHeights(flat));
4527
- }, false),
4587
+ hierarchy,
4528
4588
  material: Cesium$c.Color.YELLOW.withAlpha(0.2),
4529
4589
  outline: false // 地形贴合时不支持 outline,设为 false 避免警告
4530
4590
  }
4531
4591
  });
4532
4592
  this._previewCircle._isPreview = true;
4533
4593
  this._tempEntities.push(this._previewCircle);
4534
- } else {
4535
- // 直接更新
4536
- this._previewCircle.polygon.hierarchy = new Cesium$c.CallbackProperty(() => {
4537
- const r = this._calcDistance(this._startPos, this._currentMousePos);
4538
- const pos = this._generateCirclePositions(this._startPos, r, 64);
4539
- const flat = pos.flatMap(c => [c[0], c[1], 0]);
4540
- return new Cesium$c.PolygonHierarchy(Cesium$c.Cartesian3.fromDegreesArrayHeights(flat));
4541
- }, false);
4542
4594
  }
4543
4595
  }
4544
4596
 
@@ -4706,37 +4758,39 @@ class DrawTool extends BaseTool {
4706
4758
  * @param {object} config 标绘配置
4707
4759
  * @param {Array} mousePos 当前鼠标位置 [lon, lat, height]
4708
4760
  */
4709
- _updatePlotPreview(config, mousePos) {
4710
- const isEllipse = config.plotType === PlotGraphicType.ELLIPSE;
4711
- const normalizeForEllipse = (pts) => isEllipse ? this._normalizeEllipseControlPoints(pts) : pts;
4712
-
4713
- const previewPositions = [...this._tempPositions];
4714
- if (mousePos) {
4715
- previewPositions.push(mousePos);
4716
- }
4717
-
4718
- if (previewPositions.length < config.minPoints) return;
4719
-
4761
+ _updatePlotPreview(config) {
4762
+ // 标绘预览整体思路:
4763
+ // 第一次调用时建立稳定的 CallbackProperty,回调内部读取 this._tempPositions +
4764
+ // this._currentMousePos 重新生成几何。这样 mouve 之间的预览更新只是一帧一次的
4765
+ // 几何重算,没有 CallbackProperty 重建开销。
4720
4766
  const factoryName = `create${this._capitalizePlotType(config.plotType)}`;
4721
4767
  if (!PlotGeometryUtil[factoryName]) {
4722
4768
  console.warn(`未知标绘类型: ${config.plotType}`);
4723
4769
  return;
4724
4770
  }
4725
-
4726
- const geometryPositions = normalizeForEllipse(previewPositions);
4727
- const positions = PlotGeometryUtil[factoryName](geometryPositions);
4771
+ const isEllipse = config.plotType === PlotGraphicType.ELLIPSE;
4772
+ const normalizeForEllipse = (pts) => isEllipse ? this._normalizeEllipseControlPoints(pts) : pts;
4728
4773
  const isLine = config.category === 'line';
4729
- const flatCoords = [];
4730
- for (const c of positions) {
4731
- flatCoords.push(c[0], c[1], c[2] || 0);
4732
- }
4733
- const cartesians = Cesium$c.Cartesian3.fromDegreesArrayHeights(flatCoords);
4774
+ const minPoints = config.minPoints;
4734
4775
 
4735
4776
  if (!this._previewPlot) {
4777
+ // 标绘预览几何工厂:把"读 this._tempPositions + this._currentMousePos 并生成
4778
+ // 标绘几何"的逻辑抽到一个内部方法,由稳定的 CallbackProperty 在每帧调用。
4779
+ // 避免每次 mousemove 都重建 CallbackProperty + 重新计算整个几何。
4780
+ const computePlotGeometry = () => {
4781
+ const dynamicPositions = [...this._tempPositions];
4782
+ if (this._currentMousePos) dynamicPositions.push(this._currentMousePos);
4783
+ if (dynamicPositions.length < minPoints) return null;
4784
+ const pos = PlotGeometryUtil[factoryName](normalizeForEllipse(dynamicPositions));
4785
+ const flat = [];
4786
+ for (const c of pos) flat.push(c[0], c[1], c[2] || 0);
4787
+ return Cesium$c.Cartesian3.fromDegreesArrayHeights(flat);
4788
+ };
4789
+
4736
4790
  if (isLine) {
4737
4791
  this._previewPlot = this.createTempEntity(this.generateId('temp_plot'), {
4738
4792
  polyline: {
4739
- positions: new Cesium$c.CallbackProperty(() => cartesians, false),
4793
+ positions: new Cesium$c.CallbackProperty(() => computePlotGeometry() || [], false),
4740
4794
  material: Cesium$c.Color.YELLOW,
4741
4795
  width: 2,
4742
4796
  clampToGround: true
@@ -4745,12 +4799,19 @@ class DrawTool extends BaseTool {
4745
4799
  } else {
4746
4800
  this._previewPlot = this.createTempEntity(this.generateId('temp_plot'), {
4747
4801
  polygon: {
4748
- hierarchy: new Cesium$c.CallbackProperty(() => new Cesium$c.PolygonHierarchy(cartesians), false),
4802
+ hierarchy: new Cesium$c.CallbackProperty(() => {
4803
+ const c = computePlotGeometry();
4804
+ return new Cesium$c.PolygonHierarchy(c || []);
4805
+ }, false),
4749
4806
  material: Cesium$c.Color.YELLOW.withAlpha(0.3),
4750
4807
  outline: false
4751
4808
  },
4752
4809
  polyline: {
4753
- positions: new Cesium$c.CallbackProperty(() => [...cartesians, cartesians[0]], false),
4810
+ positions: new Cesium$c.CallbackProperty(() => {
4811
+ const c = computePlotGeometry();
4812
+ if (!c || c.length === 0) return [];
4813
+ return [...c, c[0]];
4814
+ }, false),
4754
4815
  material: Cesium$c.Color.YELLOW,
4755
4816
  width: 1
4756
4817
  }
@@ -4758,51 +4819,6 @@ class DrawTool extends BaseTool {
4758
4819
  }
4759
4820
  this._previewPlot._isPreview = true;
4760
4821
  this._tempEntities.push(this._previewPlot);
4761
- } else {
4762
- const self = this;
4763
- if (isLine) {
4764
- this._previewPlot.polyline.positions = new Cesium$c.CallbackProperty(() => {
4765
- const dynamicPositions = [...self._tempPositions];
4766
- if (self._currentMousePos) {
4767
- dynamicPositions.push(self._currentMousePos);
4768
- }
4769
- if (dynamicPositions.length < config.minPoints) return [];
4770
- const pos = PlotGeometryUtil[factoryName](normalizeForEllipse(dynamicPositions));
4771
- const flat = [];
4772
- for (const c of pos) {
4773
- flat.push(c[0], c[1], c[2] || 0);
4774
- }
4775
- return Cesium$c.Cartesian3.fromDegreesArrayHeights(flat);
4776
- }, false);
4777
- } else {
4778
- this._previewPlot.polygon.hierarchy = new Cesium$c.CallbackProperty(() => {
4779
- const dynamicPositions = [...self._tempPositions];
4780
- if (self._currentMousePos) {
4781
- dynamicPositions.push(self._currentMousePos);
4782
- }
4783
- if (dynamicPositions.length < config.minPoints) return new Cesium$c.PolygonHierarchy([]);
4784
- const pos = PlotGeometryUtil[factoryName](normalizeForEllipse(dynamicPositions));
4785
- const flat = [];
4786
- for (const c of pos) {
4787
- flat.push(c[0], c[1], c[2] || 0);
4788
- }
4789
- return new Cesium$c.PolygonHierarchy(Cesium$c.Cartesian3.fromDegreesArrayHeights(flat));
4790
- }, false);
4791
- this._previewPlot.polyline.positions = new Cesium$c.CallbackProperty(() => {
4792
- const dynamicPositions = [...self._tempPositions];
4793
- if (self._currentMousePos) {
4794
- dynamicPositions.push(self._currentMousePos);
4795
- }
4796
- if (dynamicPositions.length < config.minPoints) return [];
4797
- const pos = PlotGeometryUtil[factoryName](normalizeForEllipse(dynamicPositions));
4798
- const flat = [];
4799
- for (const c of pos) {
4800
- flat.push(c[0], c[1], c[2] || 0);
4801
- }
4802
- const cart = Cesium$c.Cartesian3.fromDegreesArrayHeights(flat);
4803
- return [...cart, cart[0]];
4804
- }, false);
4805
- }
4806
4822
  }
4807
4823
  }
4808
4824
 
@@ -5225,9 +5241,9 @@ class EditTool extends BaseTool {
5225
5241
  }
5226
5242
  }
5227
5243
 
5228
- _onMove(data) {
5229
- // _onMove receives movement object from BaseTool
5230
- const screenPos = data.position || data.endPosition;
5244
+ _onMove(_lngLat, _entity, movement) {
5245
+ // BaseTool.registerObserver 现在统一转发 (lngLat, entity, movement)
5246
+ const screenPos = movement.position || movement.endPosition;
5231
5247
  if (!screenPos) return;
5232
5248
 
5233
5249
  // 非拖拽时做节流,避免 hover 检测过于频繁
@@ -5435,7 +5451,7 @@ class EditTool extends BaseTool {
5435
5451
  pixelSize: 12,
5436
5452
  outlineColor: Cesium$b.Color.BLACK,
5437
5453
  outlineWidth: 2,
5438
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
5454
+ disableDepthTestDistance: LabelDepthConfig.distance,
5439
5455
  heightReference: Cesium$b.HeightReference.CLAMP_TO_GROUND
5440
5456
  }
5441
5457
  });
@@ -5483,7 +5499,7 @@ class EditTool extends BaseTool {
5483
5499
  pixelSize: 14,
5484
5500
  outlineColor: Cesium$b.Color.BLUE,
5485
5501
  outlineWidth: 2,
5486
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
5502
+ disableDepthTestDistance: LabelDepthConfig.distance,
5487
5503
  heightReference: Cesium$b.HeightReference.CLAMP_TO_GROUND
5488
5504
  }
5489
5505
  });
@@ -5650,7 +5666,7 @@ class EditTool extends BaseTool {
5650
5666
  pixelSize: 12,
5651
5667
  outlineColor: Cesium$b.Color.BLACK,
5652
5668
  outlineWidth: 2,
5653
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
5669
+ disableDepthTestDistance: LabelDepthConfig.distance,
5654
5670
  heightReference: Cesium$b.HeightReference.CLAMP_TO_GROUND,
5655
5671
  }
5656
5672
  });
@@ -5676,7 +5692,7 @@ class EditTool extends BaseTool {
5676
5692
  pixelSize: 14,
5677
5693
  outlineColor: Cesium$b.Color.BLUE,
5678
5694
  outlineWidth: 2,
5679
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
5695
+ disableDepthTestDistance: LabelDepthConfig.distance,
5680
5696
  heightReference: Cesium$b.HeightReference.CLAMP_TO_GROUND
5681
5697
  }
5682
5698
  });
@@ -5690,7 +5706,7 @@ class EditTool extends BaseTool {
5690
5706
  pixelSize: 12,
5691
5707
  outlineColor: Cesium$b.Color.YELLOW,
5692
5708
  outlineWidth: 2,
5693
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
5709
+ disableDepthTestDistance: LabelDepthConfig.distance,
5694
5710
  heightReference: Cesium$b.HeightReference.CLAMP_TO_GROUND
5695
5711
  }
5696
5712
  });
@@ -5715,7 +5731,7 @@ class EditTool extends BaseTool {
5715
5731
  pixelSize: 12,
5716
5732
  outlineColor: Cesium$b.Color.BLACK,
5717
5733
  outlineWidth: 2,
5718
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
5734
+ disableDepthTestDistance: LabelDepthConfig.distance,
5719
5735
  // heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
5720
5736
  }
5721
5737
  });
@@ -5747,7 +5763,7 @@ class EditTool extends BaseTool {
5747
5763
  pixelSize: 10,
5748
5764
  outlineColor: Cesium$b.Color.RED,
5749
5765
  outlineWidth: 2,
5750
- disableDepthTestDistance: Number.POSITIVE_INFINITY
5766
+ disableDepthTestDistance: LabelDepthConfig.distance
5751
5767
  }
5752
5768
  }
5753
5769
  );
@@ -5815,7 +5831,7 @@ class EditTool extends BaseTool {
5815
5831
  pixelSize: 14,
5816
5832
  outlineColor: Cesium$b.Color.BLUE,
5817
5833
  outlineWidth: 2,
5818
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
5834
+ disableDepthTestDistance: LabelDepthConfig.distance,
5819
5835
  heightReference: Cesium$b.HeightReference.CLAMP_TO_GROUND
5820
5836
  }
5821
5837
  });
@@ -6018,24 +6034,43 @@ class EditTool extends BaseTool {
6018
6034
  return this._clampPositionToGround(newPos);
6019
6035
  });
6020
6036
  this._selectedEntity.polyline.positions = newPositions;
6021
-
6022
- // 修复:更新所有顶点控制点位置,同时更新中心控制点位置
6037
+
6038
+ // 修复:更新所有顶点和中点控制点位置
6023
6039
  this._editHandles.forEach((handle) => {
6024
- // 修复:统一使用 includes('center') 来判断中心控制点
6025
- if (handle.id && handle.id.includes('center')) {
6026
- // 中心控制点已经更新过了
6040
+ if (!handle.id || handle.id.includes('center')) return;
6041
+ // 顶点 handle:__edit_handle_<index>
6042
+ const vertexMatch = handle.id.match(/__edit_handle_(\d+)$/);
6043
+ if (vertexMatch) {
6044
+ const index = parseInt(vertexMatch[1], 10);
6045
+ if (newPositions[index]) {
6046
+ const posProp = handle.position;
6047
+ if (posProp && typeof posProp.setValue === 'function') {
6048
+ posProp.setValue(newPositions[index]);
6049
+ } else {
6050
+ handle.position = new Cesium$b.ConstantPositionProperty(newPositions[index]);
6051
+ }
6052
+ }
6027
6053
  return;
6028
6054
  }
6029
- // 找到对应的顶点索引
6030
- const match = handle.id.match(/__edit_handle_(\d+)$/);
6031
- if (match) {
6032
- const index = parseInt(match[1], 10);
6033
- if (newPositions[index]) {
6034
- handle.position = new Cesium$b.ConstantPositionProperty(newPositions[index]);
6055
+ // 中点 handle:__edit_handle_mid_<li>_<ri>
6056
+ const midMatch = handle.id.match(/__edit_handle_mid_(\d+)_(\d+)/);
6057
+ if (midMatch) {
6058
+ const li = parseInt(midMatch[1], 10);
6059
+ const ri = parseInt(midMatch[2], 10);
6060
+ const left = newPositions[li];
6061
+ const right = newPositions[ri];
6062
+ if (!left || !right) return;
6063
+ const midCart = Cesium$b.Cartesian3.midpoint(left, right, new Cesium$b.Cartesian3());
6064
+ const clamped = this._clampPositionToGround(midCart);
6065
+ const posProp = handle.position;
6066
+ if (posProp && typeof posProp.setValue === 'function') {
6067
+ posProp.setValue(clamped);
6068
+ } else {
6069
+ handle.position = new Cesium$b.ConstantPositionProperty(clamped);
6035
6070
  }
6036
6071
  }
6037
6072
  });
6038
-
6073
+
6039
6074
  // 更新原始位置缓存
6040
6075
  this._originalPositions = newPositions;
6041
6076
  } else if (Cesium$b.defined(this._selectedEntity.polygon)) {
@@ -6086,18 +6121,40 @@ class EditTool extends BaseTool {
6086
6121
  return this._clampPositionToGround(newPos);
6087
6122
  });
6088
6123
 
6089
- // 修复:更新顶点控制点位置,使用更可靠的ID匹配
6124
+ // 修复:更新顶点和中点控制点位置。
6125
+ // 注意:正则 `/__edit_handle_(\d+)$/` 只匹配顶点 handle;
6126
+ // 中点 handle 的 ID 是 `__edit_handle_mid_<li>_<ri>`,需要单独处理。
6090
6127
  this._editHandles.forEach((handle) => {
6091
- // 修复:统一使用 includes('center') 来判断中心控制点
6092
- if (handle.id && handle.id.includes('center')) {
6128
+ if (!handle.id || handle.id.includes('center')) return;
6129
+ // 顶点 handle:__edit_handle_<index>
6130
+ const vertexMatch = handle.id.match(/__edit_handle_(\d+)$/);
6131
+ if (vertexMatch) {
6132
+ const index = parseInt(vertexMatch[1], 10);
6133
+ if (newPositions[index]) {
6134
+ const posProp = handle.position;
6135
+ if (posProp && typeof posProp.setValue === 'function') {
6136
+ posProp.setValue(newPositions[index]);
6137
+ } else {
6138
+ handle.position = new Cesium$b.ConstantPositionProperty(newPositions[index]);
6139
+ }
6140
+ }
6093
6141
  return;
6094
6142
  }
6095
- // 提取数字索引
6096
- const match = handle.id.match(/__edit_handle_(\d+)$/);
6097
- if (match) {
6098
- const index = parseInt(match[1], 10);
6099
- if (newPositions[index]) {
6100
- handle.position = new Cesium$b.ConstantPositionProperty(newPositions[index]);
6143
+ // 中点 handle:__edit_handle_mid_<li>_<ri>
6144
+ const midMatch = handle.id.match(/__edit_handle_mid_(\d+)_(\d+)/);
6145
+ if (midMatch) {
6146
+ const li = parseInt(midMatch[1], 10);
6147
+ const ri = parseInt(midMatch[2], 10);
6148
+ const left = newPositions[li];
6149
+ const right = newPositions[ri];
6150
+ if (!left || !right) return;
6151
+ const midCart = Cesium$b.Cartesian3.midpoint(left, right, new Cesium$b.Cartesian3());
6152
+ const clamped = this._clampPositionToGround(midCart);
6153
+ const posProp = handle.position;
6154
+ if (posProp && typeof posProp.setValue === 'function') {
6155
+ posProp.setValue(clamped);
6156
+ } else {
6157
+ handle.position = new Cesium$b.ConstantPositionProperty(clamped);
6101
6158
  }
6102
6159
  }
6103
6160
  });
@@ -6658,7 +6715,14 @@ class EditTool extends BaseTool {
6658
6715
  // 多边形顶点拖动:通过 CallbackProperty 让 Cesium 直接从 handle 位置读,
6659
6716
  // 这里只需要更新 handle.position 即可,几何会跟着弯折,
6660
6717
  // 远比每帧 newPositions + new PolygonHierarchy 顺滑。
6661
- this._dragHandle.position = new Cesium$b.ConstantPositionProperty(newCartesian);
6718
+ // 复用现有 Property 实例并 setValue:避免替换 Property 引用导致
6719
+ // Cesium 某些情况下不重新渲染。
6720
+ const dragPosProp = this._dragHandle.position;
6721
+ if (dragPosProp && typeof dragPosProp.setValue === 'function') {
6722
+ dragPosProp.setValue(newCartesian);
6723
+ } else {
6724
+ this._dragHandle.position = new Cesium$b.ConstantPositionProperty(newCartesian);
6725
+ }
6662
6726
 
6663
6727
  // 修复:多边形编辑后重新计算并更新中心控制点位置
6664
6728
  this._updateCenterHandlePosition();
@@ -6701,7 +6765,14 @@ class EditTool extends BaseTool {
6701
6765
  if (!left || !right) return;
6702
6766
  const midCart = Cesium$b.Cartesian3.midpoint(left, right, new Cesium$b.Cartesian3());
6703
6767
  const clamped = this._clampPositionToGround(midCart);
6704
- handle.position = new Cesium$b.ConstantPositionProperty(clamped);
6768
+ // 复用现有 Property 实例并 setValue:避免替换 Property 引用导致
6769
+ // Cesium 某些情况下不重新渲染。
6770
+ const posProp = handle.position;
6771
+ if (posProp && typeof posProp.setValue === 'function') {
6772
+ posProp.setValue(clamped);
6773
+ } else {
6774
+ handle.position = new Cesium$b.ConstantPositionProperty(clamped);
6775
+ }
6705
6776
  });
6706
6777
  }
6707
6778
 
@@ -6924,14 +6995,15 @@ class SelectTool extends BaseTool {
6924
6995
  }
6925
6996
  }
6926
6997
 
6927
- _onMove(data) {
6998
+ _onMove(lngLat, _entity, movement) {
6928
6999
  // mouse:move passes movement object from BaseTool
6929
- const screenPos = data.position || data.endPosition;
7000
+ const screenPos = movement.position || movement.endPosition;
6930
7001
  if (!screenPos) return;
6931
7002
 
6932
7003
  if (!this._isSelecting || !this._startPos) return;
6933
7004
 
6934
- const lonLat = this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
7005
+ // 优先使用 EventModule 已计算的 lngLat(避免重复 scene.pickPosition)。
7006
+ const lonLat = lngLat || this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
6935
7007
  if (!lonLat) return;
6936
7008
 
6937
7009
  this._updateSelectionRectPreview(lonLat);
@@ -7423,17 +7495,18 @@ class DeleteTool extends BaseTool {
7423
7495
  }
7424
7496
  }
7425
7497
 
7426
- _onMove(data) {
7427
- const screenPos = data.position || data.endPosition;
7498
+ _onMove(_lngLat, pickedEntity, movement) {
7499
+ const screenPos = movement.position || movement.endPosition;
7428
7500
  if (!screenPos) return;
7429
7501
 
7430
- const picked = this._viewer.scene.pick(screenPos);
7431
-
7502
+ // 优先使用 EventModule 已计算的 pickedEntity(避免重复 scene.pick)。
7503
+ // EventModule 已经做过 pickedEntity.id(Pick 对象 → Entity 实例)取一次,
7504
+ // 这里直接取 Entity.id(字符串)即可。
7432
7505
  let hoveredId = null;
7433
7506
 
7434
- if (Cesium$9.defined(picked) && Cesium$9.defined(picked.id)) {
7435
- const entityId = picked.id.id;
7436
- if (!entityId.startsWith('__temp_') && !entityId.startsWith('__edit_handle_')) {
7507
+ if (Cesium$9.defined(pickedEntity) && Cesium$9.defined(pickedEntity.id)) {
7508
+ const entityId = pickedEntity.id;
7509
+ if (typeof entityId === 'string' && !entityId.startsWith('__temp_') && !entityId.startsWith('__edit_handle_')) {
7437
7510
  hoveredId = entityId;
7438
7511
  }
7439
7512
  }
@@ -7716,6 +7789,24 @@ const LABEL_BORDER_COLOR = '#FB120E';
7716
7789
  const LABEL_BACKGROUND_COLOR = '#ffffff';
7717
7790
  const LABEL_TEXT_COLOR = '#000000';
7718
7791
 
7792
+ // 模块级 measure canvas 单例:测量标签每次重绘文字宽度时复用,避免
7793
+ // 每次都 document.createElement + getContext(之前在 _createLabelImage 中是热路径)。
7794
+ // 延迟到首次调用时再创建:jsdom 等环境顶层 document.createElement('canvas').getContext 不可用。
7795
+ let _measureCanvas = null;
7796
+ let _measureCtx = null;
7797
+ function _measureTextWidth(text, font) {
7798
+ if (!_measureCanvas) {
7799
+ try {
7800
+ _measureCanvas = document.createElement('canvas');
7801
+ _measureCtx = _measureCanvas.getContext('2d');
7802
+ } catch (e) {
7803
+ return Math.max(1, text.length * 8);
7804
+ }
7805
+ }
7806
+ _measureCtx.font = font;
7807
+ return Math.max(1, Math.ceil(_measureCtx.measureText(text).width));
7808
+ }
7809
+
7719
7810
  /**
7720
7811
  * 测量类型枚举
7721
7812
  */
@@ -7903,8 +7994,8 @@ class MeasureTool extends BaseTool {
7903
7994
  this._updateMeasurement();
7904
7995
  }
7905
7996
 
7906
- _onMove(data) {
7907
- const screenPos = data.position || data.endPosition;
7997
+ _onMove(lngLat, entity, movement) {
7998
+ const screenPos = movement.position || movement.endPosition;
7908
7999
  if (!screenPos) return;
7909
8000
 
7910
8001
  // 拖拽顶点中:节流更新该顶点位置与对应几何/标注
@@ -7913,7 +8004,8 @@ class MeasureTool extends BaseTool {
7913
8004
  if (now - this._lastDragMoveTime < this._dragThrottleMs) return;
7914
8005
  this._lastDragMoveTime = now;
7915
8006
 
7916
- const lonLat = this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
8007
+ // 拖动路径优先用 EventModule 已计算的 lngLat;否则 fallback。
8008
+ const lonLat = lngLat || this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
7917
8009
  if (lonLat) {
7918
8010
  this._updateVertexDuringDrag(this._dragVertexIndex, lonLat, this._dragResult);
7919
8011
 
@@ -7927,25 +8019,27 @@ class MeasureTool extends BaseTool {
7927
8019
  return;
7928
8020
  }
7929
8021
 
7930
- // 非拖拽:检测悬停,更新光标(与 EditTool 风格一致)
8022
+ // 非拖拽:检测悬停,更新光标(与 EditTool 风格一致)。优先用 EventModule 给的 entity。
7931
8023
  let isOverFinishedVertex = false;
7932
- try {
7933
- const picked = this._viewer.scene.pick(screenPos);
7934
- // scene.pick 返回的是 Pick 对象(含 id / primitive 两层),需要取出 .id 才能与 pointEntities 中的 Entity 做比较
7935
- if (Cesium$7.defined(picked)) {
7936
- isOverFinishedVertex = !!this._findFinishedPointHit(picked.id);
8024
+ if (Cesium$7.defined(entity)) {
8025
+ try {
8026
+ // EventModule 传过来的是 Cesium Entity 实例(不是 Pick 对象),直接用于命中判定
8027
+ isOverFinishedVertex = !!this._findFinishedPointHit(entity);
8028
+ } catch (e) {
8029
+ // ignore
7937
8030
  }
7938
- } catch (e) {
7939
- // ignore
7940
8031
  }
7941
8032
  // 悬停在角点上:CSS `grab`(开放的手)暗示"可拖动";无浏览器支持时回退 `default`
7942
8033
  this._setCanvasCursor(isOverFinishedVertex ? 'grab' : 'default');
7943
8034
 
7944
- const lonLat = this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
8035
+ const lonLat = lngLat || this.screenToLonLat({ x: screenPos.x, y: screenPos.y });
7945
8036
  if (!lonLat) return;
7946
8037
  this._mousePos = lonLat;
8038
+
8039
+ // 非拖拽预览路径:用 rAF 合并 mousemove,与 render 帧同步,避免全球视角下
8040
+ // 多次 mousemove 之间重复重建几何/canvas。
7947
8041
  if (this._isMeasuring) {
7948
- this._updateMeasurement();
8042
+ this._scheduleMoveWork(() => this._updateMeasurement());
7949
8043
  }
7950
8044
  }
7951
8045
 
@@ -8725,7 +8819,7 @@ class MeasureTool extends BaseTool {
8725
8819
  verticalOrigin: Cesium$7.VerticalOrigin.BOTTOM,
8726
8820
  horizontalOrigin: Cesium$7.HorizontalOrigin.CENTER,
8727
8821
  pixelOffset: new Cesium$7.Cartesian2(0, -6),
8728
- disableDepthTestDistance: Number.POSITIVE_INFINITY
8822
+ disableDepthTestDistance: LabelDepthConfig.distance
8729
8823
  }
8730
8824
  });
8731
8825
  entity._lastText = text;
@@ -8751,11 +8845,8 @@ class MeasureTool extends BaseTool {
8751
8845
  const triangleHeight = 8;
8752
8846
  const rectHeight = 20;
8753
8847
 
8754
- // 先用临时 ctx 测量文字宽度,确定目标 canvas 尺寸
8755
- const measureCanvas = document.createElement('canvas');
8756
- const measureCtx = measureCanvas.getContext('2d');
8757
- measureCtx.font = font;
8758
- const textWidth = Math.max(1, Math.ceil(measureCtx.measureText(text).width));
8848
+ // 复用模块级 measure canvas 单例,避免每次 mousemove 都 document.createElement('canvas')
8849
+ const textWidth = _measureTextWidth(text, font);
8759
8850
  const rectWidth = textWidth + paddingX * 2;
8760
8851
  const width = rectWidth + borderWidth * 2;
8761
8852
  const height = rectHeight + triangleHeight + borderWidth * 2;
@@ -8877,7 +8968,7 @@ class MeasureTool extends BaseTool {
8877
8968
  style: Cesium$7.LabelStyle.FILL_AND_OUTLINE,
8878
8969
  verticalOrigin: Cesium$7.VerticalOrigin.BOTTOM,
8879
8970
  horizontalOrigin: Cesium$7.HorizontalOrigin.CENTER,
8880
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
8971
+ disableDepthTestDistance: LabelDepthConfig.distance,
8881
8972
  pixelOffset: new Cesium$7.Cartesian2(0, -10)
8882
8973
  }
8883
8974
  });
@@ -8892,7 +8983,7 @@ class MeasureTool extends BaseTool {
8892
8983
  pixelSize: 8,
8893
8984
  outlineColor: Cesium$7.Color.fromCssColorString(MEASURE_POINT_OUTLINE_COLOR),
8894
8985
  outlineWidth: 1,
8895
- disableDepthTestDistance: Number.POSITIVE_INFINITY
8986
+ disableDepthTestDistance: LabelDepthConfig.distance
8896
8987
  }
8897
8988
  });
8898
8989
  this._pointEntities.push(point);
@@ -10054,9 +10145,15 @@ class Zoom extends BaseControl {
10054
10145
  const camera = this._viewer.camera;
10055
10146
  // 以双击位置拾取到的地表点为缩放中心;拾取不到退回相机正下方
10056
10147
  let cartographic = camera.positionCartographic;
10148
+ // 锚点缩放需要"点击像素的视线方向":保持这条视线不变,
10149
+ // 锚点缩放后才仍投影在光标下方(相机中轴方向只在点击点恰为中心时成立)
10150
+ let rayDirection = camera.direction;
10057
10151
  if (movement?.position && this._viewer.scene?.globe) {
10058
10152
  const ray = camera.getPickRay(movement.position);
10059
10153
  const cartesian = ray ? this._viewer.scene.globe.pick(ray, this._viewer.scene) : null;
10154
+ if (ray) {
10155
+ rayDirection = ray.direction;
10156
+ }
10060
10157
  if (cartesian) {
10061
10158
  cartographic = Cesium$5.Cartographic.fromCartesian(cartesian);
10062
10159
  }
@@ -10064,7 +10161,89 @@ class Zoom extends BaseControl {
10064
10161
 
10065
10162
  const currentLevel = this._pendingLevel ?? this._getZoomLevel();
10066
10163
  const targetLevel = Math.max(0, Math.min(24, currentLevel + levelDelta));
10067
- this._flyToLevel(targetLevel, cartographic);
10164
+ this._flyToLevelKeepScreenPoint(targetLevel, cartographic, rayDirection);
10165
+ }
10166
+
10167
+ /**
10168
+ * 飞到指定层级,且保持 cartographic 对应的地面点在原屏幕位置(锚点缩放)。
10169
+ *
10170
+ * 与 _flyToLevel(以点为中心)不同:这里把相机沿"点击像素的视线方向"反方向
10171
+ * 退到目标高度——同一像素的视线方向只取决于相机姿态、与位置无关,因此相机
10172
+ * 沿该方向移动后,锚点仍投影在双击位置(光标下方),而不是被搬到屏幕中心。
10173
+ * 视线几乎水平(点击靠近地平线/天空)时退化为居中模式。
10174
+ *
10175
+ * @param {number} targetLevel 目标层级
10176
+ * @param {Cesium.Cartographic} cartographic 锚点(双击拾取的地面点)
10177
+ * @param {Cesium.Cartesian3} rayDirection 点击像素的视线方向(ECEF 单位向量)
10178
+ */
10179
+ _flyToLevelKeepScreenPoint(targetLevel, cartographic, rayDirection) {
10180
+ const camera = this._viewer.camera;
10181
+ const targetHeight = this._getHeightByZoomLevel(targetLevel);
10182
+ const pitch = camera.pitch;
10183
+
10184
+ let destination = null;
10185
+ // 相机俯角至少 ~60° 才做锚点缩放;更平时后退距离发散,观感也差,退化为中心模式
10186
+ const minPitch = Cesium$5.Math.toRadians(-60);
10187
+ if (pitch <= minPitch && rayDirection) {
10188
+ // 锚点处的本地"上"方向(ECEF)
10189
+ const cosLat = Math.cos(cartographic.latitude);
10190
+ const up = new Cesium$5.Cartesian3(
10191
+ cosLat * Math.cos(cartographic.longitude),
10192
+ cosLat * Math.sin(cartographic.longitude),
10193
+ Math.sin(cartographic.latitude)
10194
+ );
10195
+ // 视线向下的分量
10196
+ const rayUp = Cesium$5.Cartesian3.dot(rayDirection, up);
10197
+ // 相机沿视线到锚点的距离:垂直落差 / 视线向下分量
10198
+ const s = (targetHeight - cartographic.height) / (-rayUp);
10199
+ if (rayUp < -0.05 && Number.isFinite(s) && s > 0) {
10200
+ const anchor = Cesium$5.Cartesian3.fromRadians(
10201
+ cartographic.longitude,
10202
+ cartographic.latitude,
10203
+ cartographic.height
10204
+ );
10205
+ destination = Cesium$5.Cartesian3.add(
10206
+ anchor,
10207
+ Cesium$5.Cartesian3.multiplyByScalar(rayDirection, -s, new Cesium$5.Cartesian3()),
10208
+ new Cesium$5.Cartesian3()
10209
+ );
10210
+ }
10211
+ }
10212
+
10213
+ if (!destination) {
10214
+ destination = Cesium$5.Cartesian3.fromRadians(
10215
+ cartographic.longitude,
10216
+ cartographic.latitude,
10217
+ targetHeight
10218
+ );
10219
+ }
10220
+ this._flyToDestination(targetLevel, destination);
10221
+ }
10222
+
10223
+ /**
10224
+ * 飞到指定层级的目的地(目的地构造交给调用方),并记录 _pendingLevel
10225
+ * @param {number} targetLevel 目标层级
10226
+ * @param {Cesium.Cartesian3} destination 相机最终位置
10227
+ */
10228
+ _flyToDestination(targetLevel, destination) {
10229
+ const camera = this._viewer.camera;
10230
+
10231
+ this._pendingLevel = targetLevel;
10232
+ camera.flyTo({
10233
+ destination,
10234
+ orientation: {
10235
+ heading: camera.heading,
10236
+ pitch: camera.pitch,
10237
+ roll: camera.roll
10238
+ },
10239
+ duration: this._duration,
10240
+ complete: () => {
10241
+ if (this._pendingLevel === targetLevel) this._pendingLevel = null;
10242
+ },
10243
+ cancel: () => {
10244
+ if (this._pendingLevel === targetLevel) this._pendingLevel = null;
10245
+ }
10246
+ });
10068
10247
  }
10069
10248
 
10070
10249
  _zoomIn() {
@@ -10086,34 +10265,19 @@ class Zoom extends BaseControl {
10086
10265
  }
10087
10266
 
10088
10267
  /**
10089
- * 飞到指定层级(以给定经纬度为中心),并记录 _pendingLevel
10268
+ * 飞到指定层级(以给定经纬度为正下方中心),并记录 _pendingLevel
10090
10269
  * @param {number} targetLevel 目标层级
10091
10270
  * @param {Cesium.Cartographic} cartographic 目的地中心
10092
10271
  */
10093
10272
  _flyToLevel(targetLevel, cartographic) {
10094
- const camera = this._viewer.camera;
10095
- const targetHeight = this._getHeightByZoomLevel(targetLevel);
10096
-
10097
- this._pendingLevel = targetLevel;
10098
- camera.flyTo({
10099
- destination: Cesium$5.Cartesian3.fromRadians(
10273
+ this._flyToDestination(
10274
+ targetLevel,
10275
+ Cesium$5.Cartesian3.fromRadians(
10100
10276
  cartographic.longitude,
10101
10277
  cartographic.latitude,
10102
- targetHeight
10103
- ),
10104
- orientation: {
10105
- heading: camera.heading,
10106
- pitch: camera.pitch,
10107
- roll: camera.roll
10108
- },
10109
- duration: this._duration,
10110
- complete: () => {
10111
- if (this._pendingLevel === targetLevel) this._pendingLevel = null;
10112
- },
10113
- cancel: () => {
10114
- if (this._pendingLevel === targetLevel) this._pendingLevel = null;
10115
- }
10116
- });
10278
+ this._getHeightByZoomLevel(targetLevel)
10279
+ )
10280
+ );
10117
10281
  }
10118
10282
 
10119
10283
  _getZoomLevel() {
@@ -11888,6 +12052,7 @@ const xs3d = {
11888
12052
  Zoom,
11889
12053
  Compass,
11890
12054
  DistanceLegend,
12055
+ LabelDepthConfig,
11891
12056
  // 导出工具枚举
11892
12057
  DrawType,
11893
12058
  EditMode,