huweili-cesium 1.2.4 → 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
 
@@ -21,7 +21,7 @@
21
21
  * - 收到无人机数据时绿色高亮闪烁(.active)
22
22
  */
23
23
  import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
24
- import { useEventBus } from '../../js/utils/useEventBus'
24
+ import { useEventBus } from '../../js/utils/useEventBus.js'
25
25
 
26
26
  const { on, off } = useEventBus()
27
27
 
package/index.vue CHANGED
@@ -1,385 +1,325 @@
1
- <template>
2
- <div class="cesium-map-wrapper" @pointerdown="handleMapPointerDown">
3
- <div class="cesium-container" ref="cesiumContainer" />
4
- <WsIndicator />
5
- </div>
6
- </template>
7
-
8
- <script setup>
9
- import * as Cesium from 'cesium'
10
- import { ref, watch, onMounted, onUnmounted, onActivated, onDeactivated, nextTick, toRaw } from 'vue'
11
- import { MapConfig, DroneConfig } from './config'
12
- import WsIndicator from './components/wsIndicator/wsIndicator.vue'
13
- import { objectUtils } from './utils/cesium/object'
14
- import { basicConfig, teardownOrthographicWheelZoom } from './basis'
15
- import { useMapStore, MapLoadStatus } from './stores/mapStore'
16
- import { useEventBus } from './utils/useEventBus'
17
- import { processMapConfigColors } from './tileProviders'
18
- import { applyImageryLayers, resolveOnlineBasemap } from './utils/mapImagery'
19
- import { createCustomToolbarButtons } from './customToolbarButtons'
20
- import { ensureMapFocus } from './utils/cameraInteraction'
21
-
22
- const { merge } = objectUtils()
23
- const { mouseController, setInitialCameraView, syncCameraInteractionMode } = basicConfig()
24
-
25
- const {
26
- addToolbarButton,
27
- addToolbarButtons,
28
- removeToolbarButtons,
29
- createDefaultToolbarButtons,
30
- } = createCustomToolbarButtons()
31
-
32
- const mapStore = useMapStore()
33
- const { on: onEvent, off: offEvent } = useEventBus()
34
-
35
- const emit = defineEmits(['onload'])
36
-
37
- const props = defineProps({
38
- options: {
39
- type: Object,
40
- default: undefined,
41
- },
42
- mapId: {
43
- type: String,
44
- default: 'default',
45
- },
46
- showDefaultToolbar: {
47
- type: Boolean,
48
- default: true,
49
- },
50
- toolbarButtons: {
51
- type: Array,
52
- default: undefined,
53
- },
54
- extraToolbarButtons: {
55
- type: Array,
56
- default: () => [],
57
- },
58
- })
59
-
60
- function resolveToolbarButtonConfigs() {
61
- if (props.toolbarButtons?.length) {
62
- return props.toolbarButtons
63
- }
64
- const configs = []
65
- if (props.showDefaultToolbar) {
66
- configs.push(...createDefaultToolbarButtons({ mapId: props.mapId }))
67
- }
68
- if (props.extraToolbarButtons?.length) {
69
- configs.push(...props.extraToolbarButtons)
70
- }
71
- return configs
72
- }
73
-
74
- const handleSceneModeChange = ({ mapId, sceneMode }) => {
75
- if (mapId !== props.mapId || !map || map.isDestroyed?.()) return
76
- syncCameraInteractionMode(map, mapId, sceneMode)
77
- }
78
-
79
- const handleMapPointerDown = () => {
80
- if (map && !map.isDestroyed?.()) {
81
- ensureMapFocus(map)
82
- }
83
- }
84
-
85
- const cesiumContainer = ref(null)
86
- let map = null
87
- let customButtons = []
88
- let isMapActive = true
89
-
90
- function mountToolbarButtons(viewer) {
91
- if (!viewer || viewer.isDestroyed?.()) return
92
- removeToolbarButtons(customButtons)
93
- customButtons = []
94
- const toolbarConfigs = resolveToolbarButtonConfigs()
95
- if (toolbarConfigs.length) {
96
- customButtons = addToolbarButtons(viewer, toolbarConfigs)
97
- }
98
- }
99
-
100
- const initCesium = async () => {
101
- if (!cesiumContainer.value) return
102
- mapStore.setMapLoadSta(MapLoadStatus.LOADING, props.mapId)
103
-
104
- let mapOptions
105
- if (MapConfig) {
106
- mapOptions = MapConfig
107
- }
108
-
109
- if (DroneConfig) {
110
- mapStore.setTrailTime(DroneConfig.trailTime, props.mapId)
111
- }
112
-
113
- if (props.options) {
114
- let exOptions
115
- if (props.options.then) {
116
- exOptions = toRaw(await props.options)
117
- } else {
118
- exOptions = toRaw(props.options)
119
- }
120
-
121
- if (mapOptions) {
122
- mapOptions = merge(mapOptions, exOptions)
123
- } else {
124
- mapOptions = exOptions
125
- }
126
- }
127
-
128
- if (mapOptions) {
129
- mapOptions = processMapConfigColors(mapOptions)
130
- }
131
-
132
- try {
133
- const viewerOptions = {
134
- sceneMode: Cesium.SceneMode.SCENE3D,
135
- useDefaultRenderLoop: false,
136
- baseLayer: mapOptions.control.imageryProvider === false ? false : undefined,
137
- baseLayerPicker: mapOptions.control.baseLayerPicker,
138
- geocoder: mapOptions.control.geocoder,
139
- homeButton: mapOptions.control.homeButton,
140
- sceneModePicker: mapOptions.control.sceneModePicker,
141
- navigationHelpButton: mapOptions.control.navigationHelpButton,
142
- animation: mapOptions.control.animation,
143
- timeline: mapOptions.control.timeline,
144
- infoBox: mapOptions.control.infoBox,
145
- fullscreenButton: mapOptions.control.fullscreenButton,
146
- vrButton: mapOptions.control.vrButton,
147
- terrainProvider: mapOptions.terrain.show
148
- ? await Cesium.CesiumTerrainProvider.fromUrl(mapOptions.terrain.url, {
149
- requestWaterMask: mapOptions.terrain.coastlineData,
150
- requestVertexNormals: mapOptions.terrain.lightingData,
151
- })
152
- : undefined,
153
- terrainShadows: Cesium.ShadowMode.RECEIVE_ONLY,
154
- creditContainer: document.createElement('div'),
155
- }
156
-
157
- const contextOptions = {
158
- webgl: {
159
- alpha: false,
160
- depth: true,
161
- stencil: false,
162
- antialias: true,
163
- premultipliedAlpha: true,
164
- preserveDrawingBuffer: false,
165
- failIfMajorPerformanceCaveat: false,
166
- },
167
- requestWebGl2: true,
168
- powerPreference: 'high-performance',
169
- }
170
-
171
- const sceneRenderOptions = {
172
- debugShowFramesPerSecond: mapOptions.scene.debugShowFramesPerSecond,
173
- enableLighting: mapOptions.scene.globe.enableLighting,
174
- depthTestAgainstTerrain: mapOptions.scene.globe.depthTestAgainstTerrain,
175
- fogEnabled: mapOptions.scene.fog.enabled,
176
- skyAtmosphereShow: mapOptions.scene.skyAtmosphere.show,
177
- dynamicAtmosphereLighting: mapOptions.scene.globe.dynamicAtmosphereLighting,
178
- dynamicAtmosphereLightingFromSun: mapOptions.scene.globe.dynamicAtmosphereLightingFromSun,
179
- }
180
-
181
- map = new Cesium.Viewer(cesiumContainer.value, {
182
- ...viewerOptions,
183
- contextOptions,
184
- })
185
-
186
- map.scene.debugShowFramesPerSecond = sceneRenderOptions.debugShowFramesPerSecond
187
- map.scene.globe.enableLighting = sceneRenderOptions.enableLighting
188
- map.scene.globe.depthTestAgainstTerrain = sceneRenderOptions.depthTestAgainstTerrain
189
- map.scene.fog.enabled = sceneRenderOptions.fogEnabled
190
- map.scene.skyAtmosphere.show = sceneRenderOptions.skyAtmosphereShow
191
- map.scene.globe.dynamicAtmosphereLighting = sceneRenderOptions.dynamicAtmosphereLighting
192
- map.scene.globe.dynamicAtmosphereLightingFromSun =
193
- sceneRenderOptions.dynamicAtmosphereLightingFromSun
194
-
195
- const sceneMode = mapOptions.scene.sceneMode || '3D'
196
-
197
- const activeBasemap = resolveOnlineBasemap(mapOptions)
198
- if (activeBasemap) {
199
- applyImageryLayers(map, mapOptions, activeBasemap)
200
- console.log(`加载在线底图:${activeBasemap.name},URL:${activeBasemap.url}`)
201
- window._currentBasemapId = activeBasemap.id
202
- } else {
203
- console.warn('未找到可用底图,Cesium 将使用默认底图或空底图显示')
204
- }
205
-
206
- if (map.screenSpaceEventHandler && mapOptions.control.disableDoubleClick) {
207
- map.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
208
- }
209
-
210
- if (mapOptions.control.disableEntityClick) {
211
- map.selectedEntityChanged.addEventListener(() => {
212
- map.selectedEntity = undefined
213
- })
214
- }
215
-
216
- mouseController(map, props.mapId)
217
-
218
- mapStore.setMapInfo(
219
- 'center',
220
- {
221
- lng: mapOptions.scene.center.lng,
222
- lat: mapOptions.scene.center.lat,
223
- heading: mapOptions.scene.center.heading,
224
- pitch: mapOptions.scene.center.pitch,
225
- roll: mapOptions.scene.center.roll,
226
- duration: mapOptions.scene.center.duration,
227
- alt: mapOptions.scene.center.alt,
228
- },
229
- props.mapId,
230
- )
231
- mapStore.setMap(map, props.mapId)
232
- mapStore.setSceneMode(sceneMode, props.mapId)
233
-
234
- const center = mapOptions.scene.center || {}
235
-
236
- mountToolbarButtons(map)
237
- mapStore.setMapLoadSta(MapLoadStatus.LOADED, props.mapId)
238
-
239
- console.log(`Cesium 地图加载成功,mapId: ${props.mapId}`)
240
- emit('onload', {
241
- map,
242
- center,
243
- mapId: props.mapId,
244
- toolbar: {
245
- addToolbarButton: (opt) => addToolbarButton(map, opt),
246
- addToolbarButtons: (opts) => addToolbarButtons(map, opts),
247
- removeToolbarButtons,
248
- },
249
- syncCameraInteractionMode: (mode) => syncCameraInteractionMode(map, props.mapId, mode),
250
- })
251
-
252
- map.resize()
253
- setInitialCameraView(map, mapOptions.scene.center, sceneMode, props.mapId)
254
- map.render()
255
-
256
- if (sceneMode === '2D') {
257
- nextTick(() => {
258
- if (!map || map.isDestroyed?.()) return
259
- syncCameraInteractionMode(map, props.mapId, '2D')
260
- })
261
- }
262
-
263
- startContinuousRendering(map)
264
- } catch (error) {
265
- mapStore.setMapLoadSta(MapLoadStatus.FAILED, props.mapId)
266
- console.error('Cesium 地图加载失败:', error)
267
- }
268
- }
269
-
270
- let animationId = null
271
- let lastActiveTime = Date.now()
272
- const IDLE_TIMEOUT = 30000
273
-
274
- function startContinuousRendering(viewer) {
275
- if (animationId) return
276
- function animate() {
277
- const now = Date.now()
278
- if (
279
- isMapActive &&
280
- !document.hidden &&
281
- map &&
282
- !map.isDestroyed?.() &&
283
- now - lastActiveTime < IDLE_TIMEOUT
284
- ) {
285
- viewer.render()
286
- }
287
- animationId = requestAnimationFrame(animate)
288
- }
289
- animate()
290
- }
291
-
292
- function stopRenderLoop() {
293
- if (animationId) {
294
- cancelAnimationFrame(animationId)
295
- animationId = null
296
- }
297
- }
298
-
299
- function setupActivityListeners() {
300
- const activities = ['mousemove', 'keydown', 'touchmove', 'wheel', 'click']
301
- activities.forEach((event) => {
302
- document.addEventListener(event, () => {
303
- lastActiveTime = Date.now()
304
- })
305
- })
306
- }
307
-
308
- function resumeMapAfterCache() {
309
- if (!map || map.isDestroyed?.()) return
310
- mapStore.setCurrentMapId(props.mapId)
311
- isMapActive = true
312
- lastActiveTime = Date.now()
313
- nextTick(() => {
314
- if (!map || map.isDestroyed?.()) return
315
- map.resize()
316
- const mode = mapStore.getSceneMode(props.mapId)
317
- syncCameraInteractionMode(map, props.mapId, mode === '2D' ? '2D' : '3D')
318
- map.render()
319
- if (!animationId) startContinuousRendering(map)
320
- })
321
- }
322
-
323
- watch(
324
- () => props.toolbarButtons,
325
- () => {
326
- if (map && !map.isDestroyed?.()) {
327
- mountToolbarButtons(map)
328
- }
329
- },
330
- { deep: true },
331
- )
332
-
333
- onMounted(() => {
334
- mapStore.setCurrentMapId(props.mapId)
335
- setupActivityListeners()
336
- onEvent('map-scene-mode-changed', handleSceneModeChange)
337
- initCesium()
338
- })
339
-
340
- onActivated(() => {
341
- mapStore.setCurrentMapId(props.mapId)
342
- if (!map || map.isDestroyed?.()) {
343
- isMapActive = true
344
- initCesium()
345
- return
346
- }
347
- resumeMapAfterCache()
348
- })
349
-
350
- onDeactivated(() => {
351
- isMapActive = false
352
- stopRenderLoop()
353
- })
354
-
355
- onUnmounted(() => {
356
- isMapActive = false
357
- stopRenderLoop()
358
- offEvent('map-scene-mode-changed', handleSceneModeChange)
359
- if (map) {
360
- teardownOrthographicWheelZoom(map)
361
- }
362
- removeToolbarButtons(customButtons)
363
- customButtons = []
364
- if (map) {
365
- map.destroy()
366
- map = null
367
- }
368
- mapStore.removeMapInstance(props.mapId)
369
- })
370
- </script>
371
-
372
- <style scoped lang="less">
373
- .cesium-map-wrapper {
374
- width: 100%;
375
- height: 100%;
376
- position: relative;
377
- }
378
-
379
- .cesium-container {
380
- width: 100%;
381
- height: 100%;
382
- position: relative;
383
- }
384
- </style>
385
-
1
+ <template>
2
+ <div class="cesium-map-wrapper">
3
+ <div class="cesium-container" ref="cesiumContainer" />
4
+ <WsIndicator />
5
+ </div>
6
+ </template>
7
+
8
+ <script setup>
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
+ import * as Cesium from 'cesium'
18
+ import { ref, watch, onMounted, onUnmounted, onActivated, onDeactivated, nextTick, toRaw } from 'vue'
19
+ import { MapConfig, DroneConfig } from './js/config/index.js'
20
+ import WsIndicator from './components/wsIndicator/wsIndicator.vue'
21
+ import { objectUtils } from './js/utils/cesium/object.js'
22
+ import { basicConfig, teardownOrthographicWheelZoom } from './js/basis.js'
23
+ import { useMapStore, MapLoadStatus } from './js/stores/mapStore.js'
24
+ import { useEventBus } from './js/utils/useEventBus.js'
25
+ import { createCustomToolbarButtons } from './js/customToolbarButtons.js'
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
+ // ---------------------------------------------------------------------------
40
+ const { merge } = objectUtils()
41
+ const { mouseController, setInitialCameraView, syncCameraInteractionMode } = basicConfig()
42
+ const {
43
+ addToolbarButton,
44
+ addToolbarButtons,
45
+ removeToolbarButtons,
46
+ createDefaultToolbarButtons,
47
+ } = createCustomToolbarButtons()
48
+ const mapStore = useMapStore()
49
+ const { on: onEvent, off: offEvent } = useEventBus()
50
+
51
+ // ---------------------------------------------------------------------------
52
+ // 1. 组件接口
53
+ // ---------------------------------------------------------------------------
54
+ const emit = defineEmits(['onload'])
55
+ const props = defineProps({
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: () => [] },
61
+ })
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
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // 3. 工具栏
77
+ // ---------------------------------------------------------------------------
78
+ function resolveToolbarButtonConfigs() {
79
+ if (props.toolbarButtons?.length) return props.toolbarButtons
80
+ const configs = []
81
+ if (props.showDefaultToolbar) {
82
+ configs.push(...createDefaultToolbarButtons({ mapId: props.mapId }))
83
+ }
84
+ if (props.extraToolbarButtons?.length) {
85
+ configs.push(...props.extraToolbarButtons)
86
+ }
87
+ return configs
88
+ }
89
+
90
+ function mountToolbarButtons(viewer) {
91
+ if (!viewer || viewer.isDestroyed?.()) return
92
+ removeToolbarButtons(customButtons)
93
+ customButtons = []
94
+ const configs = resolveToolbarButtonConfigs()
95
+ if (configs.length) {
96
+ customButtons = addToolbarButtons(viewer, configs)
97
+ }
98
+ }
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)
106
+ }
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
+ }
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
+ }
144
+
145
+ function finalizeCameraAndRender(viewer, mapOptions, sceneMode) {
146
+ viewer.resize()
147
+ setInitialCameraView(viewer, mapOptions.scene.center, sceneMode, props.mapId)
148
+ viewer.render()
149
+ }
150
+
151
+ // ---------------------------------------------------------------------------
152
+ // 5. 地图初始化主流程
153
+ // ---------------------------------------------------------------------------
154
+ async function initCesium() {
155
+ if (!cesiumContainer.value) return
156
+
157
+ mapStore.setMapLoadSta(MapLoadStatus.LOADING, props.mapId)
158
+ if (DroneConfig?.trailTime != null) {
159
+ mapStore.setTrailTime(DroneConfig.trailTime, props.mapId)
160
+ }
161
+
162
+ let mapOptions
163
+ try {
164
+ mapOptions = await resolveMapOptions({
165
+ mapConfig: MapConfig,
166
+ propsOptions: props.options,
167
+ merge,
168
+ toRaw,
169
+ })
170
+ if (!mapOptions) {
171
+ throw new Error('缺少地图配置 MapConfig / options')
172
+ }
173
+
174
+ // 1. Viewer:控件、地形、渲染循环策略
175
+ const viewerOptions = await buildViewerOptions(mapOptions)
176
+ const contextOptions = buildContextOptions()
177
+ map = createMapViewer(cesiumContainer.value, viewerOptions, contextOptions)
178
+
179
+ // 2. Scene / Globe:光照、雾、大气
180
+ const sceneRenderOptions = buildSceneRenderOptions(mapOptions)
181
+ applySceneRenderOptions(map, sceneRenderOptions)
182
+
183
+ // 3. Imagery:在线底图
184
+ applyConfiguredImagery(map, mapOptions)
185
+
186
+ // 4. Input:双击、选中、鼠标扩展
187
+ applyViewerInputPolicy(map, mapOptions)
188
+ mouseController(map, props.mapId)
189
+
190
+ // 5. 业务上下文:Pinia + 工具栏 + 加载态
191
+ const { center, sceneMode } = registerMapContext(map, mapOptions)
192
+ mountToolbarButtons(map)
193
+ mapStore.setMapLoadSta(MapLoadStatus.LOADED, props.mapId)
194
+
195
+ // 6. 相机与首帧(须在容器尺寸稳定后 resize)
196
+ finalizeCameraAndRender(map, mapOptions, sceneMode)
197
+
198
+ // 7. 通知宿主(相机、工具栏已就绪)
199
+ emitMapReady(map, center)
200
+ console.log(`Cesium 地图加载成功,mapId: ${props.mapId}`)
201
+
202
+ // 8. 自管渲染循环(useDefaultRenderLoop: false)
203
+ startContinuousRendering(map)
204
+ } catch (error) {
205
+ mapStore.setMapLoadSta(MapLoadStatus.FAILED, props.mapId)
206
+ console.error('Cesium 地图加载失败:', error)
207
+ }
208
+ }
209
+
210
+ // ---------------------------------------------------------------------------
211
+ // 6. 渲染循环
212
+ // ---------------------------------------------------------------------------
213
+ function startContinuousRendering(viewer) {
214
+ if (animationId) return
215
+ const tick = () => {
216
+ const now = Date.now()
217
+ if (
218
+ isMapActive &&
219
+ !document.hidden &&
220
+ map &&
221
+ !map.isDestroyed?.() &&
222
+ now - lastActiveTime < IDLE_TIMEOUT
223
+ ) {
224
+ viewer.render()
225
+ }
226
+ animationId = requestAnimationFrame(tick)
227
+ }
228
+ tick()
229
+ }
230
+
231
+ function stopRenderLoop() {
232
+ if (animationId) {
233
+ cancelAnimationFrame(animationId)
234
+ animationId = null
235
+ }
236
+ }
237
+
238
+ function setupActivityListeners() {
239
+ ;['mousemove', 'keydown', 'touchmove', 'wheel', 'click'].forEach((event) => {
240
+ document.addEventListener(event, () => {
241
+ lastActiveTime = Date.now()
242
+ })
243
+ })
244
+ }
245
+
246
+ // ---------------------------------------------------------------------------
247
+ // 7. keep-alive 恢复
248
+ // ---------------------------------------------------------------------------
249
+ function resumeMapAfterCache() {
250
+ if (!map || map.isDestroyed?.()) return
251
+ mapStore.setCurrentMapId(props.mapId)
252
+ isMapActive = true
253
+ lastActiveTime = Date.now()
254
+
255
+ nextTick(() => {
256
+ if (!map || map.isDestroyed?.()) return
257
+ map.resize()
258
+ const mode = mapStore.getSceneMode(props.mapId)
259
+ syncCameraInteractionMode(map, props.mapId, mode === '2D' ? '2D' : '3D')
260
+ map.render()
261
+ if (!animationId) startContinuousRendering(map)
262
+ })
263
+ }
264
+
265
+ // ---------------------------------------------------------------------------
266
+ // 8. 生命周期
267
+ // ---------------------------------------------------------------------------
268
+ watch(
269
+ () => props.toolbarButtons,
270
+ () => {
271
+ if (map && !map.isDestroyed?.()) mountToolbarButtons(map)
272
+ },
273
+ { deep: true },
274
+ )
275
+
276
+ onMounted(() => {
277
+ mapStore.setCurrentMapId(props.mapId)
278
+ setupActivityListeners()
279
+ onEvent('map-scene-mode-changed', handleSceneModeChange)
280
+ initCesium()
281
+ })
282
+
283
+ onActivated(() => {
284
+ mapStore.setCurrentMapId(props.mapId)
285
+ if (!map || map.isDestroyed?.()) {
286
+ isMapActive = true
287
+ initCesium()
288
+ return
289
+ }
290
+ resumeMapAfterCache()
291
+ })
292
+
293
+ onDeactivated(() => {
294
+ isMapActive = false
295
+ stopRenderLoop()
296
+ })
297
+
298
+ onUnmounted(() => {
299
+ isMapActive = false
300
+ stopRenderLoop()
301
+ offEvent('map-scene-mode-changed', handleSceneModeChange)
302
+ if (map) teardownOrthographicWheelZoom(map)
303
+ removeToolbarButtons(customButtons)
304
+ customButtons = []
305
+ if (map) {
306
+ map.destroy()
307
+ map = null
308
+ }
309
+ mapStore.removeMapInstance(props.mapId)
310
+ })
311
+ </script>
312
+
313
+ <style scoped lang="less">
314
+ .cesium-map-wrapper {
315
+ width: 100%;
316
+ height: 100%;
317
+ position: relative;
318
+ }
319
+
320
+ .cesium-container {
321
+ width: 100%;
322
+ height: 100%;
323
+ position: relative;
324
+ }
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.4",
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",