huweili-cesium 1.2.87 → 1.2.89

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/index.vue CHANGED
@@ -16,6 +16,15 @@ import { useMapStore, MapLoadStatus } from './js/stores/mapStore.js'
16
16
  import { useEventBus } from './js/utils/useEventBus.js'
17
17
  import { processMapConfigColors } from './js/tileProviders.js'
18
18
  import { applyImageryLayers, resolveOnlineBasemap } from './js/utils/mapImagery.js'
19
+ import {
20
+ ensureTerrainConfig,
21
+ resolveTerrainConfig,
22
+ createTerrainProvider,
23
+ applyTerrain,
24
+ isTerrainEnabled,
25
+ syncTerrainConfig,
26
+ } from './js/utils/terrainProvider.js'
27
+ import { resampleAllMapFences } from './js/utils/terrainHeight.js'
19
28
  import { createCustomToolbarButtons } from './js/customToolbarButtons.js'
20
29
  const { merge } = objectUtils()
21
30
  const { mouseController, setInitialCameraView, syncCameraInteractionMode } = basicConfig()
@@ -89,6 +98,25 @@ const handleSceneModeChange = ({ mapId, sceneMode }) => {
89
98
  syncCameraInteractionMode(map, mapId, sceneMode)
90
99
  }
91
100
 
101
+ /**
102
+ * 处理地形开关变化(地图外 UI 通过事件总线触发)
103
+ *
104
+ * 用法示例(宿主页面):
105
+ * emit('map-terrain-changed', { mapId: 'home-main-map', show: true })
106
+ * emit('map-terrain-changed', { mapId: 'home-main-map', url: '/terrain' })
107
+ *
108
+ * @param {Object} param
109
+ * @param {string} param.mapId - 地图实例 ID
110
+ * @param {boolean} [param.show] - 是否启用地形
111
+ * @param {string} [param.url] - 地形服务地址(离线数据替换此 url 即可)
112
+ */
113
+ const handleTerrainChange = async ({ mapId, show, url }) => {
114
+ if (mapId !== props.mapId || !map || map.isDestroyed?.()) return
115
+ await syncTerrainConfig(map, MapConfig, { show, url })
116
+ await resampleAllMapFences(mapStore, props.mapId)
117
+ map.render()
118
+ }
119
+
92
120
  const cesiumContainer = ref(null)
93
121
  let map = null
94
122
  let customButtons = []
@@ -151,6 +179,7 @@ const initCesium = async () => {
151
179
 
152
180
  if (mapOptions) {
153
181
  mapOptions = processMapConfigColors(mapOptions)
182
+ ensureTerrainConfig(mapOptions)
154
183
  }
155
184
 
156
185
  try {
@@ -169,12 +198,16 @@ const initCesium = async () => {
169
198
  infoBox: mapOptions.control.infoBox, // 实体信息弹窗
170
199
  fullscreenButton: mapOptions.control.fullscreenButton, // 全屏按钮
171
200
  vrButton: mapOptions.control.vrButton, // VR 模式按钮
172
- terrainProvider: mapOptions.terrain.show // 地形服务;不开启时为 undefined
173
- ? await Cesium.CesiumTerrainProvider.fromUrl(mapOptions.terrain.url, {
174
- requestWaterMask: mapOptions.terrain.coastlineData, // 水体效果所需海岸线数据
175
- requestVertexNormals: mapOptions.terrain.lightingData, // 地形光照法线数据
176
- })
177
- : undefined,
201
+ terrainProvider: await (async () => {
202
+ const terrainConfig = resolveTerrainConfig(mapOptions)
203
+ if (!terrainConfig.show) return undefined
204
+ try {
205
+ return await createTerrainProvider(terrainConfig)
206
+ } catch (error) {
207
+ console.error('初始地形加载失败,将使用默认椭球地形:', error)
208
+ return undefined
209
+ }
210
+ })(),
178
211
  terrainShadows: Cesium.ShadowMode.RECEIVE_ONLY, // 地形只接收阴影,不投射,节省性能
179
212
  creditContainer: document.createElement('div'), // 用空容器替换默认版权水印区域
180
213
  }
@@ -228,6 +261,15 @@ const initCesium = async () => {
228
261
  console.warn('未找到可用底图,Cesium 将使用默认底图或空底图显示')
229
262
  }
230
263
 
264
+ const terrainConfig = resolveTerrainConfig(mapOptions)
265
+ if (terrainConfig.show) {
266
+ if (isTerrainEnabled(map)) {
267
+ console.log(`地形已启用,URL:${terrainConfig.url}`)
268
+ } else {
269
+ console.warn(`地形开关已开启但未加载成功,请检查 mapConfig.terrain.url:${terrainConfig.url}`)
270
+ }
271
+ }
272
+
231
273
  if (map.screenSpaceEventHandler && mapOptions.control.disableDoubleClick) {
232
274
  // 移除双击左键事件,防止地图缩放
233
275
  map.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
@@ -277,6 +319,25 @@ const initCesium = async () => {
277
319
  removeToolbarButtons, // 移除工具栏按钮
278
320
  },
279
321
  syncCameraInteractionMode: (mode) => syncCameraInteractionMode(map, props.mapId, mode), // 同步相机交互模式
322
+ terrain: {
323
+ getEnabled: () => isTerrainEnabled(map),
324
+ getConfig: () => resolveTerrainConfig(mapOptions),
325
+ setEnabled: async (show) => {
326
+ await syncTerrainConfig(map, MapConfig, { show })
327
+ map?.render()
328
+ return isTerrainEnabled(map)
329
+ },
330
+ setUrl: async (url) => {
331
+ await syncTerrainConfig(map, MapConfig, { url, show: true })
332
+ map?.render()
333
+ return isTerrainEnabled(map)
334
+ },
335
+ apply: async (config = {}) => {
336
+ await syncTerrainConfig(map, MapConfig, config)
337
+ map?.render()
338
+ return isTerrainEnabled(map)
339
+ },
340
+ },
280
341
  })
281
342
 
282
343
  map.resize() // 调整地图尺寸
@@ -414,6 +475,7 @@ onMounted(() => {
414
475
  mapStore.setCurrentMapId(props.mapId) // 初始化当前地图ID
415
476
  setupActivityListeners() // 初始化用户活动监听器,用于判断是否需要继续渲染地图,避免空闲超时导致的渲染暂停
416
477
  onEvent('map-scene-mode-changed', handleSceneModeChange) // 监听场景模式变化事件,用于同步相机交互模式
478
+ onEvent('map-terrain-changed', handleTerrainChange) // 监听地形开关变化(地图外 UI 控制)
417
479
  initCesium() // 初始化 Cesium 地图实例
418
480
  })
419
481
 
@@ -439,6 +501,7 @@ onUnmounted(() => {
439
501
  isMapActive = false
440
502
  stopRenderLoop() // 停止持续渲染循环,释放资源
441
503
  offEvent('map-scene-mode-changed', handleSceneModeChange) // 移除场景模式变化事件监听器
504
+ offEvent('map-terrain-changed', handleTerrainChange) // 移除地形开关变化事件监听器
442
505
  if (map) {
443
506
  teardownOrthographicWheelZoom(map) // 移除正交投影滚轮缩放事件监听器
444
507
  }
@@ -11,6 +11,15 @@
11
11
  import * as Cesium from 'cesium'
12
12
  import { useMapStore } from './stores/mapStore.js'
13
13
  import { BaseConfig } from './config/index.js'
14
+ import {
15
+ applyTerrainWallToEntity,
16
+ cartesianFootprintFromLngLat,
17
+ cartesianToLngLat,
18
+ generateCircleLngLatPositions,
19
+ getCenterLngLat,
20
+ resampleAllMapFences,
21
+ sampleTerrainHeights,
22
+ } from './utils/terrainHeight.js'
14
23
 
15
24
  export function drawFenceNew() {
16
25
  const mapStore = useMapStore()
@@ -126,14 +135,7 @@ export function drawFenceNew() {
126
135
  return cartesianToGroundCartesian(cartesian)
127
136
  }
128
137
 
129
- const cartesianToLngLatHeight = (cartesian) => {
130
- const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
131
- return {
132
- lng: Cesium.Math.toDegrees(cartographic.longitude),
133
- lat: Cesium.Math.toDegrees(cartographic.latitude),
134
- height: 0
135
- }
136
- }
138
+ const cartesianToLngLatHeight = (cartesian) => cartesianToLngLat(cartesian)
137
139
 
138
140
  const getPolylinePositions = () => {
139
141
  if (positions.length === 0) return []
@@ -155,23 +157,10 @@ export function drawFenceNew() {
155
157
  return wallPositions
156
158
  }
157
159
 
158
- const getPolygonCenter = (polygonPositions = []) => {
159
- const validPositions = polygonPositions.filter(Boolean)
160
- if (!validPositions.length) return null
161
-
162
- const lngLatList = validPositions.map(item => cartesianToLngLatHeight(item))
163
- const total = lngLatList.reduce((sum, item) => {
164
- sum.lng += item.lng
165
- sum.lat += item.lat
166
- sum.height += item.height || 0
167
- return sum
168
- }, { lng: 0, lat: 0, height: 0 })
169
-
170
- return Cesium.Cartesian3.fromDegrees(
171
- total.lng / lngLatList.length,
172
- total.lat / lngLatList.length,
173
- total.height / lngLatList.length
174
- )
160
+ const getPolygonCenterCartesian = (lngLatList, terrainHeights) => {
161
+ if (!lngLatList?.length) return null
162
+ const center = getCenterLngLat(lngLatList, terrainHeights)
163
+ return Cesium.Cartesian3.fromDegrees(center.lng, center.lat, center.height)
175
164
  }
176
165
 
177
166
  const createFenceNameLabel = (position, text = '') => {
@@ -245,8 +234,8 @@ export function drawFenceNew() {
245
234
  positions: new Cesium.CallbackProperty(() => getPolylinePositions(), false),
246
235
  width: outlineWidth,
247
236
  material: color,
248
- clampToGround: false,
249
- arcType: Cesium.ArcType.NONE
237
+ clampToGround: true,
238
+ arcType: Cesium.ArcType.GEODESIC
250
239
  }
251
240
  })
252
241
  }
@@ -324,7 +313,7 @@ export function drawFenceNew() {
324
313
  map.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
325
314
  }
326
315
 
327
- const finalize = () => {
316
+ const finalize = async () => {
328
317
  if (isFinished) return null
329
318
 
330
319
  if (positions.length < 3) {
@@ -335,9 +324,11 @@ export function drawFenceNew() {
335
324
  isFinished = true
336
325
  destroyHandler()
337
326
 
338
- const finalPositions = positions.map(item => Cesium.Cartesian3.clone(item))
327
+ const finalPositions = positions.map((item) => Cesium.Cartesian3.clone(item))
328
+ const lngLatList = finalPositions.map((item) => cartesianToLngLatHeight(item))
329
+ const terrainHeights = await sampleTerrainHeights(map, lngLatList)
339
330
  const finalWallPositions = [...finalPositions, finalPositions[0]]
340
- const lngLatPositions = finalPositions.map(item => cartesianToLngLatHeight(item))
331
+ const lngLatPositions = lngLatList.map(({ lng, lat }) => ({ lng, lat }))
341
332
 
342
333
  const fenceName = options.name || `多边形电子围栏-${options.id}`
343
334
  const fenceEntity = wallEntity || map.entities.add({
@@ -355,30 +346,30 @@ export function drawFenceNew() {
355
346
  positions: finalWallPositions,
356
347
  width: outlineWidth,
357
348
  material: color,
358
- clampToGround: false,
359
- arcType: Cesium.ArcType.NONE
349
+ clampToGround: true,
350
+ arcType: Cesium.ArcType.GEODESIC
360
351
  }
361
352
  })
362
353
 
363
354
  fenceEntity.name = fenceName
364
355
  fenceEntity.wall.positions = finalWallPositions
365
- fenceEntity.wall.maximumHeights = finalWallPositions.map(() => height)
366
- fenceEntity.wall.minimumHeights = finalWallPositions.map(() => 0)
367
356
  fenceEntity.wall.material = color.withAlpha(0.18)
368
357
  fenceEntity.wall.outline = true
369
358
  fenceEntity.wall.outlineColor = color
370
359
 
360
+ await applyTerrainWallToEntity(map, fenceEntity, lngLatList, height)
361
+
371
362
  if (polylineEntity) {
372
363
  map.entities.remove(polylineEntity)
373
364
  polylineEntity = null
374
365
  }
375
366
 
376
367
  fenceEntity.polyline = {
377
- positions: finalWallPositions,
368
+ positions: fenceEntity.wall.positions,
378
369
  width: outlineWidth,
379
370
  material: color,
380
- clampToGround: false,
381
- arcType: Cesium.ArcType.NONE
371
+ clampToGround: true,
372
+ arcType: Cesium.ArcType.GEODESIC
382
373
  }
383
374
 
384
375
  if (activePointEntity) {
@@ -394,7 +385,7 @@ export function drawFenceNew() {
394
385
  wallEntity = null
395
386
  cleanupPointEntities()
396
387
 
397
- const centerCartesian = getPolygonCenter(finalPositions)
388
+ const centerCartesian = getPolygonCenterCartesian(lngLatList, terrainHeights)
398
389
  const withLabel = options.withLabel !== false
399
390
  let labelEntity = null
400
391
  if (withLabel) {
@@ -406,6 +397,7 @@ export function drawFenceNew() {
406
397
 
407
398
  fenceEntity._originalOptions = {
408
399
  ...options,
400
+ type: 'polygon',
409
401
  positions: lngLatPositions,
410
402
  height,
411
403
  color: options.color || '#00ffff',
@@ -446,6 +438,7 @@ export function drawFenceNew() {
446
438
  options.onFinish(result)
447
439
  }
448
440
 
441
+ map.scene.requestRender()
449
442
  return result
450
443
  }
451
444
 
@@ -501,11 +494,11 @@ export function drawFenceNew() {
501
494
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
502
495
 
503
496
  handler.setInputAction(() => {
504
- finalize()
497
+ finalize().catch((error) => console.error('多边形围栏完成绘制失败:', error))
505
498
  }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
506
499
 
507
500
  handler.setInputAction(() => {
508
- finalize()
501
+ finalize().catch((error) => console.error('多边形围栏完成绘制失败:', error))
509
502
  }, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
510
503
 
511
504
  map.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
@@ -578,24 +571,9 @@ export function drawFenceNew() {
578
571
  const opacity = options.opacity ?? 0.35
579
572
  const outlineWidth = options.outlineWidth ?? 3
580
573
 
581
- const lngLatToCartesian = (lngLat) => {
582
- return Cesium.Cartesian3.fromDegrees(lngLat.lng, lngLat.lat, lngLat.height || 0)
583
- }
584
-
585
- const finalPositions = options.positions.map(pos => lngLatToCartesian(pos))
574
+ const lngLatList = options.positions.map((pos) => ({ lng: pos.lng, lat: pos.lat }))
575
+ const finalPositions = lngLatList.map(cartesianFootprintFromLngLat)
586
576
  const finalWallPositions = [...finalPositions, finalPositions[0]]
587
- const center = options.positions.reduce((sum, item) => {
588
- sum.lng += item.lng
589
- sum.lat += item.lat
590
- sum.height += item.height || 0
591
- return sum
592
- }, { lng: 0, lat: 0, height: 0 })
593
- const centerData = {
594
- lng: center.lng / options.positions.length,
595
- lat: center.lat / options.positions.length,
596
- height: center.height / options.positions.length
597
- }
598
- const centerCartesian = Cesium.Cartesian3.fromDegrees(centerData.lng, centerData.lat, centerData.height || 0)
599
577
  const fenceName = options.name || `多边形电子围栏-${options.id}`
600
578
 
601
579
  const fenceEntity = map.entities.add({
@@ -613,13 +591,20 @@ export function drawFenceNew() {
613
591
  positions: finalWallPositions,
614
592
  width: outlineWidth,
615
593
  material: color,
616
- clampToGround: false,
617
- arcType: Cesium.ArcType.NONE
594
+ clampToGround: true,
595
+ arcType: Cesium.ArcType.GEODESIC
618
596
  }
619
597
  })
620
598
 
621
599
  const withLabel = options.withLabel !== false
622
600
  let labelEntity = null
601
+ const centerData = {
602
+ lng: lngLatList.reduce((sum, p) => sum + p.lng, 0) / lngLatList.length,
603
+ lat: lngLatList.reduce((sum, p) => sum + p.lat, 0) / lngLatList.length,
604
+ height: 0,
605
+ }
606
+ const centerCartesian = Cesium.Cartesian3.fromDegrees(centerData.lng, centerData.lat, 0)
607
+
623
608
  if (withLabel) {
624
609
  labelEntity = map.entities.add({
625
610
  id: `${options.id}_fence_name_label`,
@@ -644,7 +629,8 @@ export function drawFenceNew() {
644
629
  fenceEntity._labelEntity = labelEntity
645
630
  fenceEntity._originalOptions = {
646
631
  ...options,
647
- positions: options.positions,
632
+ type: 'polygon',
633
+ positions: lngLatList,
648
634
  height,
649
635
  color: options.color || '#00ffff',
650
636
  opacity,
@@ -653,6 +639,32 @@ export function drawFenceNew() {
653
639
 
654
640
  mapStore.setGraphicMap(options.id, fenceEntity, options.mapId)
655
641
 
642
+ const applyHeights = async () => {
643
+ const terrainHeights = await sampleTerrainHeights(map, lngLatList)
644
+ await applyTerrainWallToEntity(map, fenceEntity, lngLatList, height)
645
+ const center = getCenterLngLat(lngLatList, terrainHeights)
646
+ centerData.lng = center.lng
647
+ centerData.lat = center.lat
648
+ centerData.height = center.height
649
+ if (labelEntity) {
650
+ labelEntity.position = Cesium.Cartesian3.fromDegrees(center.lng, center.lat, center.height)
651
+ }
652
+ if (options.zoomTo) {
653
+ map.flyTo(fenceEntity)
654
+ }
655
+ map.scene.requestRender()
656
+ if (typeof options.onReady === 'function') {
657
+ options.onReady({
658
+ id: options.id,
659
+ entity: fenceEntity,
660
+ labelEntity,
661
+ center: centerData,
662
+ })
663
+ }
664
+ }
665
+
666
+ applyHeights().catch((error) => console.error('多边形围栏回显贴地失败:', error))
667
+
656
668
  if (options.show === false) {
657
669
  fenceEntity.show = false
658
670
  if (labelEntity) {
@@ -660,15 +672,11 @@ export function drawFenceNew() {
660
672
  }
661
673
  }
662
674
 
663
- if (options.zoomTo) {
664
- map.flyTo(fenceEntity)
665
- }
666
-
667
675
  return {
668
676
  id: options.id,
669
677
  entity: fenceEntity,
670
678
  labelEntity,
671
- positions: options.positions,
679
+ positions: lngLatList,
672
680
  cartesianPositions: finalPositions,
673
681
  center: centerData,
674
682
  centerCartesian,
@@ -814,33 +822,37 @@ export function drawFenceNew() {
814
822
  const height = options.height ?? 2
815
823
  const opacity = options.opacity ?? 0.35
816
824
  const outlineWidth = options.outlineWidth ?? 3
817
-
818
- const centerCartesian = Cesium.Cartesian3.fromDegrees(
819
- options.center.lng,
820
- options.center.lat,
821
- options.center.height || 0
822
- )
823
-
825
+ const centerData = { lng: options.center.lng, lat: options.center.lat, height: 0 }
826
+ const circleLngLats = generateCircleLngLatPositions(centerData.lng, centerData.lat, options.radius)
827
+ const footprint = circleLngLats.map(cartesianFootprintFromLngLat)
828
+ const wallPositions = [...footprint, footprint[0]]
824
829
  const fenceName = options.name || `圆形电子围栏-${options.id}`
830
+
825
831
  const fenceEntity = map.entities.add({
826
832
  id: options.id,
827
833
  name: fenceName,
828
- position: centerCartesian,
829
- ellipse: {
830
- semiMajorAxis: options.radius,
831
- semiMinorAxis: options.radius,
832
- height: 0,
833
- extrudedHeight: height,
834
- material: color.withAlpha(0),
834
+ wall: {
835
+ positions: wallPositions,
836
+ maximumHeights: wallPositions.map(() => height),
837
+ minimumHeights: wallPositions.map(() => 0),
838
+ material: color.withAlpha(opacity),
835
839
  outline: true,
836
840
  outlineColor: color,
837
- outlineWidth,
838
- numberOfVerticalLines: 64
841
+ outlineWidth
842
+ },
843
+ polyline: {
844
+ positions: wallPositions,
845
+ width: outlineWidth,
846
+ material: color,
847
+ clampToGround: true,
848
+ arcType: Cesium.ArcType.GEODESIC
839
849
  }
840
850
  })
841
851
 
842
852
  const withLabel = options.withLabel !== false
843
853
  let labelEntity = null
854
+ const centerCartesian = Cesium.Cartesian3.fromDegrees(centerData.lng, centerData.lat, 0)
855
+
844
856
  if (withLabel) {
845
857
  labelEntity = map.entities.add({
846
858
  id: `${options.id}_fence_name_label`,
@@ -865,7 +877,8 @@ export function drawFenceNew() {
865
877
  fenceEntity._labelEntity = labelEntity
866
878
  fenceEntity._originalOptions = {
867
879
  ...options,
868
- center: options.center,
880
+ type: 'circle',
881
+ center: centerData,
869
882
  radius: options.radius,
870
883
  height,
871
884
  color: options.color || '#00ffff',
@@ -875,6 +888,29 @@ export function drawFenceNew() {
875
888
 
876
889
  mapStore.setGraphicMap(options.id, fenceEntity, options.mapId)
877
890
 
891
+ const applyHeights = async () => {
892
+ await applyTerrainWallToEntity(map, fenceEntity, circleLngLats, height)
893
+ const centerHeights = await sampleTerrainHeights(map, [centerData])
894
+ centerData.height = centerHeights[0] || 0
895
+ if (labelEntity) {
896
+ labelEntity.position = Cesium.Cartesian3.fromDegrees(centerData.lng, centerData.lat, centerData.height)
897
+ }
898
+ if (options.zoomTo) {
899
+ map.flyTo(fenceEntity)
900
+ }
901
+ map.scene.requestRender()
902
+ if (typeof options.onReady === 'function') {
903
+ options.onReady({
904
+ id: options.id,
905
+ entity: fenceEntity,
906
+ labelEntity,
907
+ center: centerData,
908
+ })
909
+ }
910
+ }
911
+
912
+ applyHeights().catch((error) => console.error('圆形围栏回显贴地失败:', error))
913
+
878
914
  if (options.show === false) {
879
915
  fenceEntity.show = false
880
916
  if (labelEntity) {
@@ -882,16 +918,12 @@ export function drawFenceNew() {
882
918
  }
883
919
  }
884
920
 
885
- if (options.zoomTo) {
886
- map.flyTo(fenceEntity)
887
- }
888
-
889
921
  return {
890
922
  id: options.id,
891
923
  entity: fenceEntity,
892
924
  labelEntity,
893
- center: options.center,
894
- centerCartesian,
925
+ center: centerData,
926
+ centerCartesian: Cesium.Cartesian3.fromDegrees(centerData.lng, centerData.lat, centerData.height),
895
927
  radius: options.radius,
896
928
  height,
897
929
  name: fenceName
@@ -1030,15 +1062,7 @@ export function drawFenceNew() {
1030
1062
  * @param {Cartesian3} cartesian - 世界3D坐标
1031
1063
  * @returns {Object} - 经纬度高度对象 {lng: 经度, lat: 纬度, height: 高度}
1032
1064
  */
1033
- const cartesianToLngLatHeight = (cartesian) => {
1034
- // 先将Cartesian3转换为Cartographic(弧度制经纬度)
1035
- const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
1036
- return {
1037
- lng: Cesium.Math.toDegrees(cartographic.longitude), // 弧度 → 角度(经度)
1038
- lat: Cesium.Math.toDegrees(cartographic.latitude), // 弧度 → 角度(纬度)
1039
- height: 0 // 圆形围栏绘制点统一贴地
1040
- }
1041
- }
1065
+ const cartesianToLngLatHeight = (cartesian) => cartesianToLngLat(cartesian)
1042
1066
 
1043
1067
  /**
1044
1068
  * 计算当前围栏的半径(圆心到边界点的地表距离)
@@ -1201,60 +1225,54 @@ export function drawFenceNew() {
1201
1225
  * 【重要函数:完成绘制,生成最终围栏
1202
1226
  * 当用户第二次点击或右键点击时调用
1203
1227
  */
1204
- const finalize = () => {
1205
- // 如果已经完成过了,直接返回,防止重复执行
1228
+ const finalize = async () => {
1206
1229
  if (isFinished) return null
1207
1230
 
1208
- // 检查是否满足绘制条件:必须有圆心,且半径大于0
1209
1231
  const radius = getRadius()
1210
1232
  if (!centerPosition || radius <= 0) {
1211
1233
  console.warn('至少需要 2 个点才能形成圆形电子围栏')
1212
1234
  return null
1213
1235
  }
1214
1236
 
1215
- // ==================== 步骤1:标记绘制完成,开始收尾工作
1216
1237
  isFinished = true
1217
- destroyHandler() // 销毁事件监听器
1238
+ destroyHandler()
1239
+
1240
+ const finalCenter = Cesium.Cartesian3.clone(centerPosition)
1241
+ const finalEdge = Cesium.Cartesian3.clone(edgePosition)
1242
+ const fenceName = options.name || `圆形电子围栏-${options.id}`
1243
+ const centerData = cartesianToLngLatHeight(finalCenter)
1244
+ const circleLngLats = generateCircleLngLatPositions(centerData.lng, centerData.lat, radius)
1245
+ const footprint = circleLngLats.map(cartesianFootprintFromLngLat)
1246
+ const wallPositions = [...footprint, footprint[0]]
1218
1247
 
1219
- // ==================== 步骤2:保存最终数据(clone一份防止被修改)
1220
- const finalCenter = Cesium.Cartesian3.clone(centerPosition) // 最终圆心
1221
- const finalEdge = Cesium.Cartesian3.clone(edgePosition) // 最终边界点
1222
- const fenceName = options.name || `圆形电子围栏-${options.id}` // 围栏名称
1248
+ if (ellipseEntity) {
1249
+ map.entities.remove(ellipseEntity)
1250
+ ellipseEntity = null
1251
+ }
1223
1252
 
1224
- // ==================== 步骤3:创建或修改围栏实体
1225
- // 如果之前已有预览的ellipseEntity,就复用它;否则创建新的
1226
- const fenceEntity = ellipseEntity || map.entities.add({
1253
+ const fenceEntity = map.entities.add({
1227
1254
  id: options.id,
1228
1255
  name: fenceName,
1229
- position: finalCenter,
1230
- ellipse: {
1231
- semiMajorAxis: radius,
1232
- semiMinorAxis: radius,
1233
- height: 0,
1234
- extrudedHeight: height,
1235
- material: color.withAlpha(0),
1256
+ wall: {
1257
+ positions: wallPositions,
1258
+ maximumHeights: wallPositions.map(() => height),
1259
+ minimumHeights: wallPositions.map(() => 0),
1260
+ material: color.withAlpha(0.18),
1236
1261
  outline: true,
1237
1262
  outlineColor: color,
1238
- outlineWidth,
1239
- numberOfVerticalLines: 64
1263
+ outlineWidth
1264
+ },
1265
+ polyline: {
1266
+ positions: wallPositions,
1267
+ width: outlineWidth,
1268
+ material: color,
1269
+ clampToGround: true,
1270
+ arcType: Cesium.ArcType.GEODESIC
1240
1271
  }
1241
1272
  })
1242
1273
 
1243
- // 确保围栏实体的所有属性都是最终值(避免使用静态值,不再是CallbackProperty)
1244
- fenceEntity.name = fenceName
1245
- fenceEntity.position = finalCenter
1246
- fenceEntity.ellipse.semiMajorAxis = radius
1247
- fenceEntity.ellipse.semiMinorAxis = radius
1248
- fenceEntity.ellipse.height = 0
1249
- fenceEntity.ellipse.extrudedHeight = height
1250
- fenceEntity.ellipse.material = color.withAlpha(0)
1251
- fenceEntity.ellipse.outline = true
1252
- fenceEntity.ellipse.outlineColor = color
1253
- fenceEntity.ellipse.outlineWidth = outlineWidth
1254
- fenceEntity.ellipse.numberOfVerticalLines = 64
1255
-
1256
- // ==================== 步骤4:清理临时实体
1257
- // 移除预览用的点、提示文字等临时实体(不再需要了)
1274
+ await applyTerrainWallToEntity(map, fenceEntity, circleLngLats, height)
1275
+
1258
1276
  ;[centerPointEntity, activePointEntity, tipEntity].forEach((entity) => {
1259
1277
  if (entity) {
1260
1278
  map.entities.remove(entity)
@@ -1263,12 +1281,15 @@ export function drawFenceNew() {
1263
1281
  centerPointEntity = null
1264
1282
  activePointEntity = null
1265
1283
  tipEntity = null
1266
- ellipseEntity = null
1267
1284
 
1268
- // ==================== 步骤5:保存原始配置(方便后续修改或回显
1285
+ const centerHeights = await sampleTerrainHeights(map, [centerData])
1286
+ const centerHeight = centerHeights[0] || 0
1287
+ const centerCartesian = Cesium.Cartesian3.fromDegrees(centerData.lng, centerData.lat, centerHeight)
1288
+
1269
1289
  fenceEntity._originalOptions = {
1270
1290
  ...options,
1271
- center: cartesianToLngLatHeight(finalCenter),
1291
+ type: 'circle',
1292
+ center: { lng: centerData.lng, lat: centerData.lat },
1272
1293
  edge: cartesianToLngLatHeight(finalEdge),
1273
1294
  radius,
1274
1295
  height,
@@ -1277,21 +1298,13 @@ export function drawFenceNew() {
1277
1298
  outlineWidth
1278
1299
  }
1279
1300
 
1280
- // ==================== 步骤6:将围栏存入地图存储(便于管理
1281
1301
  mapStore.setGraphicMap(options.id, fenceEntity, options.mapId)
1282
1302
 
1283
- // ==================== 步骤7:如果配置了zoomTo,就飞过去查看
1284
- if (options.zoomTo) {
1285
- map.flyTo(fenceEntity)
1286
- }
1287
-
1288
- // ==================== 步骤8:创建围栏名称标签
1289
- const centerData = cartesianToLngLatHeight(finalCenter)
1290
1303
  nameLabelEntity = map.entities.add({
1291
1304
  id: `${options.id}_draw_circle_name_label`,
1292
- position: finalCenter,
1305
+ position: centerCartesian,
1293
1306
  label: {
1294
- text: '', // 暂时为空,可通过updateCircleFenceName来设置
1307
+ text: '',
1295
1308
  font: BaseConfig.fenceLabelFont,
1296
1309
  fillColor: Cesium.Color.WHITE,
1297
1310
  style: Cesium.LabelStyle.FILL,
@@ -1305,27 +1318,27 @@ export function drawFenceNew() {
1305
1318
  distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.POSITIVE_INFINITY)
1306
1319
  }
1307
1320
  })
1308
- // 将标签实体挂载到围栏实体上,方便后续管理
1309
1321
  fenceEntity._labelEntity = nameLabelEntity
1310
-
1311
- // ==================== 步骤9:构造返回结果对象
1322
+
1323
+ if (options.zoomTo) {
1324
+ map.flyTo(fenceEntity)
1325
+ }
1326
+
1312
1327
  const result = {
1313
1328
  id: options.id,
1314
1329
  entity: fenceEntity,
1315
- center: centerData,
1330
+ center: { lng: centerData.lng, lat: centerData.lat },
1316
1331
  edge: cartesianToLngLatHeight(finalEdge),
1317
- centerCartesian: finalCenter,
1332
+ centerCartesian,
1318
1333
  edgeCartesian: finalEdge,
1319
1334
  radius,
1320
1335
  height,
1321
- // 数据库存储用的数据
1322
1336
  dbData: {
1323
1337
  id: options.id,
1324
1338
  type: 'circle',
1325
1339
  center: {
1326
1340
  lng: centerData.lng,
1327
1341
  lat: centerData.lat,
1328
- height: centerData.height || 0
1329
1342
  },
1330
1343
  radius: radius,
1331
1344
  height: height,
@@ -1337,11 +1350,11 @@ export function drawFenceNew() {
1337
1350
  }
1338
1351
  }
1339
1352
 
1340
- // ==================== 步骤10:触发完成回调
1341
1353
  if (typeof options.onFinish === 'function') {
1342
1354
  options.onFinish(result)
1343
1355
  }
1344
1356
 
1357
+ map.scene.requestRender()
1345
1358
  return result
1346
1359
  }
1347
1360
 
@@ -1398,7 +1411,7 @@ export function drawFenceNew() {
1398
1411
  // 情况2:已有圆心 → 这是第二次点击,完成绘制
1399
1412
  edgePosition = Cesium.Cartesian3.clone(cartesian)
1400
1413
  emitChange()
1401
- finalize() // 完成绘制
1414
+ finalize().catch((error) => console.error('圆形围栏完成绘制失败:', error))
1402
1415
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
1403
1416
 
1404
1417
  // ==================== 事件2:鼠标移动 ====================
@@ -1417,7 +1430,7 @@ export function drawFenceNew() {
1417
1430
 
1418
1431
  // ==================== 事件3:鼠标右键点击 ====================
1419
1432
  handler.setInputAction(() => {
1420
- finalize() // 右键点击也可以完成绘制
1433
+ finalize().catch((error) => console.error('圆形围栏完成绘制失败:', error))
1421
1434
  }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
1422
1435
 
1423
1436
  // ==================== 返回绘制控制器 ====================
@@ -1460,7 +1473,12 @@ export function drawFenceNew() {
1460
1473
  const labelEntity = entity._labelEntity || map.entities.getById(`${id}_fence_name_label`)
1461
1474
  if (labelEntity?.label) {
1462
1475
  labelEntity.label.text = finalName
1463
- labelEntity.position = entity.position
1476
+ const center = entity._originalOptions?.center
1477
+ if (center && !labelEntity.position) {
1478
+ const cartographic = Cesium.Cartographic.fromDegrees(center.lng, center.lat)
1479
+ const terrainHeight = map.scene.sampleHeight(cartographic) || 0
1480
+ labelEntity.position = Cesium.Cartesian3.fromDegrees(center.lng, center.lat, terrainHeight)
1481
+ }
1464
1482
  }
1465
1483
  entity._labelEntity = labelEntity || null
1466
1484
  return true
@@ -1543,6 +1561,7 @@ export function drawFenceNew() {
1543
1561
  drawCircleFence,
1544
1562
  updateCircleFenceName,
1545
1563
  destroyCircleFence,
1546
- destroyAllFence
1564
+ destroyAllFence,
1565
+ resampleAllFenceHeights: (mapId) => resampleAllMapFences(mapStore, mapId),
1547
1566
  }
1548
1567
  }
package/js/index.js CHANGED
@@ -58,3 +58,22 @@ export {
58
58
  export { getAddressByLocation, setGeocodeProvider } from './api/gaode.js'
59
59
 
60
60
  export { objectUtils } from './utils/cesium/object.js'
61
+
62
+ export {
63
+ DEFAULT_TERRAIN_CONFIG,
64
+ resolveTerrainConfig,
65
+ ensureTerrainConfig,
66
+ createTerrainProvider,
67
+ applyTerrain,
68
+ isTerrainEnabled,
69
+ syncTerrainConfig,
70
+ } from './utils/terrainProvider.js'
71
+
72
+ export {
73
+ sampleTerrainHeights,
74
+ applyTerrainWallToEntity,
75
+ generateCircleLngLatPositions,
76
+ resampleAllMapFences,
77
+ resampleFenceEntityHeights,
78
+ isTerrainActive,
79
+ } from './utils/terrainHeight.js'
@@ -0,0 +1,229 @@
1
+ /**
2
+ * 地形高度采样与围栏墙高度计算
3
+ */
4
+ import * as Cesium from 'cesium'
5
+
6
+ const DEFAULT_SAMPLE_LEVEL = 12
7
+ export const DEFAULT_CIRCLE_SEGMENTS = 64
8
+
9
+ export function isTerrainActive(map) {
10
+ if (!map?.terrainProvider) return false
11
+ return !(map.terrainProvider instanceof Cesium.EllipsoidTerrainProvider)
12
+ }
13
+
14
+ export function lngLatToCartographic(lngLat) {
15
+ return Cesium.Cartographic.fromDegrees(lngLat.lng, lngLat.lat, 0)
16
+ }
17
+
18
+ export function cartesianFootprintFromLngLat(lngLat) {
19
+ return Cesium.Cartesian3.fromDegrees(lngLat.lng, lngLat.lat, 0)
20
+ }
21
+
22
+ export function cartesianToLngLat(cartesian) {
23
+ const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
24
+ return {
25
+ lng: Cesium.Math.toDegrees(cartographic.longitude),
26
+ lat: Cesium.Math.toDegrees(cartographic.latitude),
27
+ }
28
+ }
29
+
30
+ export function buildWallHeights(terrainHeights, fenceHeight) {
31
+ const minimumHeights = terrainHeights.map((h) => h || 0)
32
+ const maximumHeights = minimumHeights.map((h) => h + fenceHeight)
33
+ return { minimumHeights, maximumHeights }
34
+ }
35
+
36
+ export function closeRing(values) {
37
+ if (!values?.length) return []
38
+ return [...values, values[0]]
39
+ }
40
+
41
+ /**
42
+ * 批量采样地形椭球海拔(米)
43
+ * @param {Cesium.Viewer} map
44
+ * @param {Array<{lng:number, lat:number}>} lngLatList
45
+ * @param {{ level?: number }} [options]
46
+ * @returns {Promise<number[]>}
47
+ */
48
+ export async function sampleTerrainHeights(map, lngLatList, options = {}) {
49
+ const level = options.level ?? DEFAULT_SAMPLE_LEVEL
50
+ if (!lngLatList?.length) return []
51
+
52
+ if (!isTerrainActive(map)) {
53
+ return lngLatList.map(() => 0)
54
+ }
55
+
56
+ const cartographics = lngLatList.map(lngLatToCartographic)
57
+
58
+ try {
59
+ const sampled = await Cesium.sampleTerrainMostDetailed(map.terrainProvider, level, cartographics)
60
+ return sampled.map((cartographic) => resolveCartographicHeight(map, cartographic))
61
+ } catch (error) {
62
+ console.warn('地形高度采样失败,尝试同步采样:', error)
63
+ return cartographics.map((cartographic) => resolveCartographicHeight(map, cartographic))
64
+ }
65
+ }
66
+
67
+ function resolveCartographicHeight(map, cartographic) {
68
+ if (Cesium.defined(cartographic.height) && Number.isFinite(cartographic.height)) {
69
+ return cartographic.height
70
+ }
71
+ const syncHeight = map.scene.sampleHeight(cartographic)
72
+ if (Cesium.defined(syncHeight) && Number.isFinite(syncHeight)) {
73
+ return syncHeight
74
+ }
75
+ return 0
76
+ }
77
+
78
+ /**
79
+ * 根据圆心、半径生成圆周经纬度点
80
+ */
81
+ export function generateCircleLngLatPositions(
82
+ centerLng,
83
+ centerLat,
84
+ radiusMeters,
85
+ segments = DEFAULT_CIRCLE_SEGMENTS,
86
+ ) {
87
+ const positions = []
88
+ const center = Cesium.Cartographic.fromDegrees(centerLng, centerLat)
89
+ const ellipsoid = Cesium.Ellipsoid.WGS84
90
+ const centerCartesian = ellipsoid.cartographicToCartesian(center)
91
+ const transform = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian)
92
+
93
+ for (let i = 0; i < segments; i += 1) {
94
+ const bearing = (2 * Math.PI * i) / segments
95
+ const east = radiusMeters * Math.sin(bearing)
96
+ const north = radiusMeters * Math.cos(bearing)
97
+ const localPoint = new Cesium.Cartesian3(east, north, 0)
98
+ const worldPoint = Cesium.Matrix4.multiplyByPoint(transform, localPoint, new Cesium.Cartesian3())
99
+ const cartographic = ellipsoid.cartesianToCartographic(worldPoint)
100
+ positions.push({
101
+ lng: Cesium.Math.toDegrees(cartographic.longitude),
102
+ lat: Cesium.Math.toDegrees(cartographic.latitude),
103
+ })
104
+ }
105
+
106
+ return positions
107
+ }
108
+
109
+ export function getAverageTerrainHeight(terrainHeights) {
110
+ if (!terrainHeights?.length) return 0
111
+ return terrainHeights.reduce((sum, h) => sum + (h || 0), 0) / terrainHeights.length
112
+ }
113
+
114
+ export function getCenterLngLat(lngLatList, terrainHeights) {
115
+ const total = lngLatList.reduce(
116
+ (sum, item) => {
117
+ sum.lng += item.lng
118
+ sum.lat += item.lat
119
+ return sum
120
+ },
121
+ { lng: 0, lat: 0 },
122
+ )
123
+
124
+ return {
125
+ lng: total.lng / lngLatList.length,
126
+ lat: total.lat / lngLatList.length,
127
+ height: getAverageTerrainHeight(terrainHeights),
128
+ }
129
+ }
130
+
131
+ /**
132
+ * 将采样高度应用到 wall 实体(footprint 用 height=0,起伏由 min/max 控制)
133
+ */
134
+ export async function applyTerrainWallToEntity(map, entity, lngLatList, fenceHeight, options = {}) {
135
+ const closed = options.closed !== false
136
+ const terrainHeights = await sampleTerrainHeights(map, lngLatList, options)
137
+ const ringHeights = closed ? closeRing(terrainHeights) : terrainHeights
138
+ const footprint = lngLatList.map(cartesianFootprintFromLngLat)
139
+ const wallPositions = closed ? closeRing(footprint) : footprint
140
+ const { minimumHeights, maximumHeights } = buildWallHeights(ringHeights, fenceHeight)
141
+
142
+ if (!entity.wall) {
143
+ entity.wall = {}
144
+ }
145
+
146
+ entity.wall.positions = wallPositions
147
+ entity.wall.minimumHeights = minimumHeights
148
+ entity.wall.maximumHeights = maximumHeights
149
+
150
+ if (entity.polyline) {
151
+ entity.polyline.positions = wallPositions
152
+ entity.polyline.clampToGround = true
153
+ }
154
+
155
+ if (entity.ellipse) {
156
+ entity.ellipse = undefined
157
+ entity.position = undefined
158
+ }
159
+
160
+ map.scene?.requestRender?.()
161
+
162
+ return {
163
+ terrainHeights,
164
+ minimumHeights,
165
+ maximumHeights,
166
+ wallPositions,
167
+ }
168
+ }
169
+
170
+ /**
171
+ * 地形变更后,根据 _originalOptions 重新采样单个围栏
172
+ */
173
+ export async function resampleFenceEntityHeights(map, entity) {
174
+ const opts = entity?._originalOptions
175
+ if (!opts || !entity) return false
176
+
177
+ const fenceHeight = opts.height ?? 2
178
+
179
+ if (opts.center && typeof opts.radius === 'number') {
180
+ const circleLngLats = generateCircleLngLatPositions(opts.center.lng, opts.center.lat, opts.radius)
181
+ await applyTerrainWallToEntity(map, entity, circleLngLats, fenceHeight)
182
+
183
+ const centerHeights = await sampleTerrainHeights(map, [{ lng: opts.center.lng, lat: opts.center.lat }])
184
+ const centerHeight = centerHeights[0] || 0
185
+ if (entity._labelEntity) {
186
+ entity._labelEntity.position = Cesium.Cartesian3.fromDegrees(
187
+ opts.center.lng,
188
+ opts.center.lat,
189
+ centerHeight,
190
+ )
191
+ }
192
+ return true
193
+ }
194
+
195
+ if (opts.positions?.length >= 3) {
196
+ const lngLatList = opts.positions.map((p) => ({ lng: p.lng, lat: p.lat }))
197
+ await applyTerrainWallToEntity(map, entity, lngLatList, fenceHeight)
198
+ const terrainHeights = await sampleTerrainHeights(map, lngLatList)
199
+ const center = getCenterLngLat(lngLatList, terrainHeights)
200
+ if (entity._labelEntity) {
201
+ entity._labelEntity.position = Cesium.Cartesian3.fromDegrees(center.lng, center.lat, center.height)
202
+ }
203
+ return true
204
+ }
205
+
206
+ return false
207
+ }
208
+
209
+ /**
210
+ * 重新采样地图上所有围栏实体的高度
211
+ */
212
+ export async function resampleAllMapFences(mapStore, mapId) {
213
+ const map = mapStore.getMap(mapId)
214
+ if (!map) return 0
215
+
216
+ const targetMapId = mapId != null ? mapId : mapStore.getCurrentMapId()
217
+ const context = mapStore.mapContexts.get(targetMapId)
218
+ if (!context?.graphicMap) return 0
219
+
220
+ let count = 0
221
+ for (const entity of context.graphicMap.values()) {
222
+ if (entity?.wall && entity._originalOptions) {
223
+ const updated = await resampleFenceEntityHeights(map, entity)
224
+ if (updated) count += 1
225
+ }
226
+ }
227
+
228
+ return count
229
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * 地形服务加载与切换(支持 mapConfig.terrain 配置及地图外开关)
3
+ */
4
+ import * as Cesium from 'cesium'
5
+
6
+ /** mapConfig.terrain 缺省时的默认值(show 仅在为 undefined 时生效) */
7
+ export const DEFAULT_TERRAIN_CONFIG = {
8
+ show: true,
9
+ url: 'https://data.mars3d.cn/terrain',
10
+ coastlineData: false,
11
+ lightingData: false,
12
+ }
13
+
14
+ /**
15
+ * 解析地形配置(合并 mapConfig.terrain 与默认值)
16
+ * @param {Object} mapOptions
17
+ * @returns {{ show: boolean, url: string, coastlineData: boolean, lightingData: boolean }}
18
+ */
19
+ export function resolveTerrainConfig(mapOptions) {
20
+ const terrain = mapOptions?.terrain || {}
21
+ return {
22
+ show: terrain.show ?? DEFAULT_TERRAIN_CONFIG.show,
23
+ url: terrain.url ?? DEFAULT_TERRAIN_CONFIG.url,
24
+ coastlineData: terrain.coastlineData ?? DEFAULT_TERRAIN_CONFIG.coastlineData,
25
+ lightingData: terrain.lightingData ?? DEFAULT_TERRAIN_CONFIG.lightingData,
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 补全 mapOptions.terrain,避免宿主未配置时无法加载
31
+ * @param {Object} mapOptions
32
+ */
33
+ export function ensureTerrainConfig(mapOptions) {
34
+ if (!mapOptions) return mapOptions
35
+ if (!mapOptions.terrain) {
36
+ mapOptions.terrain = { ...DEFAULT_TERRAIN_CONFIG }
37
+ return mapOptions
38
+ }
39
+ const resolved = resolveTerrainConfig(mapOptions)
40
+ mapOptions.terrain = { ...mapOptions.terrain, ...resolved }
41
+ return mapOptions
42
+ }
43
+
44
+ /**
45
+ * 创建 Cesium 地形 Provider
46
+ * @param {Object} terrainConfig
47
+ * @returns {Promise<Cesium.CesiumTerrainProvider|undefined>}
48
+ */
49
+ export async function createTerrainProvider(terrainConfig) {
50
+ const config = resolveTerrainConfig({ terrain: terrainConfig })
51
+ if (!config.show || !config.url) return undefined
52
+
53
+ return Cesium.CesiumTerrainProvider.fromUrl(config.url, {
54
+ requestWaterMask: !!config.coastlineData,
55
+ requestVertexNormals: !!config.lightingData,
56
+ })
57
+ }
58
+
59
+ /**
60
+ * 将地形应用到 Viewer(关闭时使用椭球地形)
61
+ * @param {Cesium.Viewer} viewer
62
+ * @param {Object} terrainConfig
63
+ * @returns {Promise<boolean>} 是否成功加载真实地形
64
+ */
65
+ export async function applyTerrain(viewer, terrainConfig) {
66
+ if (!viewer || viewer.isDestroyed?.()) return false
67
+
68
+ const config = resolveTerrainConfig({ terrain: terrainConfig })
69
+
70
+ if (!config.show) {
71
+ viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider()
72
+ viewer.scene?.requestRender?.()
73
+ return false
74
+ }
75
+
76
+ if (!config.url) {
77
+ console.warn('地形已开启但未配置 url,请在 mapConfig.js 的 terrain.url 中设置地形服务地址')
78
+ viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider()
79
+ viewer.scene?.requestRender?.()
80
+ return false
81
+ }
82
+
83
+ try {
84
+ const provider = await createTerrainProvider(config)
85
+ viewer.terrainProvider = provider
86
+ viewer.scene?.requestRender?.()
87
+ console.log(`地形已加载:${config.url}`)
88
+ return true
89
+ } catch (error) {
90
+ console.error('地形加载失败,将使用默认椭球地形:', error)
91
+ viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider()
92
+ viewer.scene?.requestRender?.()
93
+ return false
94
+ }
95
+ }
96
+
97
+ /**
98
+ * 当前 Viewer 是否在使用真实地形(非椭球)
99
+ * @param {Cesium.Viewer} viewer
100
+ */
101
+ export function isTerrainEnabled(viewer) {
102
+ if (!viewer?.terrainProvider) return false
103
+ return !(viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider)
104
+ }
105
+
106
+ /**
107
+ * 同步 mapConfig.terrain 并应用到 Viewer
108
+ * @param {Cesium.Viewer} viewer
109
+ * @param {Object} mapConfig - 通常为 window.MapConfig / MapConfig
110
+ * @param {{ show?: boolean, url?: string }} patch
111
+ */
112
+ export async function syncTerrainConfig(viewer, mapConfig, patch = {}) {
113
+ if (!mapConfig) {
114
+ return applyTerrain(viewer, patch)
115
+ }
116
+
117
+ if (!mapConfig.terrain) {
118
+ mapConfig.terrain = { ...DEFAULT_TERRAIN_CONFIG }
119
+ }
120
+
121
+ if (patch.show !== undefined) mapConfig.terrain.show = patch.show
122
+ if (patch.url !== undefined) mapConfig.terrain.url = patch.url
123
+
124
+ return applyTerrain(viewer, mapConfig.terrain)
125
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huweili-cesium",
3
- "version": "1.2.87",
3
+ "version": "1.2.89",
4
4
  "description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -81,6 +81,10 @@
81
81
  "./utils/basemapSwitcher.js": "./js/utils/basemapSwitcher.js",
82
82
  "./utils/mapImagery": "./js/utils/mapImagery.js",
83
83
  "./utils/mapImagery.js": "./js/utils/mapImagery.js",
84
+ "./utils/terrainProvider": "./js/utils/terrainProvider.js",
85
+ "./utils/terrainProvider.js": "./js/utils/terrainProvider.js",
86
+ "./utils/terrainHeight": "./js/utils/terrainHeight.js",
87
+ "./utils/terrainHeight.js": "./js/utils/terrainHeight.js",
84
88
  "./utils/eventBus": "./js/utils/eventBus.js",
85
89
  "./utils/eventBus.js": "./js/utils/eventBus.js",
86
90
  "./utils/useEventBus": "./js/utils/useEventBus.js",