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 +69 -6
- package/js/drawFenceNew.js +177 -158
- package/js/index.js +19 -0
- package/js/utils/terrainHeight.js +229 -0
- package/js/utils/terrainProvider.js +125 -0
- package/package.json +5 -1
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:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
}
|
package/js/drawFenceNew.js
CHANGED
|
@@ -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
|
|
159
|
-
|
|
160
|
-
|
|
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:
|
|
249
|
-
arcType: Cesium.ArcType.
|
|
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 =
|
|
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:
|
|
359
|
-
arcType: Cesium.ArcType.
|
|
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:
|
|
368
|
+
positions: fenceEntity.wall.positions,
|
|
378
369
|
width: outlineWidth,
|
|
379
370
|
material: color,
|
|
380
|
-
clampToGround:
|
|
381
|
-
arcType: Cesium.ArcType.
|
|
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 =
|
|
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
|
|
582
|
-
|
|
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:
|
|
617
|
-
arcType: Cesium.ArcType.
|
|
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
|
-
|
|
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:
|
|
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
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1248
|
+
if (ellipseEntity) {
|
|
1249
|
+
map.entities.remove(ellipseEntity)
|
|
1250
|
+
ellipseEntity = null
|
|
1251
|
+
}
|
|
1223
1252
|
|
|
1224
|
-
|
|
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
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1244
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
1305
|
+
position: centerCartesian,
|
|
1293
1306
|
label: {
|
|
1294
|
-
text: '',
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|