huweili-cesium 1.2.6 → 1.2.8

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/README.md CHANGED
@@ -86,7 +86,18 @@ export default defineConfig({
86
86
 
87
87
  事件:`@onload`,参数 `{ map, center, mapId, toolbar }`(`map` 为 Cesium `Viewer` 实例;`toolbar` 可在加载后动态增删按钮)。
88
88
 
89
- ### 各项目自定义工具栏
89
+ ### 为何不用 Cesium 内置 2D 模式
90
+
91
+ Viewer 固定为 `SCENE3D`,业务上的「2D」是 **正交相机 + 俯视**(`basis.js` / `cameraInteraction.js`),不切换 `SCENE2D` / `COLUMBUS_VIEW`。简要优点:
92
+
93
+ - **2D/3D 切换更顺**:无场景变形(morph),透视与正交互换,缩放与锚点更易对齐(含工具栏切换、RTK 跟飞)。
94
+ - **渲染一致**:无人机、轨迹、围栏等与 3D 同一套规则,少踩 2D 投影下的显示差异。
95
+ - **交互可控**:2D 可锁定俯视、自定义滚轮缩放(正交宽度),避免与内置 2D 控制器行为不一致。
96
+ - **语义清晰**:`mapStore` 的 `2D`/`3D` 表示业务视角,不与 Cesium `SceneMode` 枚举混用。
97
+
98
+ 配置里 `scene.sceneMode: '2D'` 表示初始化俯视正交,而非启用 Cesium 平面地图模式。
99
+
100
+ ### 各项目自定义工具栏
90
101
 
91
102
  **方式 1:组件 props(推荐)**
92
103
 
package/index.vue CHANGED
@@ -1,765 +1,327 @@
1
1
  <template>
2
-
3
- <div class="cesium-map-wrapper" @pointerdown="handleMapPointerDown">
4
-
2
+ <div class="cesium-map-wrapper">
5
3
  <div class="cesium-container" ref="cesiumContainer" />
6
-
7
4
  <WsIndicator />
8
-
9
5
  </div>
10
-
11
6
  </template>
12
7
 
13
-
14
-
15
8
  <script setup>
16
-
9
+ /**
10
+ * CesiumMap 组件 — 编排层
11
+ *
12
+ * 职责边界:
13
+ * - 本文件:Vue 生命周期、Pinia 注册、渲染循环、工具栏、onload 事件
14
+ * - cesiumMapBootstrap.js:Viewer / Scene / Imagery / Input 分层配置
15
+ * - basis.js + cameraInteraction.js:2D 正交 / 3D 透视相机与交互
16
+ */
17
17
  import * as Cesium from 'cesium'
18
-
19
18
  import { ref, watch, onMounted, onUnmounted, onActivated, onDeactivated, nextTick, toRaw } from 'vue'
20
-
21
19
  import { MapConfig, DroneConfig } from './js/config/index.js'
22
-
23
20
  import WsIndicator from './components/wsIndicator/wsIndicator.vue'
24
-
25
21
  import { objectUtils } from './js/utils/cesium/object.js'
26
-
27
22
  import { basicConfig, teardownOrthographicWheelZoom } from './js/basis.js'
28
-
29
23
  import { useMapStore, MapLoadStatus } from './js/stores/mapStore.js'
30
-
31
24
  import { useEventBus } from './js/utils/useEventBus.js'
32
-
33
- import { processMapConfigColors } from './js/tileProviders.js'
34
-
35
- import { applyImageryLayers, resolveOnlineBasemap } from './js/utils/mapImagery.js'
36
-
37
25
  import { createCustomToolbarButtons } from './js/customToolbarButtons.js'
38
-
39
- import { ensureMapFocus } from './js/utils/cameraInteraction.js'
40
-
41
-
42
-
26
+ import {
27
+ resolveMapOptions,
28
+ buildViewerOptions,
29
+ buildContextOptions,
30
+ buildSceneRenderOptions,
31
+ applySceneRenderOptions,
32
+ applyConfiguredImagery,
33
+ applyViewerInputPolicy,
34
+ createMapViewer,
35
+ } from './js/utils/cesiumMapBootstrap.js'
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // 0. 依赖注入(包内模块能力)
39
+ // ---------------------------------------------------------------------------
43
40
  const { merge } = objectUtils()
44
-
45
41
  const { mouseController, setInitialCameraView, syncCameraInteractionMode } = basicConfig()
46
-
47
-
48
-
49
42
  const {
50
-
51
43
  addToolbarButton,
52
-
53
44
  addToolbarButtons,
54
-
55
45
  removeToolbarButtons,
56
-
57
46
  createDefaultToolbarButtons,
58
-
59
47
  } = createCustomToolbarButtons()
60
-
61
-
62
-
63
48
  const mapStore = useMapStore()
64
-
65
49
  const { on: onEvent, off: offEvent } = useEventBus()
66
50
 
67
-
68
-
51
+ // ---------------------------------------------------------------------------
52
+ // 1. 组件接口
53
+ // ---------------------------------------------------------------------------
69
54
  const emit = defineEmits(['onload'])
70
-
71
-
72
-
73
55
  const props = defineProps({
74
-
75
- options: {
76
-
77
- type: Object,
78
-
79
- default: undefined,
80
-
81
- },
82
-
83
- mapId: {
84
-
85
- type: String,
86
-
87
- default: 'default',
88
-
89
- },
90
-
91
- showDefaultToolbar: {
92
-
93
- type: Boolean,
94
-
95
- default: true,
96
-
97
- },
98
-
99
- toolbarButtons: {
100
-
101
- type: Array,
102
-
103
- default: undefined,
104
-
105
- },
106
-
107
- extraToolbarButtons: {
108
-
109
- type: Array,
110
-
111
- default: () => [],
112
-
113
- },
114
-
56
+ options: { type: Object, default: undefined },
57
+ mapId: { type: String, default: 'default' },
58
+ showDefaultToolbar: { type: Boolean, default: true },
59
+ toolbarButtons: { type: Array, default: undefined },
60
+ extraToolbarButtons: { type: Array, default: () => [] },
115
61
  })
116
62
 
63
+ // ---------------------------------------------------------------------------
64
+ // 2. 运行时状态
65
+ // ---------------------------------------------------------------------------
66
+ const cesiumContainer = ref(null)
67
+ /** @type {import('cesium').Viewer|null} */
68
+ let map = null
69
+ let customButtons = []
70
+ let isMapActive = true
71
+ let animationId = null
72
+ let lastActiveTime = Date.now()
73
+ const IDLE_TIMEOUT = 30000
117
74
 
118
-
75
+ // ---------------------------------------------------------------------------
76
+ // 3. 工具栏
77
+ // ---------------------------------------------------------------------------
119
78
  function resolveToolbarButtonConfigs() {
120
-
121
- if (props.toolbarButtons?.length) {
122
-
123
- return props.toolbarButtons
124
-
125
- }
126
-
79
+ if (props.toolbarButtons?.length) return props.toolbarButtons
127
80
  const configs = []
128
-
129
81
  if (props.showDefaultToolbar) {
130
-
131
82
  configs.push(...createDefaultToolbarButtons({ mapId: props.mapId }))
132
-
133
83
  }
134
-
135
84
  if (props.extraToolbarButtons?.length) {
136
-
137
85
  configs.push(...props.extraToolbarButtons)
138
-
139
86
  }
140
-
141
87
  return configs
142
-
143
- }
144
-
145
-
146
-
147
- const handleSceneModeChange = ({ mapId, sceneMode }) => {
148
-
149
- if (mapId !== props.mapId || !map || map.isDestroyed?.()) return
150
-
151
- syncCameraInteractionMode(map, mapId, sceneMode)
152
-
153
- }
154
-
155
-
156
-
157
- const handleMapPointerDown = () => {
158
-
159
- if (map && !map.isDestroyed?.()) {
160
-
161
- ensureMapFocus(map)
162
-
163
- }
164
-
165
88
  }
166
89
 
167
-
168
-
169
- const cesiumContainer = ref(null)
170
-
171
- let map = null
172
-
173
- let customButtons = []
174
-
175
- let isMapActive = true
176
-
177
-
178
-
179
90
  function mountToolbarButtons(viewer) {
180
-
181
91
  if (!viewer || viewer.isDestroyed?.()) return
182
-
183
92
  removeToolbarButtons(customButtons)
184
-
185
93
  customButtons = []
186
-
187
- const toolbarConfigs = resolveToolbarButtonConfigs()
188
-
189
- if (toolbarConfigs.length) {
190
-
191
- customButtons = addToolbarButtons(viewer, toolbarConfigs)
192
-
94
+ const configs = resolveToolbarButtonConfigs()
95
+ if (configs.length) {
96
+ customButtons = addToolbarButtons(viewer, configs)
193
97
  }
98
+ }
194
99
 
100
+ // ---------------------------------------------------------------------------
101
+ // 4. 场景模式事件(工具栏 2D/3D 切换 → 同步相机交互)
102
+ // ---------------------------------------------------------------------------
103
+ function handleSceneModeChange({ mapId, sceneMode }) {
104
+ if (mapId !== props.mapId || !map || map.isDestroyed?.()) return
105
+ syncCameraInteractionMode(map, mapId, sceneMode)
195
106
  }
196
107
 
108
+ function registerMapContext(viewer, mapOptions) {
109
+ const center = mapOptions.scene.center || {}
110
+ const sceneMode = mapOptions.scene.sceneMode || '3D'
111
+
112
+ mapStore.setMapInfo(
113
+ 'center',
114
+ {
115
+ lng: center.lng,
116
+ lat: center.lat,
117
+ heading: center.heading,
118
+ pitch: center.pitch,
119
+ roll: center.roll,
120
+ duration: center.duration,
121
+ alt: center.alt,
122
+ },
123
+ props.mapId,
124
+ )
125
+ mapStore.setMap(viewer, props.mapId)
126
+ mapStore.setSceneMode(sceneMode, props.mapId)
127
+
128
+ return { center, sceneMode }
129
+ }
197
130
 
131
+ function emitMapReady(viewer, center) {
132
+ emit('onload', {
133
+ map: viewer,
134
+ center,
135
+ mapId: props.mapId,
136
+ toolbar: {
137
+ addToolbarButton: (opt) => addToolbarButton(viewer, opt),
138
+ addToolbarButtons: (opts) => addToolbarButtons(viewer, opts),
139
+ removeToolbarButtons,
140
+ },
141
+ syncCameraInteractionMode: (mode) => syncCameraInteractionMode(viewer, props.mapId, mode),
142
+ })
143
+ }
198
144
 
199
- const initCesium = async () => {
145
+ function finalizeCameraAndRender(viewer, mapOptions, sceneMode) {
146
+ viewer.resize()
147
+ setInitialCameraView(viewer, mapOptions.scene.center, sceneMode, props.mapId)
148
+ viewer.render()
149
+ }
200
150
 
151
+ // ---------------------------------------------------------------------------
152
+ // 5. 地图初始化主流程
153
+ // ---------------------------------------------------------------------------
154
+ async function initCesium() {
201
155
  if (!cesiumContainer.value) return
202
156
 
203
157
  mapStore.setMapLoadSta(MapLoadStatus.LOADING, props.mapId)
204
-
205
-
206
-
207
- let mapOptions
208
-
209
- if (MapConfig) {
210
-
211
- mapOptions = MapConfig
212
-
213
- }
214
-
215
-
216
-
217
158
  if (DroneConfig) {
218
-
219
159
  mapStore.setTrailTime(DroneConfig.trailTime, props.mapId)
220
-
221
- }
222
-
223
-
224
-
225
- if (props.options) {
226
-
227
- let exOptions
228
-
229
- if (props.options.then) {
230
-
231
- exOptions = toRaw(await props.options)
232
-
233
- } else {
234
-
235
- exOptions = toRaw(props.options)
236
-
237
- }
238
-
239
-
240
-
241
- if (mapOptions) {
242
-
243
- mapOptions = merge(mapOptions, exOptions)
244
-
245
- } else {
246
-
247
- mapOptions = exOptions
248
-
249
- }
250
-
251
160
  }
252
161
 
253
-
254
-
255
- if (mapOptions) {
256
-
257
- mapOptions = processMapConfigColors(mapOptions)
258
-
259
- }
260
-
261
-
262
-
162
+ let mapOptions
263
163
  try {
264
-
265
- const viewerOptions = {
266
-
267
- sceneMode: Cesium.SceneMode.SCENE3D,
268
-
269
- useDefaultRenderLoop: false,
270
-
271
- baseLayer: mapOptions.control.imageryProvider === false ? false : undefined,
272
-
273
- baseLayerPicker: mapOptions.control.baseLayerPicker,
274
-
275
- geocoder: mapOptions.control.geocoder,
276
-
277
- homeButton: mapOptions.control.homeButton,
278
-
279
- sceneModePicker: mapOptions.control.sceneModePicker,
280
-
281
- navigationHelpButton: mapOptions.control.navigationHelpButton,
282
-
283
- animation: mapOptions.control.animation,
284
-
285
- timeline: mapOptions.control.timeline,
286
-
287
- infoBox: mapOptions.control.infoBox,
288
-
289
- fullscreenButton: mapOptions.control.fullscreenButton,
290
-
291
- vrButton: mapOptions.control.vrButton,
292
-
293
- terrainProvider: mapOptions.terrain.show
294
-
295
- ? await Cesium.CesiumTerrainProvider.fromUrl(mapOptions.terrain.url, {
296
-
297
- requestWaterMask: mapOptions.terrain.coastlineData,
298
-
299
- requestVertexNormals: mapOptions.terrain.lightingData,
300
-
301
- })
302
-
303
- : undefined,
304
-
305
- terrainShadows: Cesium.ShadowMode.RECEIVE_ONLY,
306
-
307
- creditContainer: document.createElement('div'),
308
-
309
- }
310
-
311
-
312
-
313
- const contextOptions = {
314
-
315
- webgl: {
316
-
317
- alpha: false,
318
-
319
- depth: true,
320
-
321
- stencil: false,
322
-
323
- antialias: true,
324
-
325
- premultipliedAlpha: true,
326
-
327
- preserveDrawingBuffer: false,
328
-
329
- failIfMajorPerformanceCaveat: false,
330
-
331
- },
332
-
333
- requestWebGl2: true,
334
-
335
- powerPreference: 'high-performance',
336
-
337
- }
338
-
339
-
340
-
341
- const sceneRenderOptions = {
342
-
343
- debugShowFramesPerSecond: mapOptions.scene.debugShowFramesPerSecond,
344
-
345
- enableLighting: mapOptions.scene.globe.enableLighting,
346
-
347
- depthTestAgainstTerrain: mapOptions.scene.globe.depthTestAgainstTerrain,
348
-
349
- fogEnabled: mapOptions.scene.fog.enabled,
350
-
351
- skyAtmosphereShow: mapOptions.scene.skyAtmosphere.show,
352
-
353
- dynamicAtmosphereLighting: mapOptions.scene.globe.dynamicAtmosphereLighting,
354
-
355
- dynamicAtmosphereLightingFromSun: mapOptions.scene.globe.dynamicAtmosphereLightingFromSun,
356
-
357
- }
358
-
359
-
360
-
361
- map = new Cesium.Viewer(cesiumContainer.value, {
362
-
363
- ...viewerOptions,
364
-
365
- contextOptions,
366
-
164
+ mapOptions = await resolveMapOptions({
165
+ mapConfig: MapConfig,
166
+ propsOptions: props.options,
167
+ merge,
168
+ toRaw,
367
169
  })
368
-
369
-
370
-
371
- map.scene.debugShowFramesPerSecond = sceneRenderOptions.debugShowFramesPerSecond
372
-
373
- map.scene.globe.enableLighting = sceneRenderOptions.enableLighting
374
-
375
- map.scene.globe.depthTestAgainstTerrain = sceneRenderOptions.depthTestAgainstTerrain
376
-
377
- map.scene.fog.enabled = sceneRenderOptions.fogEnabled
378
-
379
- map.scene.skyAtmosphere.show = sceneRenderOptions.skyAtmosphereShow
380
-
381
- map.scene.globe.dynamicAtmosphereLighting = sceneRenderOptions.dynamicAtmosphereLighting
382
-
383
- map.scene.globe.dynamicAtmosphereLightingFromSun =
384
-
385
- sceneRenderOptions.dynamicAtmosphereLightingFromSun
386
-
387
-
388
-
389
- const sceneMode = mapOptions.scene.sceneMode || '3D'
390
-
391
-
392
-
393
- const activeBasemap = resolveOnlineBasemap(mapOptions)
394
-
395
- if (activeBasemap) {
396
-
397
- applyImageryLayers(map, mapOptions, activeBasemap)
398
-
399
- console.log(`加载在线底图:${activeBasemap.name},URL:${activeBasemap.url}`)
400
-
401
- window._currentBasemapId = activeBasemap.id
402
-
403
- } else {
404
-
405
- console.warn('未找到可用底图,Cesium 将使用默认底图或空底图显示')
406
-
407
- }
408
-
409
-
410
-
411
- if (map.screenSpaceEventHandler && mapOptions.control.disableDoubleClick) {
412
-
413
- map.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
414
-
170
+ if (!mapOptions) {
171
+ throw new Error('缺少地图配置 MapConfig / options')
415
172
  }
416
173
 
174
+ // 1. Viewer:控件、地形、渲染循环策略
175
+ const viewerOptions = await buildViewerOptions(mapOptions)
176
+ const contextOptions = buildContextOptions()
177
+ map = createMapViewer(cesiumContainer.value, viewerOptions, contextOptions)
417
178
 
179
+ // 2. Scene / Globe:光照、雾、大气
180
+ const sceneRenderOptions = buildSceneRenderOptions(mapOptions)
181
+ applySceneRenderOptions(map, sceneRenderOptions)
418
182
 
419
- if (mapOptions.control.disableEntityClick) {
420
-
421
- map.selectedEntityChanged.addEventListener(() => {
422
-
423
- map.selectedEntity = undefined
424
-
425
- })
426
-
427
- }
428
-
429
-
183
+ // 3. Imagery:在线底图
184
+ applyConfiguredImagery(map, mapOptions)
430
185
 
186
+ // 4. Input:双击、选中、鼠标扩展
187
+ applyViewerInputPolicy(map, mapOptions)
431
188
  mouseController(map, props.mapId)
432
189
 
433
-
434
-
435
- mapStore.setMapInfo(
436
-
437
- 'center',
438
-
439
- {
440
-
441
- lng: mapOptions.scene.center.lng,
442
-
443
- lat: mapOptions.scene.center.lat,
444
-
445
- heading: mapOptions.scene.center.heading,
446
-
447
- pitch: mapOptions.scene.center.pitch,
448
-
449
- roll: mapOptions.scene.center.roll,
450
-
451
- duration: mapOptions.scene.center.duration,
452
-
453
- alt: mapOptions.scene.center.alt,
454
-
455
- },
456
-
457
- props.mapId,
458
-
459
- )
460
-
461
- mapStore.setMap(map, props.mapId)
462
-
463
- mapStore.setSceneMode(sceneMode, props.mapId)
464
-
465
-
466
-
467
- const center = mapOptions.scene.center || {}
468
-
469
-
470
-
190
+ // 5. 业务上下文:Pinia + 工具栏 + 加载态
191
+ const { center, sceneMode } = registerMapContext(map, mapOptions)
471
192
  mountToolbarButtons(map)
472
-
473
193
  mapStore.setMapLoadSta(MapLoadStatus.LOADED, props.mapId)
474
194
 
475
-
476
-
195
+ // 6. 通知宿主(必须在相机 finalize 之前,与历史行为一致)
196
+ // 宿主在 mapOnLoad 内同步添加半球、航迹、无人机等 Entity/Primitive;
197
+ // 若先 setInitialCameraView 再 onload,首帧已绘制且部分场景下实体不刷新。
198
+ emitMapReady(map, center)
477
199
  console.log(`Cesium 地图加载成功,mapId: ${props.mapId}`)
478
200
 
479
- emit('onload', {
480
-
481
- map,
482
-
483
- center,
484
-
485
- mapId: props.mapId,
486
-
487
- toolbar: {
488
-
489
- addToolbarButton: (opt) => addToolbarButton(map, opt),
490
-
491
- addToolbarButtons: (opts) => addToolbarButtons(map, opts),
492
-
493
- removeToolbarButtons,
494
-
495
- },
496
-
497
- syncCameraInteractionMode: (mode) => syncCameraInteractionMode(map, props.mapId, mode),
498
-
499
- })
500
-
501
-
502
-
503
- map.resize()
504
-
505
- setInitialCameraView(map, mapOptions.scene.center, sceneMode, props.mapId)
506
-
507
- map.render()
508
-
509
-
510
-
511
- if (sceneMode === '2D') {
512
-
513
- nextTick(() => {
514
-
515
- if (!map || map.isDestroyed?.()) return
516
-
517
- syncCameraInteractionMode(map, props.mapId, '2D')
518
-
519
- })
520
-
521
- }
522
-
523
-
201
+ // 7. 相机与首帧(须在容器尺寸稳定后 resize)
202
+ finalizeCameraAndRender(map, mapOptions, sceneMode)
524
203
 
204
+ // 8. 自管渲染循环(useDefaultRenderLoop: false)
525
205
  startContinuousRendering(map)
526
-
527
206
  } catch (error) {
528
-
529
207
  mapStore.setMapLoadSta(MapLoadStatus.FAILED, props.mapId)
530
-
531
208
  console.error('Cesium 地图加载失败:', error)
532
-
533
209
  }
534
-
535
210
  }
536
211
 
537
-
538
-
539
- let animationId = null
540
-
541
- let lastActiveTime = Date.now()
542
-
543
- const IDLE_TIMEOUT = 30000
544
-
545
-
546
-
212
+ // ---------------------------------------------------------------------------
213
+ // 6. 渲染循环
214
+ // ---------------------------------------------------------------------------
547
215
  function startContinuousRendering(viewer) {
548
-
549
216
  if (animationId) return
550
-
551
- function animate() {
552
-
217
+ const tick = () => {
553
218
  const now = Date.now()
554
-
555
219
  if (
556
-
557
220
  isMapActive &&
558
-
559
221
  !document.hidden &&
560
-
561
222
  map &&
562
-
563
223
  !map.isDestroyed?.() &&
564
-
565
224
  now - lastActiveTime < IDLE_TIMEOUT
566
-
567
225
  ) {
568
-
569
226
  viewer.render()
570
-
571
227
  }
572
-
573
- animationId = requestAnimationFrame(animate)
574
-
228
+ animationId = requestAnimationFrame(tick)
575
229
  }
576
-
577
- animate()
578
-
230
+ tick()
579
231
  }
580
232
 
581
-
582
-
583
233
  function stopRenderLoop() {
584
-
585
234
  if (animationId) {
586
-
587
235
  cancelAnimationFrame(animationId)
588
-
589
236
  animationId = null
590
-
591
237
  }
592
-
593
238
  }
594
239
 
595
-
596
-
597
240
  function setupActivityListeners() {
598
-
599
- const activities = ['mousemove', 'keydown', 'touchmove', 'wheel', 'click']
600
-
601
- activities.forEach((event) => {
602
-
241
+ ;['mousemove', 'keydown', 'touchmove', 'wheel', 'click'].forEach((event) => {
603
242
  document.addEventListener(event, () => {
604
-
605
243
  lastActiveTime = Date.now()
606
-
607
244
  })
608
-
609
245
  })
610
-
611
246
  }
612
247
 
613
-
614
-
248
+ // ---------------------------------------------------------------------------
249
+ // 7. keep-alive 恢复
250
+ // ---------------------------------------------------------------------------
615
251
  function resumeMapAfterCache() {
616
-
617
252
  if (!map || map.isDestroyed?.()) return
618
-
619
253
  mapStore.setCurrentMapId(props.mapId)
620
-
621
254
  isMapActive = true
622
-
623
255
  lastActiveTime = Date.now()
624
256
 
625
257
  nextTick(() => {
626
-
627
258
  if (!map || map.isDestroyed?.()) return
628
-
629
259
  map.resize()
630
-
631
260
  const mode = mapStore.getSceneMode(props.mapId)
632
-
633
261
  syncCameraInteractionMode(map, props.mapId, mode === '2D' ? '2D' : '3D')
634
-
635
262
  map.render()
636
-
637
263
  if (!animationId) startContinuousRendering(map)
638
-
639
264
  })
640
-
641
265
  }
642
266
 
643
-
644
-
267
+ // ---------------------------------------------------------------------------
268
+ // 8. 生命周期
269
+ // ---------------------------------------------------------------------------
645
270
  watch(
646
-
647
271
  () => props.toolbarButtons,
648
-
649
272
  () => {
650
-
651
- if (map && !map.isDestroyed?.()) {
652
-
653
- mountToolbarButtons(map)
654
-
655
- }
656
-
273
+ if (map && !map.isDestroyed?.()) mountToolbarButtons(map)
657
274
  },
658
-
659
275
  { deep: true },
660
-
661
276
  )
662
277
 
663
-
664
-
665
278
  onMounted(() => {
666
-
667
279
  mapStore.setCurrentMapId(props.mapId)
668
-
669
280
  setupActivityListeners()
670
-
671
281
  onEvent('map-scene-mode-changed', handleSceneModeChange)
672
-
673
282
  initCesium()
674
-
675
283
  })
676
284
 
677
-
678
-
679
285
  onActivated(() => {
680
-
681
286
  mapStore.setCurrentMapId(props.mapId)
682
-
683
287
  if (!map || map.isDestroyed?.()) {
684
-
685
288
  isMapActive = true
686
-
687
289
  initCesium()
688
-
689
290
  return
690
-
691
291
  }
692
-
693
292
  resumeMapAfterCache()
694
-
695
293
  })
696
294
 
697
-
698
-
699
295
  onDeactivated(() => {
700
-
701
296
  isMapActive = false
702
-
703
297
  stopRenderLoop()
704
-
705
298
  })
706
299
 
707
-
708
-
709
300
  onUnmounted(() => {
710
-
711
301
  isMapActive = false
712
-
713
302
  stopRenderLoop()
714
-
715
303
  offEvent('map-scene-mode-changed', handleSceneModeChange)
716
-
717
- if (map) {
718
-
719
- teardownOrthographicWheelZoom(map)
720
-
721
- }
722
-
304
+ if (map) teardownOrthographicWheelZoom(map)
723
305
  removeToolbarButtons(customButtons)
724
-
725
306
  customButtons = []
726
-
727
307
  if (map) {
728
-
729
308
  map.destroy()
730
-
731
309
  map = null
732
-
733
310
  }
734
-
735
311
  mapStore.removeMapInstance(props.mapId)
736
-
737
312
  })
738
-
739
313
  </script>
740
314
 
741
-
742
-
743
315
  <style scoped lang="less">
744
316
  .cesium-map-wrapper {
745
-
746
317
  width: 100%;
747
-
748
318
  height: 100%;
749
-
750
319
  position: relative;
751
-
752
320
  }
753
321
 
754
-
755
-
756
322
  .cesium-container {
757
-
758
323
  width: 100%;
759
-
760
324
  height: 100%;
761
-
762
325
  position: relative;
763
-
764
326
  }
765
327
  </style>
package/js/index.js CHANGED
@@ -36,6 +36,16 @@ export {
36
36
  teardownOrthographicWheelZoom,
37
37
  syncCameraInteractionMode,
38
38
  } from './utils/cameraInteraction.js'
39
+ export {
40
+ resolveMapOptions,
41
+ buildViewerOptions,
42
+ buildContextOptions,
43
+ buildSceneRenderOptions,
44
+ applySceneRenderOptions,
45
+ applyConfiguredImagery,
46
+ applyViewerInputPolicy,
47
+ createMapViewer,
48
+ } from './utils/cesiumMapBootstrap.js'
39
49
 
40
50
  export {
41
51
  BaseConfig,
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Cesium Viewer 启动配置(按 Cesium 架构分层)
3
+ *
4
+ * 1. Viewer — 控件、地形、底图策略、渲染循环开关
5
+ * 2. Context — WebGL 上下文
6
+ * 3. Scene — 光照、雾、大气、调试项
7
+ * 4. Imagery — 在线底图(业务配置驱动)
8
+ * 5. Input — 双击、实体选中、鼠标扩展
9
+ */
10
+ import * as Cesium from 'cesium'
11
+ import { processMapConfigColors } from '../tileProviders.js'
12
+ import { applyImageryLayers, resolveOnlineBasemap } from './mapImagery.js'
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // 阶段 0:合并业务配置
16
+ // ---------------------------------------------------------------------------
17
+
18
+ /**
19
+ * @param {object} params
20
+ * @param {object} [params.mapConfig] MapConfig
21
+ * @param {object} [params.propsOptions] 组件 props.options(支持 Promise)
22
+ * @param {Function} params.merge objectUtils().merge
23
+ * @param {Function} params.toRaw vue toRaw
24
+ */
25
+ export async function resolveMapOptions({ mapConfig, propsOptions, merge, toRaw }) {
26
+ let mapOptions = mapConfig || undefined
27
+
28
+ if (propsOptions) {
29
+ const exOptions = propsOptions.then
30
+ ? toRaw(await propsOptions)
31
+ : toRaw(propsOptions)
32
+ mapOptions = mapOptions ? merge(mapOptions, exOptions) : exOptions
33
+ }
34
+
35
+ if (mapOptions) {
36
+ mapOptions = processMapConfigColors(mapOptions)
37
+ }
38
+
39
+ return mapOptions
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // 阶段 1:Viewer 构造参数
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export async function buildViewerOptions(mapOptions) {
47
+ return {
48
+ sceneMode: Cesium.SceneMode.SCENE3D,
49
+ useDefaultRenderLoop: false,
50
+ baseLayer: mapOptions.control.imageryProvider === false ? false : undefined,
51
+ baseLayerPicker: mapOptions.control.baseLayerPicker,
52
+ geocoder: mapOptions.control.geocoder,
53
+ homeButton: mapOptions.control.homeButton,
54
+ sceneModePicker: mapOptions.control.sceneModePicker,
55
+ navigationHelpButton: mapOptions.control.navigationHelpButton,
56
+ animation: mapOptions.control.animation,
57
+ timeline: mapOptions.control.timeline,
58
+ infoBox: mapOptions.control.infoBox,
59
+ fullscreenButton: mapOptions.control.fullscreenButton,
60
+ vrButton: mapOptions.control.vrButton,
61
+ terrainProvider: mapOptions.terrain.show
62
+ ? await Cesium.CesiumTerrainProvider.fromUrl(mapOptions.terrain.url, {
63
+ requestWaterMask: mapOptions.terrain.coastlineData,
64
+ requestVertexNormals: mapOptions.terrain.lightingData,
65
+ })
66
+ : undefined,
67
+ terrainShadows: Cesium.ShadowMode.RECEIVE_ONLY,
68
+ creditContainer: document.createElement('div'),
69
+ }
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // 阶段 2:WebGL Context
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export function buildContextOptions() {
77
+ return {
78
+ webgl: {
79
+ alpha: false,
80
+ depth: true,
81
+ stencil: false,
82
+ antialias: true,
83
+ premultipliedAlpha: true,
84
+ preserveDrawingBuffer: false,
85
+ failIfMajorPerformanceCaveat: false,
86
+ },
87
+ requestWebGl2: true,
88
+ powerPreference: 'high-performance',
89
+ }
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // 阶段 3:Scene / Globe 渲染环境
94
+ // ---------------------------------------------------------------------------
95
+
96
+ export function buildSceneRenderOptions(mapOptions) {
97
+ return {
98
+ debugShowFramesPerSecond: mapOptions.scene.debugShowFramesPerSecond,
99
+ enableLighting: mapOptions.scene.globe.enableLighting,
100
+ depthTestAgainstTerrain: mapOptions.scene.globe.depthTestAgainstTerrain,
101
+ fogEnabled: mapOptions.scene.fog.enabled,
102
+ skyAtmosphereShow: mapOptions.scene.skyAtmosphere.show,
103
+ dynamicAtmosphereLighting: mapOptions.scene.globe.dynamicAtmosphereLighting,
104
+ dynamicAtmosphereLightingFromSun: mapOptions.scene.globe.dynamicAtmosphereLightingFromSun,
105
+ }
106
+ }
107
+
108
+ export function applySceneRenderOptions(viewer, options) {
109
+ viewer.scene.debugShowFramesPerSecond = options.debugShowFramesPerSecond
110
+ viewer.scene.globe.enableLighting = options.enableLighting
111
+ viewer.scene.globe.depthTestAgainstTerrain = options.depthTestAgainstTerrain
112
+ viewer.scene.fog.enabled = options.fogEnabled
113
+ viewer.scene.skyAtmosphere.show = options.skyAtmosphereShow
114
+ viewer.scene.globe.dynamicAtmosphereLighting = options.dynamicAtmosphereLighting
115
+ viewer.scene.globe.dynamicAtmosphereLightingFromSun = options.dynamicAtmosphereLightingFromSun
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // 阶段 4:Imagery 底图
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * @returns {string|undefined} 当前底图 id(写入 window._currentBasemapId)
124
+ */
125
+ export function applyConfiguredImagery(viewer, mapOptions) {
126
+ const activeBasemap = resolveOnlineBasemap(mapOptions)
127
+ if (activeBasemap) {
128
+ applyImageryLayers(viewer, mapOptions, activeBasemap)
129
+ console.log(`加载在线底图:${activeBasemap.name},URL:${activeBasemap.url}`)
130
+ if (typeof window !== 'undefined') {
131
+ window._currentBasemapId = activeBasemap.id
132
+ }
133
+ return activeBasemap.id
134
+ }
135
+ console.warn('未找到可用底图,Cesium 将使用默认底图或空底图显示')
136
+ return undefined
137
+ }
138
+
139
+ // ---------------------------------------------------------------------------
140
+ // 阶段 5:输入与交互策略
141
+ // ---------------------------------------------------------------------------
142
+
143
+ export function applyViewerInputPolicy(viewer, mapOptions) {
144
+ if (viewer.screenSpaceEventHandler && mapOptions.control.disableDoubleClick) {
145
+ viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
146
+ }
147
+
148
+ if (mapOptions.control.disableEntityClick) {
149
+ viewer.selectedEntityChanged.addEventListener(() => {
150
+ viewer.selectedEntity = undefined
151
+ })
152
+ }
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // 阶段 6:创建 Viewer
157
+ // ---------------------------------------------------------------------------
158
+
159
+ export function createMapViewer(container, viewerOptions, contextOptions) {
160
+ return new Cesium.Viewer(container, {
161
+ ...viewerOptions,
162
+ contextOptions,
163
+ })
164
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huweili-cesium",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -93,6 +93,8 @@
93
93
  "./utils/orthographicCameraZoom.js": "./js/utils/orthographicCameraZoom.js",
94
94
  "./utils/cameraInteraction": "./js/utils/cameraInteraction.js",
95
95
  "./utils/cameraInteraction.js": "./js/utils/cameraInteraction.js",
96
+ "./utils/cesiumMapBootstrap": "./js/utils/cesiumMapBootstrap.js",
97
+ "./utils/cesiumMapBootstrap.js": "./js/utils/cesiumMapBootstrap.js",
96
98
  "./js/utils/orthographicCameraZoom": "./js/utils/orthographicCameraZoom.js",
97
99
  "./js/utils/orthographicCameraZoom.js": "./js/utils/orthographicCameraZoom.js",
98
100
  "./utils/cesium/object": "./js/utils/cesium/object.js",