huweili-cesium 1.2.6 → 1.2.7

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,325 @@
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
- if (DroneConfig) {
218
-
158
+ if (DroneConfig?.trailTime != null) {
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
 
195
+ // 6. 相机与首帧(须在容器尺寸稳定后 resize)
196
+ finalizeCameraAndRender(map, mapOptions, sceneMode)
475
197
 
476
-
198
+ // 7. 通知宿主(相机、工具栏已就绪)
199
+ emitMapReady(map, center)
477
200
  console.log(`Cesium 地图加载成功,mapId: ${props.mapId}`)
478
201
 
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
-
524
-
202
+ // 8. 自管渲染循环(useDefaultRenderLoop: false)
525
203
  startContinuousRendering(map)
526
-
527
204
  } catch (error) {
528
-
529
205
  mapStore.setMapLoadSta(MapLoadStatus.FAILED, props.mapId)
530
-
531
206
  console.error('Cesium 地图加载失败:', error)
532
-
533
207
  }
534
-
535
208
  }
536
209
 
537
-
538
-
539
- let animationId = null
540
-
541
- let lastActiveTime = Date.now()
542
-
543
- const IDLE_TIMEOUT = 30000
544
-
545
-
546
-
210
+ // ---------------------------------------------------------------------------
211
+ // 6. 渲染循环
212
+ // ---------------------------------------------------------------------------
547
213
  function startContinuousRendering(viewer) {
548
-
549
214
  if (animationId) return
550
-
551
- function animate() {
552
-
215
+ const tick = () => {
553
216
  const now = Date.now()
554
-
555
217
  if (
556
-
557
218
  isMapActive &&
558
-
559
219
  !document.hidden &&
560
-
561
220
  map &&
562
-
563
221
  !map.isDestroyed?.() &&
564
-
565
222
  now - lastActiveTime < IDLE_TIMEOUT
566
-
567
223
  ) {
568
-
569
224
  viewer.render()
570
-
571
225
  }
572
-
573
- animationId = requestAnimationFrame(animate)
574
-
226
+ animationId = requestAnimationFrame(tick)
575
227
  }
576
-
577
- animate()
578
-
228
+ tick()
579
229
  }
580
230
 
581
-
582
-
583
231
  function stopRenderLoop() {
584
-
585
232
  if (animationId) {
586
-
587
233
  cancelAnimationFrame(animationId)
588
-
589
234
  animationId = null
590
-
591
235
  }
592
-
593
236
  }
594
237
 
595
-
596
-
597
238
  function setupActivityListeners() {
598
-
599
- const activities = ['mousemove', 'keydown', 'touchmove', 'wheel', 'click']
600
-
601
- activities.forEach((event) => {
602
-
239
+ ;['mousemove', 'keydown', 'touchmove', 'wheel', 'click'].forEach((event) => {
603
240
  document.addEventListener(event, () => {
604
-
605
241
  lastActiveTime = Date.now()
606
-
607
242
  })
608
-
609
243
  })
610
-
611
244
  }
612
245
 
613
-
614
-
246
+ // ---------------------------------------------------------------------------
247
+ // 7. keep-alive 恢复
248
+ // ---------------------------------------------------------------------------
615
249
  function resumeMapAfterCache() {
616
-
617
250
  if (!map || map.isDestroyed?.()) return
618
-
619
251
  mapStore.setCurrentMapId(props.mapId)
620
-
621
252
  isMapActive = true
622
-
623
253
  lastActiveTime = Date.now()
624
254
 
625
255
  nextTick(() => {
626
-
627
256
  if (!map || map.isDestroyed?.()) return
628
-
629
257
  map.resize()
630
-
631
258
  const mode = mapStore.getSceneMode(props.mapId)
632
-
633
259
  syncCameraInteractionMode(map, props.mapId, mode === '2D' ? '2D' : '3D')
634
-
635
260
  map.render()
636
-
637
261
  if (!animationId) startContinuousRendering(map)
638
-
639
262
  })
640
-
641
263
  }
642
264
 
643
-
644
-
265
+ // ---------------------------------------------------------------------------
266
+ // 8. 生命周期
267
+ // ---------------------------------------------------------------------------
645
268
  watch(
646
-
647
269
  () => props.toolbarButtons,
648
-
649
270
  () => {
650
-
651
- if (map && !map.isDestroyed?.()) {
652
-
653
- mountToolbarButtons(map)
654
-
655
- }
656
-
271
+ if (map && !map.isDestroyed?.()) mountToolbarButtons(map)
657
272
  },
658
-
659
273
  { deep: true },
660
-
661
274
  )
662
275
 
663
-
664
-
665
276
  onMounted(() => {
666
-
667
277
  mapStore.setCurrentMapId(props.mapId)
668
-
669
278
  setupActivityListeners()
670
-
671
279
  onEvent('map-scene-mode-changed', handleSceneModeChange)
672
-
673
280
  initCesium()
674
-
675
281
  })
676
282
 
677
-
678
-
679
283
  onActivated(() => {
680
-
681
284
  mapStore.setCurrentMapId(props.mapId)
682
-
683
285
  if (!map || map.isDestroyed?.()) {
684
-
685
286
  isMapActive = true
686
-
687
287
  initCesium()
688
-
689
288
  return
690
-
691
289
  }
692
-
693
290
  resumeMapAfterCache()
694
-
695
291
  })
696
292
 
697
-
698
-
699
293
  onDeactivated(() => {
700
-
701
294
  isMapActive = false
702
-
703
295
  stopRenderLoop()
704
-
705
296
  })
706
297
 
707
-
708
-
709
298
  onUnmounted(() => {
710
-
711
299
  isMapActive = false
712
-
713
300
  stopRenderLoop()
714
-
715
301
  offEvent('map-scene-mode-changed', handleSceneModeChange)
716
-
717
- if (map) {
718
-
719
- teardownOrthographicWheelZoom(map)
720
-
721
- }
722
-
302
+ if (map) teardownOrthographicWheelZoom(map)
723
303
  removeToolbarButtons(customButtons)
724
-
725
304
  customButtons = []
726
-
727
305
  if (map) {
728
-
729
306
  map.destroy()
730
-
731
307
  map = null
732
-
733
308
  }
734
-
735
309
  mapStore.removeMapInstance(props.mapId)
736
-
737
310
  })
738
-
739
311
  </script>
740
312
 
741
-
742
-
743
313
  <style scoped lang="less">
744
314
  .cesium-map-wrapper {
745
-
746
315
  width: 100%;
747
-
748
316
  height: 100%;
749
-
750
317
  position: relative;
751
-
752
318
  }
753
319
 
754
-
755
-
756
320
  .cesium-container {
757
-
758
321
  width: 100%;
759
-
760
322
  height: 100%;
761
-
762
323
  position: relative;
763
-
764
324
  }
765
325
  </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.7",
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",