huweili-cesium 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.vue CHANGED
@@ -1,474 +1,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
- import * as Cesium from 'cesium'
10
- import { ref, onMounted, onUnmounted, onActivated, onDeactivated, nextTick, toRaw } from 'vue'
11
- import { MapConfig, DroneConfig } from './js/config/index'
12
- import WsIndicator from './components/wsIndicator/wsIndicator.vue'
13
- import { objectUtils } from './js/utils/cesium/object'
14
- import { basicConfig } from './js/basis'
15
- import { useMapStore, MapLoadStatus } from './js/stores/mapStore'
16
- import { processMapConfigColors } from './js/tileProviders'
17
- import { applyImageryLayers, resolveOnlineBasemap } from './js/utils/mapImagery'
18
- import { createCustomToolbarButtons } from './js/customToolbarButtons'
19
-
20
- const { merge } = objectUtils()
21
- const { mouseController, setInitialCameraView } = basicConfig()
22
-
23
- const {
24
- addToolbarButton,
25
- addToolbarButtons,
26
- removeToolbarButtons,
27
- createDefaultToolbarButtons,
28
- } = createCustomToolbarButtons()
29
-
30
- /** 根据 props 组装最终要渲染的工具栏按钮配置 */
31
- function resolveToolbarButtonConfigs() {
32
- if (props.toolbarButtons?.length) {
33
- return props.toolbarButtons
34
- }
35
- const configs = []
36
- if (props.showDefaultToolbar) {
37
- configs.push(...createDefaultToolbarButtons({ mapId: props.mapId }))
38
- }
39
- if (props.extraToolbarButtons?.length) {
40
- configs.push(...props.extraToolbarButtons)
41
- }
42
- return configs
43
- }
44
-
45
- // 获取store实例,保持响应性
46
- const mapStore = useMapStore()
47
-
48
- // onload事件将在地图渲染后触发
49
- const emit = defineEmits(["onload"])
50
-
51
- // ✅ 正确:纯 JS 写法,去掉 withDefaults
52
- const props = defineProps({
53
- options: {
54
- type: Object,
55
- default: undefined
56
- },
57
- mapId: {
58
- type: String,
59
- default: 'default'
60
- },
61
- /** 是否挂载 npm 包内置的默认工具栏按钮 */
62
- showDefaultToolbar: {
63
- type: Boolean,
64
- default: true
65
- },
66
- /**
67
- * 完全自定义工具栏按钮配置(传入后不再使用内置默认按钮)
68
- * 项格式同 createCustomToolbarButtons 的 addToolbarButton options
69
- */
70
- toolbarButtons: {
71
- type: Array,
72
- default: undefined
73
- },
74
- /** 在默认按钮之后追加的额外按钮(各项目可在此扩展) */
75
- extraToolbarButtons: {
76
- type: Array,
77
- default: () => []
78
- }
79
- })
80
-
81
- // 容器引用
82
- const cesiumContainer = ref(null)
83
- // 用于存放地球组件实例
84
- let map = null
85
- let customButtons = []
86
- let isMapActive = true
87
-
88
- const initCesium = async () => {
89
- if (!cesiumContainer.value) return
90
- mapStore.setMapLoadSta(MapLoadStatus.LOADING, props.mapId)
91
-
92
- // 获取配置
93
- let mapOptions
94
- if (MapConfig) {
95
- mapOptions = MapConfig
96
- }
97
-
98
- if (DroneConfig) {
99
- const configOptions = DroneConfig
100
- mapStore.setTrailTime(configOptions.trailTime, props.mapId)
101
- }
102
-
103
- if (props.options) {
104
- // 存在叠加的属性时
105
- let exOptions
106
- if (props.options.then) {
107
- exOptions = toRaw(await props.options)
108
- } else {
109
- exOptions = toRaw(props.options)
110
- }
111
-
112
- if (mapOptions) {
113
- mapOptions = merge(mapOptions, exOptions) // 合并配置
114
- } else {
115
- mapOptions = exOptions
116
- }
117
- }
118
-
119
- // 统一处理配置中的颜色,将十六进制转换为RGB
120
- if (mapOptions) {
121
- mapOptions = processMapConfigColors(mapOptions)
122
- }
123
-
124
- try {
125
- // 初始化 Cesium 地球
126
- // ==================== Cesium 配置统一管理 ====================
127
-
128
- // Viewer 核心配置
129
- const viewerOptions = {
130
- // 场景模式:固定为 3D 模式
131
- sceneMode: Cesium.SceneMode.SCENE3D,
132
- // 禁用默认渲染循环,由应用控制渲染时机
133
- useDefaultRenderLoop: false,
134
- // 底图配置:为 false 时禁用默认底图
135
- baseLayer: mapOptions.control.imageryProvider === false ? false : undefined,
136
- // 底图选择器
137
- baseLayerPicker: mapOptions.control.baseLayerPicker,
138
- // 地址搜索
139
- geocoder: mapOptions.control.geocoder,
140
- // 主页按钮
141
- homeButton: mapOptions.control.homeButton,
142
- // 场景模式选择器
143
- sceneModePicker: mapOptions.control.sceneModePicker,
144
- // 导航帮助按钮
145
- navigationHelpButton: mapOptions.control.navigationHelpButton,
146
- // 动画控件
147
- animation: mapOptions.control.animation,
148
- // 时间轴
149
- timeline: mapOptions.control.timeline,
150
- // 信息框
151
- infoBox: mapOptions.control.infoBox,
152
- // 全屏按钮
153
- fullscreenButton: mapOptions.control.fullscreenButton,
154
- // VR 按钮
155
- vrButton: mapOptions.control.vrButton,
156
- // 地形服务配置
157
- terrainProvider: mapOptions.terrain.show ? await Cesium.CesiumTerrainProvider.fromUrl(mapOptions.terrain.url, {
158
- requestWaterMask: mapOptions.terrain.coastlineData, // 请求水体效果所需要的海岸线数据
159
- requestVertexNormals: mapOptions.terrain.lightingData, // 请求地形照明数据
160
- }) : undefined,
161
- // 地形阴影:只接收阴影,不投射阴影,节省性能
162
- terrainShadows: Cesium.ShadowMode.RECEIVE_ONLY,
163
- // 隐藏版权水印:用空容器替换
164
- creditContainer: document.createElement('div'),
165
- }
166
-
167
- // WebGL 上下文配置
168
- const contextOptions = {
169
- webgl: {
170
- alpha: false, // 禁用透明度通道
171
- depth: true, // 启用深度缓冲区
172
- stencil: false, // 禁用模板缓冲区
173
- antialias: true, // 启用抗锯齿
174
- premultipliedAlpha: true, // 启用预乘透明度
175
- preserveDrawingBuffer: false, // 不保留绘制缓冲区
176
- failIfMajorPerformanceCaveat: false // 允许在性能受限环境中运行
177
- },
178
- requestWebGl2: true, // 启用 WebGL 2.0
179
- powerPreference: 'high-performance' // 优先使用高性能 GPU
180
- }
181
-
182
- // 场景渲染配置(需要在 Viewer 创建后应用)
183
- const sceneRenderOptions = {
184
- // 显示帧速(FPS)
185
- debugShowFramesPerSecond: mapOptions.scene.debugShowFramesPerSecond,
186
- // 地球光照效果
187
- enableLighting: mapOptions.scene.globe.enableLighting,
188
- // 地形深度测试,确保在地形上的实体正确渲染
189
- depthTestAgainstTerrain: mapOptions.scene.globe.depthTestAgainstTerrain,
190
- // 雾效
191
- fogEnabled: mapOptions.scene.fog.enabled,
192
- // 大气层光环
193
- skyAtmosphereShow: mapOptions.scene.skyAtmosphere.show,
194
- // 动态大气光照
195
- dynamicAtmosphereLighting: mapOptions.scene.globe.dynamicAtmosphereLighting,
196
- // 动态大气光照从太阳开始
197
- dynamicAtmosphereLightingFromSun: mapOptions.scene.globe.dynamicAtmosphereLightingFromSun
198
- }
199
-
200
- // ==================== 创建 Viewer 实例 ====================
201
- map = new Cesium.Viewer(cesiumContainer.value, {
202
- ...viewerOptions,
203
- contextOptions
204
- })
205
-
206
- // 应用场景渲染配置
207
- map.scene.debugShowFramesPerSecond = sceneRenderOptions.debugShowFramesPerSecond;
208
- map.scene.globe.enableLighting = sceneRenderOptions.enableLighting;
209
- map.scene.globe.depthTestAgainstTerrain = sceneRenderOptions.depthTestAgainstTerrain;
210
- map.scene.fog.enabled = sceneRenderOptions.fogEnabled;
211
- map.scene.skyAtmosphere.show = sceneRenderOptions.skyAtmosphereShow;
212
- map.scene.globe.dynamicAtmosphereLighting = sceneRenderOptions.dynamicAtmosphereLighting;
213
- map.scene.globe.dynamicAtmosphereLightingFromSun = sceneRenderOptions.dynamicAtmosphereLightingFromSun;
214
-
215
- // 设置地图初始视角(中心点、经纬度、高度、航向等)
216
- setInitialCameraView(map, mapOptions.scene.center)
217
-
218
- // 在线底图 + 可选离线共存(城市范围内优先离线瓦片)
219
- const activeBasemap = resolveOnlineBasemap(mapOptions)
220
- if (activeBasemap) {
221
- applyImageryLayers(map, mapOptions, activeBasemap)
222
- console.log(`加载在线底图:${activeBasemap.name},URL:${activeBasemap.url}`)
223
- window._currentBasemapId = activeBasemap.id
224
- } else {
225
- console.warn('未找到可用底图,Cesium 将使用默认底图或空底图显示')
226
- }
227
-
228
- // 禁用默认的双击行为 (双击缩放)
229
- if (map.screenSpaceEventHandler && mapOptions.control.disableDoubleClick) {
230
- // 移除默认的双击行为
231
- map.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
232
- }
233
-
234
- // 单击 entity 事件
235
- if (mapOptions.control.disableEntityClick) {
236
- map.selectedEntityChanged.addEventListener(() => {
237
- map.selectedEntity = undefined; // 永远不让实体被选中
238
- });
239
- }
240
-
241
- // 初始化鼠标控制器
242
- mouseController(map, props.mapId);
243
-
244
- // 保存地图中心点、旋转角度、俯仰角度
245
- mapStore.setMapInfo('center', {
246
- lng: mapOptions.scene.center.lng,
247
- lat: mapOptions.scene.center.lat,
248
- heading: mapOptions.scene.center.heading,
249
- pitch: mapOptions.scene.center.pitch,
250
- roll: mapOptions.scene.center.roll,
251
- duration: mapOptions.scene.center.duration,
252
- alt: mapOptions.scene.center.alt,
253
- }, props.mapId)
254
- mapStore.setMap(map, props.mapId)
255
-
256
- // 设置场景模式(2D/3D)
257
- const sceneMode = mapOptions.scene.sceneMode || '3D';
258
- mapStore.setSceneMode(sceneMode, props.mapId);
259
-
260
- // 初始化半球区域
261
- const center = mapOptions.scene.center || {}
262
-
263
- // 在主页按钮附近添加工具栏按钮(默认 + 项目扩展,或完全自定义)
264
- const toolbarConfigs = resolveToolbarButtonConfigs()
265
- if (toolbarConfigs.length) {
266
- customButtons = addToolbarButtons(map, toolbarConfigs)
267
- }
268
-
269
- // 设置地图加载状态为已加载
270
- mapStore.setMapLoadSta(MapLoadStatus.LOADED, props.mapId)
271
-
272
- console.log(`Cesium 地图加载成功,mapId: ${props.mapId}`)
273
- emit("onload", {
274
- map,
275
- center,
276
- mapId: props.mapId,
277
- toolbar: {
278
- addToolbarButton: (opt) => addToolbarButton(map, opt),
279
- addToolbarButtons: (opts) => addToolbarButtons(map, opts),
280
- removeToolbarButtons,
281
- },
282
- })
283
-
284
- map.resize()
285
- map.render()
286
-
287
- // 添加持续渲染循环,避免浏览器进入节能模式
288
- startContinuousRendering(map)
289
- } catch (error) {
290
- mapStore.setMapLoadSta(MapLoadStatus.FAILED, props.mapId)
291
- console.error('Cesium 地图加载失败:', error)
292
- }
293
- }
294
-
295
- // 持续渲染循环(避免浏览器节能模式)
296
- let animationId = null
297
- let lastActiveTime = Date.now()
298
- const IDLE_TIMEOUT = 30000 // 30秒无活动后暂停
299
-
300
- function startContinuousRendering(viewer) {
301
- if (animationId) return
302
- function animate() {
303
- const now = Date.now()
304
- if (
305
- isMapActive &&
306
- !document.hidden &&
307
- map &&
308
- !map.isDestroyed?.() &&
309
- now - lastActiveTime < IDLE_TIMEOUT
310
- ) {
311
- viewer.render()
312
- }
313
- animationId = requestAnimationFrame(animate)
314
- }
315
- animate()
316
- }
317
-
318
- /**
319
- * 停止持续渲染循环
320
- *
321
- * 功能说明:
322
- * - 取消 requestAnimationFrame 创建的动画帧请求
323
- * - 清空 animationId,防止重复取消
324
- * - 用于页面失活、组件卸载或需要暂停渲染时释放资源
325
- *
326
- * 使用场景:
327
- * - 页面被 keep-alive 缓存时(onDeactivated)
328
- * - 组件卸载时(onUnmounted)
329
- * - 手动暂停地图渲染以节省性能时
330
- */
331
- function stopRenderLoop() {
332
- if (animationId) {
333
- cancelAnimationFrame(animationId)
334
- animationId = null
335
- }
336
- }
337
-
338
- // 监听用户活动,重置活跃时间
339
- function setupActivityListeners() {
340
- const activities = ['mousemove', 'keydown', 'touchmove', 'wheel', 'click']
341
- activities.forEach(event => {
342
- document.addEventListener(event, () => {
343
- lastActiveTime = Date.now()
344
- })
345
- })
346
- }
347
-
348
- /**
349
- * 从缓存中恢复地图渲染(用于 keep-alive 场景)
350
- *
351
- * 功能说明:
352
- * - 当页面被 keep-alive 缓存后再次激活时,恢复 Cesium 地图的渲染状态
353
- * - 重新设置当前地图 ID、激活状态和最后活跃时间
354
- * - 在下一个 tick 中调整地图大小、触发渲染并重启持续渲染循环
355
- *
356
- * 使用场景:
357
- * - Vue 路由中使用 keep-alive 缓存包含地图的组件,切换回该页面时调用
358
- * - 在 onActivated 生命周期钩子中触发
359
- */
360
- function resumeMapAfterCache() {
361
- if (!map || map.isDestroyed?.()) return
362
- mapStore.setCurrentMapId(props.mapId)
363
- isMapActive = true
364
- lastActiveTime = Date.now()
365
- nextTick(() => {
366
- if (!map || map.isDestroyed?.()) return
367
- map.resize()
368
- map.render()
369
- if (!animationId) startContinuousRendering(map)
370
- })
371
- }
372
-
373
- /**
374
- * 组件挂载钩子(首次加载时触发)
375
- *
376
- * 触发时机:
377
- * - 组件首次挂载到 DOM 时
378
- * - 无论是否使用 keep-alive,首次加载都会触发
379
- *
380
- * 功能说明:
381
- * - 设置当前地图 ID
382
- * - 监听用户活动事件
383
- * - 初始化 Cesium 地图
384
- */
385
- onMounted(() => {
386
- mapStore.setCurrentMapId(props.mapId)
387
- setupActivityListeners()
388
- initCesium()
389
- })
390
-
391
- /**
392
- * keep-alive 组件激活钩子(从缓存中恢复时触发)
393
- *
394
- * 触发时机:
395
- * - 当前组件被 keep-alive 包裹,用户导航回到该页面时
396
- * - 组件从缓存状态恢复到活动状态
397
- *
398
- * 功能说明:
399
- * - 重新设置当前地图 ID
400
- * - 如果地图已销毁,重新初始化地图
401
- * - 如果地图还在缓存中,恢复地图渲染
402
- */
403
- onActivated(() => {
404
- mapStore.setCurrentMapId(props.mapId)
405
- if (!map || map.isDestroyed?.()) {
406
- isMapActive = true
407
- initCesium()
408
- return
409
- }
410
- resumeMapAfterCache()
411
- })
412
-
413
- /**
414
- * keep-alive 组件失活钩子(页面被缓存时触发)
415
- *
416
- * 触发时机:
417
- * - 当前组件被 keep-alive 包裹,用户导航离开该页面时
418
- * - 组件进入缓存状态,但不会被销毁
419
- *
420
- * 功能说明:
421
- * - 标记地图为非活跃状态
422
- * - 停止持续渲染循环,节省 CPU 资源
423
- *
424
- * 与 onUnmounted 的区别:
425
- * - onDeactivated:组件被缓存,可通过 onActivated 恢复
426
- * - onUnmounted:组件被销毁,需要完全清理资源
427
- */
428
- onDeactivated(() => {
429
- isMapActive = false
430
- stopRenderLoop()
431
- })
432
-
433
- /**
434
- * 组件卸载钩子(完全销毁时触发)
435
- *
436
- * 触发时机:
437
- * - 组件被完全移除时
438
- * - 路由切换且未使用 keep-alive 缓存时
439
- *
440
- * 功能说明:
441
- * - 停止渲染循环
442
- * - 移除自定义工具栏按钮
443
- * - 销毁 Cesium Viewer 实例释放内存
444
- * - 从 store 中移除地图实例
445
- */
446
- onUnmounted(() => {
447
- isMapActive = false
448
- stopRenderLoop()
449
- removeToolbarButtons(customButtons)
450
- customButtons = []
451
- if (map) {
452
- map.destroy()
453
- map = null
454
- }
455
- mapStore.removeMapInstance(props.mapId)
456
- })
457
-
458
- </script>
459
-
460
- <style scoped lang="less">
461
- .cesium-map-wrapper {
462
- width: 100%;
463
- height: 100%;
464
- position: relative;
465
- }
466
-
467
- .cesium-container {
468
- width: 100%;
469
- height: 100%;
470
- position: relative;
471
- }
472
-
473
-
474
- </style>
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
+
package/js/basis.js CHANGED
@@ -10,6 +10,11 @@
10
10
  */
11
11
  import * as Cesium from 'cesium'
12
12
  import { useMapStore } from './stores/mapStore.js'
13
+ import {
14
+ syncCameraInteractionMode,
15
+ teardownOrthographicWheelZoom,
16
+ restorePerspectiveControllerState,
17
+ } from './utils/cameraInteraction.js'
13
18
 
14
19
  export function basicConfig() {
15
20
 
@@ -71,29 +76,42 @@ export function basicConfig() {
71
76
  * roll: 0 // 不翻滚
72
77
  * })
73
78
  */
74
- const setInitialCameraView = (viewer, center = {}) => {
75
- // 安全检查:确保 viewer 和 camera 对象存在
79
+ const orthoViewHelpers = () => ({
80
+ applyOrthographicFrustum,
81
+ getEffective2DOrthoWidth,
82
+ })
83
+
84
+ /**
85
+ * 设置 Cesium 地图初始相机视角(支持 2D 正交 / 3D 透视)
86
+ * @param {Cesium.Viewer} viewer
87
+ * @param {Object} center
88
+ * @param {'2D'|'3D'} sceneMode
89
+ * @param {string=} mapId
90
+ */
91
+ const setInitialCameraView = (viewer, center = {}, sceneMode = '3D', mapId) => {
76
92
  if (!viewer?.camera) return
77
93
 
78
- // center 对象中提取参数,使用默认值确保容错性
79
- const lng = Number(center.lng) || 0 // 经度,默认 0°
80
- const lat = Number(center.lat) || 0 // 纬度,默认 0°
81
- const alt = Number(center.alt) || 10000 // 高度,默认 10000 米
82
- const heading = Cesium.Math.toRadians(Number(center.heading) || 0) // 航向角 → 弧度
83
- const pitch = Cesium.Math.toRadians(-90) // 俯仰角,固定 -90°(垂直俯视)
84
- const roll = Cesium.Math.toRadians(Number(center.roll) || 0) // 翻滚角 → 弧度
94
+ const lng = Number(center.lng) || 0
95
+ const lat = Number(center.lat) || 0
96
+ const alt = Number(center.alt) || 10000
97
+ const heading = Cesium.Math.toRadians(Number(center.heading) || 0)
98
+ const pitch = Cesium.Math.toRadians(Number(center.pitch) ?? -90)
99
+ const roll = Cesium.Math.toRadians(Number(center.roll) || 0)
100
+
101
+ if (sceneMode === '2D') {
102
+ const orthoWidth = computeOrthoWidthFromHeight(viewer, alt)
103
+ if (mapId && orthoWidth > 0) {
104
+ mapStore.setViewOrthoWidth(orthoWidth, mapId)
105
+ }
106
+ set2DView(viewer, lng, lat, alt, orthoWidth, mapId)
107
+ return
108
+ }
85
109
 
86
- // 设置相机视角
87
110
  viewer.camera.setView({
88
- // 相机目标位置:使用经纬度坐标和高度创建 Cartesian3 坐标
89
111
  destination: Cesium.Cartesian3.fromDegrees(lng, lat, alt),
90
- // 相机朝向:通过 headingpitchroll 三个角度定义
91
- orientation: {
92
- heading, // 航向角:绕垂直轴旋转(0=北,90=东,180=南,270=西)
93
- pitch, // 俯仰角:绕水平轴旋转(-90=正下方,0=水平,90=正上方)
94
- roll, // 翻滚角:绕相机朝向轴旋转
95
- },
112
+ orientation: { heading, pitch, roll },
96
113
  })
114
+ syncCameraInteractionMode(viewer, mapId, '3D', orthoViewHelpers())
97
115
  }
98
116
 
99
117
  /**
@@ -102,7 +120,7 @@ export function basicConfig() {
102
120
  * 处理地图上的鼠标事件,包括点击、拖动、缩放等、
103
121
  * @param map - 地图实例
104
122
  */
105
- const mouseController = (map) => {
123
+ const mouseController = (map, _mapId) => {
106
124
  // 添加右键点击事件监听
107
125
  map.screenSpaceEventHandler.setInputAction((click) => {
108
126
  // 获取点击位置的笛卡尔坐标
@@ -181,7 +199,7 @@ export function basicConfig() {
181
199
  if (mapId && orthoWidth > 0) {
182
200
  mapStore.setViewOrthoWidth(orthoWidth, mapId)
183
201
  }
184
- set2DView(map, lng, lat, finalAltitude, orthoWidth)
202
+ set2DView(map, lng, lat, finalAltitude, orthoWidth, mapId)
185
203
  } else {
186
204
  if (mapId) {
187
205
  const pitchDeg = Math.abs(centerInfo.pitch ?? -35)
@@ -189,7 +207,7 @@ export function basicConfig() {
189
207
  const range = Math.max(finalAltitude / Math.max(Math.sin(pitchRad), 0.1), 100)
190
208
  mapStore.setViewOrthoWidth(rangeToOrthoWidth(map, range), mapId)
191
209
  }
192
- set3DView(map, lng, lat, centerInfo, finalAltitude, finalTargetAlt)
210
+ set3DView(map, lng, lat, centerInfo, finalAltitude, finalTargetAlt, undefined, mapId)
193
211
  }
194
212
  }
195
213
 
@@ -240,6 +258,8 @@ export function basicConfig() {
240
258
  const restorePerspectiveFrustum = (map) => {
241
259
  if (!map) return
242
260
 
261
+ if (map.camera.frustum instanceof Cesium.PerspectiveFrustum) return
262
+
243
263
  const currentFrustum = map.camera.frustum
244
264
  const perspectiveFrustum = new Cesium.PerspectiveFrustum()
245
265
  perspectiveFrustum.fov = getPerspectiveVerticalFovRadians()
@@ -291,7 +311,6 @@ export function basicConfig() {
291
311
  if (!map) return
292
312
 
293
313
  const canvas = map.canvas
294
- const frustum = new Cesium.OrthographicFrustum()
295
314
  const width = canvas.clientWidth
296
315
  const height = canvas.clientHeight
297
316
 
@@ -303,10 +322,22 @@ export function basicConfig() {
303
322
  } else if (map.scene.camera.frustum instanceof Cesium.PerspectiveFrustum) {
304
323
  const cameraHeight = map.scene.camera.positionCartographic?.height ?? 10000
305
324
  widthValue = computeOrthoWidthFromHeight(map, cameraHeight)
325
+ } else if (map.scene.camera.frustum instanceof Cesium.OrthographicFrustum) {
326
+ widthValue = map.scene.camera.frustum.width || getEffective2DOrthoWidth(map) || 20000
306
327
  } else {
307
- widthValue = map.scene.camera.frustum.width || 20000
328
+ widthValue = 20000
308
329
  }
309
330
 
331
+ const currentFrustum = map.scene.camera.frustum
332
+ if (currentFrustum instanceof Cesium.OrthographicFrustum) {
333
+ currentFrustum.width = widthValue
334
+ currentFrustum.aspectRatio = width / height
335
+ currentFrustum.near = 0.1
336
+ currentFrustum.far = 10000000.0
337
+ return
338
+ }
339
+
340
+ const frustum = new Cesium.OrthographicFrustum()
310
341
  frustum.width = widthValue
311
342
  frustum.aspectRatio = width / height
312
343
  frustum.near = 0.1
@@ -322,7 +353,7 @@ export function basicConfig() {
322
353
  * @param {number=} height 相机高度
323
354
  * @param {number=} orthoWidth 正交可视宽度(米),切换 2D/3D 时传入以保持缩放不变
324
355
  */
325
- const set2DView = (map, lng, lat, height, orthoWidth) => {
356
+ const set2DView = (map, lng, lat, height, orthoWidth, mapId) => {
326
357
  if (!map) return
327
358
 
328
359
  const resolvedWidth = Number.isFinite(orthoWidth) && orthoWidth > 0
@@ -337,15 +368,17 @@ export function basicConfig() {
337
368
  cameraController.enableTilt = false
338
369
  cameraController.enableLook = false
339
370
  }
371
+
340
372
  map.camera.setView({
341
373
  destination: Cesium.Cartesian3.fromDegrees(lng, lat, resolvedHeight),
342
374
  orientation: {
343
375
  heading: Cesium.Math.toRadians(0),
344
376
  pitch: Cesium.Math.toRadians(-90),
345
- roll: Cesium.Math.toRadians(0)
346
- }
377
+ roll: Cesium.Math.toRadians(0),
378
+ },
347
379
  })
348
380
  applyOrthographicFrustum(map, undefined, resolvedWidth)
381
+ syncCameraInteractionMode(map, mapId, '2D', orthoViewHelpers())
349
382
  }
350
383
 
351
384
  /**
@@ -366,7 +399,7 @@ export function basicConfig() {
366
399
  * @param {number=} targetAlt 目标点的高度(比如无人机的飞行高度,默认0)
367
400
  * @param {number=} slantRange 可选,相机到目标点的直线距离(米);传入时优先于 altitude 换算
368
401
  */
369
- const set3DView = (map, lng, lat, centerInfo = {}, altitude = 10000, targetAlt = 0, slantRange) => {
402
+ const set3DView = (map, lng, lat, centerInfo = {}, altitude = 10000, targetAlt = 0, slantRange, mapId) => {
370
403
  // 1. 检查地图实例是否存在,不存在就直接返回
371
404
  if (!map) return
372
405
 
@@ -418,8 +451,14 @@ export function basicConfig() {
418
451
  )
419
452
  )
420
453
 
421
- // 9. 重置相机变换(确保lookAt生效后恢复正常状态)
422
454
  map.camera.lookAtTransform(Cesium.Matrix4.IDENTITY)
455
+
456
+ if (mapId) {
457
+ syncCameraInteractionMode(map, mapId, '3D', orthoViewHelpers())
458
+ } else {
459
+ teardownOrthographicWheelZoom(map)
460
+ restorePerspectiveControllerState(map)
461
+ }
423
462
  }
424
463
 
425
464
  /**
@@ -508,14 +547,14 @@ export function basicConfig() {
508
547
  if (Number.isFinite(slantRange) && slantRange > 0) {
509
548
  const orthoWidth = Math.max(rangeToOrthoWidth(map, slantRange), 100)
510
549
  mapStore.setViewOrthoWidth(orthoWidth, mapId)
511
- set3DView(map, lng, lat, centerInfo, undefined, 0, slantRange)
550
+ set3DView(map, lng, lat, centerInfo, undefined, 0, slantRange, mapId)
512
551
  return
513
552
  }
514
553
 
515
554
  const orthoWidth = getEffective2DOrthoWidth(map) || resolveViewOrthoWidth(map, mapId, centerInfo)
516
555
  mapStore.setViewOrthoWidth(orthoWidth, mapId)
517
556
  const range = Math.max(orthoWidthToRange(map, orthoWidth), 100)
518
- set3DView(map, lng, lat, centerInfo, undefined, 0, range)
557
+ set3DView(map, lng, lat, centerInfo, undefined, 0, range, mapId)
519
558
  }
520
559
 
521
560
  /**
@@ -541,7 +580,7 @@ export function basicConfig() {
541
580
  }
542
581
  }
543
582
 
544
- set2DView(map, lng, lat, undefined, orthoWidth)
583
+ set2DView(map, lng, lat, undefined, orthoWidth, mapId)
545
584
  }
546
585
 
547
586
  /**
@@ -670,6 +709,10 @@ export function basicConfig() {
670
709
  set3DView,
671
710
  applyOrthographicFrustum,
672
711
  restorePerspectiveFrustum,
673
- setInitialCameraView
712
+ setInitialCameraView,
713
+ syncCameraInteractionMode: (map, mapId, sceneMode) =>
714
+ syncCameraInteractionMode(map, mapId, sceneMode, orthoViewHelpers()),
674
715
  }
675
- }
716
+ }
717
+
718
+ export { teardownOrthographicWheelZoom, ensureMapFocus } from './utils/cameraInteraction.js'
@@ -129,6 +129,9 @@ export function createCustomToolbarButtons() {
129
129
  const result = toggleViewToRtk(mapId)
130
130
  if (result.success && btn) btn.innerText = result.currentMode
131
131
  mapStore.setSceneMode(result.currentMode, mapId)
132
+ if (result.success) {
133
+ emit('map-scene-mode-changed', { mapId, sceneMode: result.currentMode })
134
+ }
132
135
  }
133
136
  },
134
137
  {
package/js/index.js CHANGED
@@ -30,6 +30,12 @@ export { default as eventBus } from './utils/eventBus.js'
30
30
  export { useEventBus } from './utils/useEventBus.js'
31
31
  export { getMapCenterPosition } from './utils/getMapCenterPosition.js'
32
32
  export { selectDrone, deselectDrone } from './utils/droneSelection.js'
33
+ export { applyOrthographicZoomFactor } from './utils/orthographicCameraZoom.js'
34
+ export {
35
+ ensureMapFocus,
36
+ teardownOrthographicWheelZoom,
37
+ syncCameraInteractionMode,
38
+ } from './utils/cameraInteraction.js'
33
39
 
34
40
  export {
35
41
  BaseConfig,
@@ -0,0 +1,101 @@
1
+ /**
2
+ * 2D 正交 / 3D 透视下的相机交互(滚轮缩放、控制器开关)
3
+ */
4
+ import * as Cesium from 'cesium'
5
+ import { useMapStore } from '../stores/mapStore.js'
6
+ import { applyOrthographicZoomFactor } from './orthographicCameraZoom.js'
7
+
8
+ const ORTHO_WHEEL_ZOOM_RATE = 1.1
9
+ const orthoWheelCleanups = new WeakMap()
10
+
11
+ export function ensureMapFocus(map) {
12
+ const canvas = map?.canvas
13
+ if (!canvas) return
14
+ if (!canvas.hasAttribute('tabindex')) {
15
+ canvas.setAttribute('tabindex', '0')
16
+ }
17
+ canvas.focus({ preventScroll: true })
18
+ }
19
+
20
+ /** 2D:禁用内置缩放,由自定义滚轮接管 */
21
+ export function syncOrthographicControllerState(map) {
22
+ const controller = map.scene?.screenSpaceCameraController
23
+ if (!controller) return
24
+ controller.enableTilt = false
25
+ controller.enableLook = false
26
+ controller.enableZoom = false
27
+ controller.enableCollisionDetection = false
28
+ controller.inertiaZoom = 0
29
+ map.camera.cancelFlight()
30
+ }
31
+
32
+ /** 3D:恢复 Cesium 默认相机交互(含滚轮缩放) */
33
+ export function restorePerspectiveControllerState(map) {
34
+ const controller = map.scene?.screenSpaceCameraController
35
+ if (!controller) return
36
+ controller.enableTilt = true
37
+ controller.enableLook = true
38
+ controller.enableZoom = true
39
+ controller.enableCollisionDetection = true
40
+ controller.inertiaZoom = 0.8
41
+ controller.minimumZoomDistance = 1.0
42
+ controller.maximumZoomDistance = Number.POSITIVE_INFINITY
43
+ map.camera.cancelFlight()
44
+ }
45
+
46
+ function applyOrthographicZoom(map, mapId, delta) {
47
+ const factor = Math.pow(ORTHO_WHEEL_ZOOM_RATE, -delta / 120)
48
+ applyOrthographicZoomFactor(map, mapId, factor)
49
+ }
50
+
51
+ /**
52
+ * 在 canvas capture 阶段拦截滚轮,绕过 ScreenSpaceCameraController 默认缩放
53
+ */
54
+ export function setupOrthographicWheelZoom(map, mapId) {
55
+ if (!map?.canvas) return
56
+ teardownOrthographicWheelZoom(map)
57
+ syncOrthographicControllerState(map)
58
+
59
+ const onWheel = (event) => {
60
+ if (!(map.camera.frustum instanceof Cesium.OrthographicFrustum)) return
61
+ event.preventDefault()
62
+ event.stopImmediatePropagation()
63
+ const delta = -(event.deltaY || event.wheelDelta || 0)
64
+ applyOrthographicZoom(map, mapId, delta)
65
+ }
66
+
67
+ map.canvas.addEventListener('wheel', onWheel, { passive: false, capture: true })
68
+ orthoWheelCleanups.set(map, () => {
69
+ map.canvas.removeEventListener('wheel', onWheel, { capture: true })
70
+ orthoWheelCleanups.delete(map)
71
+ })
72
+ ensureMapFocus(map)
73
+ }
74
+
75
+ export function teardownOrthographicWheelZoom(map) {
76
+ if (!map || map.isDestroyed?.()) return
77
+ orthoWheelCleanups.get(map)?.()
78
+ }
79
+
80
+ /**
81
+ * 按 2D/3D 切换相机交互
82
+ * @param {object} helpers.applyOrthographicFrustum
83
+ * @param {object} helpers.getEffective2DOrthoWidth
84
+ */
85
+ export function syncCameraInteractionMode(map, mapId, sceneMode, helpers) {
86
+ if (!map || map.isDestroyed?.()) return
87
+ const { applyOrthographicFrustum, getEffective2DOrthoWidth } = helpers
88
+ const mapStore = useMapStore()
89
+
90
+ if (sceneMode === '2D') {
91
+ const orthoWidth = mapStore.getViewOrthoWidth(mapId) || getEffective2DOrthoWidth(map)
92
+ if (orthoWidth > 0) {
93
+ applyOrthographicFrustum(map, undefined, orthoWidth)
94
+ }
95
+ setupOrthographicWheelZoom(map, mapId)
96
+ } else {
97
+ teardownOrthographicWheelZoom(map)
98
+ restorePerspectiveControllerState(map)
99
+ }
100
+ map.scene.requestRender()
101
+ }
@@ -0,0 +1,28 @@
1
+ import * as Cesium from 'cesium'
2
+ import { useMapStore } from '../stores/mapStore.js'
3
+
4
+ /**
5
+ * 2D 正交缩放:同步修改 frustum.width 与相机高度,不使用 setView(避免首帧透视 dollying)
6
+ * @returns {boolean} 是否已按正交模式处理
7
+ */
8
+ export function applyOrthographicZoomFactor(map, mapId, factor) {
9
+ const camera = map?.camera
10
+ const frustum = camera?.frustum
11
+ if (!(frustum instanceof Cesium.OrthographicFrustum)) return false
12
+
13
+ frustum.width = Cesium.Math.clamp(frustum.width * factor, 100, 10000000)
14
+
15
+ const cartographic = camera.positionCartographic
16
+ if (cartographic) {
17
+ const newHeight = Cesium.Math.clamp(cartographic.height * factor, 100, 1000000)
18
+ const updated = cartographic.clone()
19
+ updated.height = newHeight
20
+ camera.position = Cesium.Cartographic.toCartesian(updated)
21
+ }
22
+
23
+ if (mapId) {
24
+ useMapStore().setViewOrthoWidth(frustum.width, mapId)
25
+ }
26
+ map.scene.requestRender()
27
+ return true
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huweili-cesium",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -87,16 +87,34 @@
87
87
  "./utils/getMapCenterPosition.js": "./js/utils/getMapCenterPosition.js",
88
88
  "./utils/droneSelection": "./js/utils/droneSelection.js",
89
89
  "./utils/droneSelection.js": "./js/utils/droneSelection.js",
90
+ "./js/utils/droneSelection": "./js/utils/droneSelection.js",
91
+ "./js/utils/droneSelection.js": "./js/utils/droneSelection.js",
92
+ "./utils/orthographicCameraZoom": "./js/utils/orthographicCameraZoom.js",
93
+ "./utils/orthographicCameraZoom.js": "./js/utils/orthographicCameraZoom.js",
94
+ "./utils/cameraInteraction": "./js/utils/cameraInteraction.js",
95
+ "./utils/cameraInteraction.js": "./js/utils/cameraInteraction.js",
96
+ "./js/utils/orthographicCameraZoom": "./js/utils/orthographicCameraZoom.js",
97
+ "./js/utils/orthographicCameraZoom.js": "./js/utils/orthographicCameraZoom.js",
90
98
  "./utils/cesium/object": "./js/utils/cesium/object.js",
91
99
  "./utils/cesium/object.js": "./js/utils/cesium/object.js",
100
+ "./js/utils/cesium/object": "./js/utils/cesium/object.js",
101
+ "./js/utils/cesium/object.js": "./js/utils/cesium/object.js",
92
102
  "./utils/cesium/json": "./js/utils/cesium/json.js",
93
103
  "./utils/cesium/json.js": "./js/utils/cesium/json.js",
104
+ "./js/utils/cesium/json": "./js/utils/cesium/json.js",
105
+ "./js/utils/cesium/json.js": "./js/utils/cesium/json.js",
94
106
  "./config": "./js/config/index.js",
95
107
  "./config/index": "./js/config/index.js",
108
+ "./js/config": "./js/config/index.js",
109
+ "./js/config/index": "./js/config/index.js",
96
110
  "./config/hooks": "./js/config/hooks.js",
97
111
  "./config/hooks.js": "./js/config/hooks.js",
112
+ "./js/config/hooks": "./js/config/hooks.js",
113
+ "./js/config/hooks.js": "./js/config/hooks.js",
98
114
  "./api/gaode": "./js/api/gaode.js",
99
- "./api/gaode.js": "./js/api/gaode.js"
115
+ "./api/gaode.js": "./js/api/gaode.js",
116
+ "./js/api/gaode": "./js/api/gaode.js",
117
+ "./js/api/gaode.js": "./js/api/gaode.js"
100
118
  },
101
119
  "files": [
102
120
  "index.js",
@@ -107,8 +125,7 @@
107
125
  ],
108
126
  "scripts": {
109
127
  "sync": "node scripts/sync-from-source.mjs",
110
- "prepublishOnly": "node scripts/verify-files.mjs",
111
- "prepare": "node scripts/welcome.js"
128
+ "prepublishOnly": "node scripts/verify-files.mjs"
112
129
  },
113
130
  "keywords": [
114
131
  "cesium",