huweili-cesium 1.2.8 → 1.2.10
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 +309 -172
- package/js/index.js +0 -10
- package/package.json +1 -1
- package/js/utils/cesiumMapBootstrap.js +0 -164
package/index.vue
CHANGED
|
@@ -6,14 +6,6 @@
|
|
|
6
6
|
</template>
|
|
7
7
|
|
|
8
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
9
|
import * as Cesium from 'cesium'
|
|
18
10
|
import { ref, watch, onMounted, onUnmounted, onActivated, onDeactivated, nextTick, toRaw } from 'vue'
|
|
19
11
|
import { MapConfig, DroneConfig } from './js/config/index.js'
|
|
@@ -22,21 +14,9 @@ import { objectUtils } from './js/utils/cesium/object.js'
|
|
|
22
14
|
import { basicConfig, teardownOrthographicWheelZoom } from './js/basis.js'
|
|
23
15
|
import { useMapStore, MapLoadStatus } from './js/stores/mapStore.js'
|
|
24
16
|
import { useEventBus } from './js/utils/useEventBus.js'
|
|
17
|
+
import { processMapConfigColors } from './js/tileProviders.js'
|
|
18
|
+
import { applyImageryLayers, resolveOnlineBasemap } from './js/utils/mapImagery.js'
|
|
25
19
|
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
20
|
const { merge } = objectUtils()
|
|
41
21
|
const { mouseController, setInitialCameraView, syncCameraInteractionMode } = basicConfig()
|
|
42
22
|
const {
|
|
@@ -45,38 +25,46 @@ const {
|
|
|
45
25
|
removeToolbarButtons,
|
|
46
26
|
createDefaultToolbarButtons,
|
|
47
27
|
} = createCustomToolbarButtons()
|
|
28
|
+
|
|
48
29
|
const mapStore = useMapStore()
|
|
49
30
|
const { on: onEvent, off: offEvent } = useEventBus()
|
|
50
|
-
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
// 1. 组件接口
|
|
53
|
-
// ---------------------------------------------------------------------------
|
|
54
31
|
const emit = defineEmits(['onload'])
|
|
55
32
|
const props = defineProps({
|
|
56
|
-
options: {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
33
|
+
options: {
|
|
34
|
+
type: Object,
|
|
35
|
+
default: undefined,
|
|
36
|
+
},
|
|
37
|
+
mapId: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: 'default',
|
|
40
|
+
},
|
|
41
|
+
showDefaultToolbar: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: true,
|
|
44
|
+
},
|
|
45
|
+
toolbarButtons: {
|
|
46
|
+
type: Array,
|
|
47
|
+
default: undefined,
|
|
48
|
+
},
|
|
49
|
+
extraToolbarButtons: {
|
|
50
|
+
type: Array,
|
|
51
|
+
default: () => [],
|
|
52
|
+
},
|
|
61
53
|
})
|
|
62
54
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let lastActiveTime = Date.now()
|
|
73
|
-
const IDLE_TIMEOUT = 30000
|
|
74
|
-
|
|
75
|
-
// ---------------------------------------------------------------------------
|
|
76
|
-
// 3. 工具栏
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* 解析工具栏按钮配置
|
|
57
|
+
*
|
|
58
|
+
* 优先级规则:
|
|
59
|
+
* 1. 如果传入了完整的 toolbarButtons 数组,直接使用
|
|
60
|
+
* 2. 否则组合默认按钮和额外按钮
|
|
61
|
+
*
|
|
62
|
+
* @returns {Array} 工具栏按钮配置数组
|
|
63
|
+
*/
|
|
78
64
|
function resolveToolbarButtonConfigs() {
|
|
79
|
-
if (props.toolbarButtons?.length)
|
|
65
|
+
if (props.toolbarButtons?.length) {
|
|
66
|
+
return props.toolbarButtons
|
|
67
|
+
}
|
|
80
68
|
const configs = []
|
|
81
69
|
if (props.showDefaultToolbar) {
|
|
82
70
|
configs.push(...createDefaultToolbarButtons({ mapId: props.mapId }))
|
|
@@ -87,134 +75,245 @@ function resolveToolbarButtonConfigs() {
|
|
|
87
75
|
return configs
|
|
88
76
|
}
|
|
89
77
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// ---------------------------------------------------------------------------
|
|
101
|
-
// 4. 场景模式事件(工具栏 2D/3D 切换 → 同步相机交互)
|
|
102
|
-
// ---------------------------------------------------------------------------
|
|
103
|
-
function handleSceneModeChange({ mapId, sceneMode }) {
|
|
78
|
+
/**
|
|
79
|
+
* 处理场景模式变化(2D/3D 切换)
|
|
80
|
+
*
|
|
81
|
+
* 当 mapStore 中的场景模式发生变化时触发,同步更新相机交互模式
|
|
82
|
+
*
|
|
83
|
+
* @param {Object} param - 场景模式变化事件参数
|
|
84
|
+
* @param {string} param.mapId - 地图实例ID
|
|
85
|
+
* @param {string} param.sceneMode - 场景模式('2D' 或 '3D')
|
|
86
|
+
*/
|
|
87
|
+
const handleSceneModeChange = ({ mapId, sceneMode }) => {
|
|
104
88
|
if (mapId !== props.mapId || !map || map.isDestroyed?.()) return
|
|
105
89
|
syncCameraInteractionMode(map, mapId, sceneMode)
|
|
106
90
|
}
|
|
107
91
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
}
|
|
92
|
+
const cesiumContainer = ref(null)
|
|
93
|
+
let map = null
|
|
94
|
+
let customButtons = []
|
|
95
|
+
let isMapActive = true
|
|
144
96
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
97
|
+
/**
|
|
98
|
+
* 挂载工具栏按钮到地图
|
|
99
|
+
*
|
|
100
|
+
* 先移除已存在的按钮,再根据配置添加新按钮
|
|
101
|
+
*
|
|
102
|
+
* @param {Cesium.Viewer} viewer - Cesium 地图实例
|
|
103
|
+
*/
|
|
104
|
+
function mountToolbarButtons(viewer) {
|
|
105
|
+
if (!viewer || viewer.isDestroyed?.()) return
|
|
106
|
+
removeToolbarButtons(customButtons) // 移除已存在的自定义工具栏按钮
|
|
107
|
+
customButtons = []
|
|
108
|
+
const toolbarConfigs = resolveToolbarButtonConfigs() // 解析工具栏按钮配置
|
|
109
|
+
if (toolbarConfigs.length) {
|
|
110
|
+
customButtons = addToolbarButtons(viewer, toolbarConfigs) // 添加自定义工具栏按钮
|
|
111
|
+
}
|
|
149
112
|
}
|
|
150
113
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
114
|
+
/**
|
|
115
|
+
* 初始化 Cesium 地图
|
|
116
|
+
*
|
|
117
|
+
* 完整的地图初始化流程:
|
|
118
|
+
* 1. 设置加载状态
|
|
119
|
+
* 2. 合并配置(MapConfig + DroneConfig + props.options)
|
|
120
|
+
* 3. 创建 Cesium Viewer 实例
|
|
121
|
+
* 4. 初始化地图工具和事件
|
|
122
|
+
* 5. 触发 onload 事件
|
|
123
|
+
*/
|
|
124
|
+
const initCesium = async () => {
|
|
155
125
|
if (!cesiumContainer.value) return
|
|
156
|
-
|
|
157
126
|
mapStore.setMapLoadSta(MapLoadStatus.LOADING, props.mapId)
|
|
127
|
+
|
|
128
|
+
let mapOptions
|
|
129
|
+
if (MapConfig) {
|
|
130
|
+
mapOptions = MapConfig
|
|
131
|
+
}
|
|
132
|
+
|
|
158
133
|
if (DroneConfig) {
|
|
159
134
|
mapStore.setTrailTime(DroneConfig.trailTime, props.mapId)
|
|
160
135
|
}
|
|
161
136
|
|
|
162
|
-
|
|
137
|
+
if (props.options) {
|
|
138
|
+
let exOptions
|
|
139
|
+
if (props.options.then) {
|
|
140
|
+
exOptions = toRaw(await props.options)
|
|
141
|
+
} else {
|
|
142
|
+
exOptions = toRaw(props.options)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (mapOptions) {
|
|
146
|
+
mapOptions = merge(mapOptions, exOptions)
|
|
147
|
+
} else {
|
|
148
|
+
mapOptions = exOptions
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (mapOptions) {
|
|
153
|
+
mapOptions = processMapConfigColors(mapOptions)
|
|
154
|
+
}
|
|
155
|
+
|
|
163
156
|
try {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
157
|
+
// 1、Viewer 创建参数
|
|
158
|
+
const viewerOptions = {
|
|
159
|
+
sceneMode: Cesium.SceneMode.SCENE3D, // 场景模式:固定为 3D(2D/3D 由相机与正交投影切换,不用 Cesium 内置 2D 模式)
|
|
160
|
+
useDefaultRenderLoop: false, // 禁用默认渲染循环,由应用自行 requestAnimationFrame 渲染
|
|
161
|
+
baseLayer: mapOptions.control.imageryProvider === false ? false : undefined, // false 时禁用默认底图,后续由 applyImageryLayers 加载
|
|
162
|
+
baseLayerPicker: mapOptions.control.baseLayerPicker, // 底图图层选择器
|
|
163
|
+
geocoder: mapOptions.control.geocoder, // 地址搜索框
|
|
164
|
+
homeButton: mapOptions.control.homeButton, // 回到初始视角按钮
|
|
165
|
+
sceneModePicker: mapOptions.control.sceneModePicker, // 2D/哥伦布/3D 场景模式切换器
|
|
166
|
+
navigationHelpButton: mapOptions.control.navigationHelpButton, // 导航操作说明按钮
|
|
167
|
+
animation: mapOptions.control.animation, // 动画控件
|
|
168
|
+
timeline: mapOptions.control.timeline, // 时间轴
|
|
169
|
+
infoBox: mapOptions.control.infoBox, // 实体信息弹窗
|
|
170
|
+
fullscreenButton: mapOptions.control.fullscreenButton, // 全屏按钮
|
|
171
|
+
vrButton: mapOptions.control.vrButton, // VR 模式按钮
|
|
172
|
+
terrainProvider: mapOptions.terrain.show // 地形服务;不开启时为 undefined
|
|
173
|
+
? await Cesium.CesiumTerrainProvider.fromUrl(mapOptions.terrain.url, {
|
|
174
|
+
requestWaterMask: mapOptions.terrain.coastlineData, // 水体效果所需海岸线数据
|
|
175
|
+
requestVertexNormals: mapOptions.terrain.lightingData, // 地形光照法线数据
|
|
176
|
+
})
|
|
177
|
+
: undefined,
|
|
178
|
+
terrainShadows: Cesium.ShadowMode.RECEIVE_ONLY, // 地形只接收阴影,不投射,节省性能
|
|
179
|
+
creditContainer: document.createElement('div'), // 用空容器替换默认版权水印区域
|
|
172
180
|
}
|
|
173
181
|
|
|
174
|
-
//
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
// 2、WebGL 上下文与 GPU 偏好(传入 Viewer 的 contextOptions)
|
|
183
|
+
const contextOptions = {
|
|
184
|
+
webgl: {
|
|
185
|
+
alpha: false, // 禁用 canvas 透明通道,减少合成开销
|
|
186
|
+
depth: true, // 启用深度缓冲,保证远近正确遮挡
|
|
187
|
+
stencil: false, // 关闭模板缓冲,一般地图场景用不到
|
|
188
|
+
antialias: true, // 开启 MSAA 抗锯齿
|
|
189
|
+
premultipliedAlpha: true, // 预乘 alpha,与 Cesium 默认混合方式一致
|
|
190
|
+
preserveDrawingBuffer: false, // 不保留上一帧缓冲(截图需 true)
|
|
191
|
+
failIfMajorPerformanceCaveat: false, // 低性能 GPU 仍尝试创建上下文,避免直接失败
|
|
192
|
+
},
|
|
193
|
+
requestWebGl2: true, // 优先使用 WebGL 2.0
|
|
194
|
+
powerPreference: 'high-performance', // 提示浏览器选用独显/高性能 GPU
|
|
195
|
+
}
|
|
178
196
|
|
|
179
|
-
//
|
|
180
|
-
const sceneRenderOptions =
|
|
181
|
-
|
|
197
|
+
// 3、场景渲染项(Viewer 创建后再写到 scene / globe,对应 mapOptions.scene)
|
|
198
|
+
const sceneRenderOptions = {
|
|
199
|
+
debugShowFramesPerSecond: mapOptions.scene.debugShowFramesPerSecond, // 左上角 FPS 调试
|
|
200
|
+
enableLighting: mapOptions.scene.globe.enableLighting, // 地球随太阳的光照变化
|
|
201
|
+
depthTestAgainstTerrain: mapOptions.scene.globe.depthTestAgainstTerrain, // 实体与地形做深度测试
|
|
202
|
+
fogEnabled: mapOptions.scene.fog.enabled, // 远景雾效
|
|
203
|
+
skyAtmosphereShow: mapOptions.scene.skyAtmosphere.show, // 大气层外圈光晕
|
|
204
|
+
dynamicAtmosphereLighting: mapOptions.scene.globe.dynamicAtmosphereLighting, // 大气动态光照
|
|
205
|
+
dynamicAtmosphereLightingFromSun: mapOptions.scene.globe.dynamicAtmosphereLightingFromSun, // 光照以太阳为参考
|
|
206
|
+
}
|
|
182
207
|
|
|
183
|
-
|
|
184
|
-
|
|
208
|
+
map = new Cesium.Viewer(cesiumContainer.value, {
|
|
209
|
+
...viewerOptions, // 控件、地形、底图等 Viewer 级配置
|
|
210
|
+
contextOptions, // WebGL 创建参数
|
|
211
|
+
})
|
|
185
212
|
|
|
186
|
-
// 4
|
|
187
|
-
|
|
188
|
-
|
|
213
|
+
// 4、将 sceneRenderOptions 应用到已创建的 Scene / Globe
|
|
214
|
+
map.scene.debugShowFramesPerSecond = sceneRenderOptions.debugShowFramesPerSecond
|
|
215
|
+
map.scene.globe.enableLighting = sceneRenderOptions.enableLighting
|
|
216
|
+
map.scene.globe.depthTestAgainstTerrain = sceneRenderOptions.depthTestAgainstTerrain
|
|
217
|
+
map.scene.fog.enabled = sceneRenderOptions.fogEnabled
|
|
218
|
+
map.scene.skyAtmosphere.show = sceneRenderOptions.skyAtmosphereShow
|
|
219
|
+
map.scene.globe.dynamicAtmosphereLighting = sceneRenderOptions.dynamicAtmosphereLighting
|
|
220
|
+
map.scene.globe.dynamicAtmosphereLightingFromSun = sceneRenderOptions.dynamicAtmosphereLightingFromSun
|
|
221
|
+
|
|
222
|
+
const activeBasemap = resolveOnlineBasemap(mapOptions) // 解析在线底图
|
|
223
|
+
if (activeBasemap) {
|
|
224
|
+
applyImageryLayers(map, mapOptions, activeBasemap) // 应用在线底图
|
|
225
|
+
console.log(`加载在线底图:${activeBasemap.name},URL:${activeBasemap.url}`)
|
|
226
|
+
window._currentBasemapId = activeBasemap.id
|
|
227
|
+
} else { // 未找到可用底图,Cesium 将使用默认底图或空底图显示
|
|
228
|
+
console.warn('未找到可用底图,Cesium 将使用默认底图或空底图显示')
|
|
229
|
+
}
|
|
189
230
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
231
|
+
if (map.screenSpaceEventHandler && mapOptions.control.disableDoubleClick) {
|
|
232
|
+
// 移除双击左键事件,防止地图缩放
|
|
233
|
+
map.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (mapOptions.control.disableEntityClick) {
|
|
237
|
+
map.selectedEntityChanged.addEventListener(() => {
|
|
238
|
+
map.selectedEntity = undefined
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
mouseController(map, props.mapId) // 初始化鼠标控制器
|
|
243
|
+
|
|
244
|
+
mapStore.setMapInfo(
|
|
245
|
+
'center',
|
|
246
|
+
{
|
|
247
|
+
lng: mapOptions.scene.center.lng,
|
|
248
|
+
lat: mapOptions.scene.center.lat,
|
|
249
|
+
heading: mapOptions.scene.center.heading,
|
|
250
|
+
pitch: mapOptions.scene.center.pitch,
|
|
251
|
+
roll: mapOptions.scene.center.roll,
|
|
252
|
+
duration: mapOptions.scene.center.duration,
|
|
253
|
+
alt: mapOptions.scene.center.alt,
|
|
254
|
+
},
|
|
255
|
+
props.mapId,
|
|
256
|
+
)
|
|
257
|
+
mapStore.setMap(map, props.mapId)
|
|
258
|
+
const sceneMode = mapOptions.scene.sceneMode || '3D'
|
|
259
|
+
mapStore.setSceneMode(sceneMode, props.mapId)
|
|
260
|
+
|
|
261
|
+
const center = mapOptions.scene.center || {}
|
|
262
|
+
|
|
263
|
+
mountToolbarButtons(map) // 挂载工具栏按钮到地图
|
|
193
264
|
mapStore.setMapLoadSta(MapLoadStatus.LOADED, props.mapId)
|
|
194
265
|
|
|
195
|
-
// 6. 通知宿主(必须在相机 finalize 之前,与历史行为一致)
|
|
196
|
-
// 宿主在 mapOnLoad 内同步添加半球、航迹、无人机等 Entity/Primitive;
|
|
197
|
-
// 若先 setInitialCameraView 再 onload,首帧已绘制且部分场景下实体不刷新。
|
|
198
|
-
emitMapReady(map, center)
|
|
199
266
|
console.log(`Cesium 地图加载成功,mapId: ${props.mapId}`)
|
|
267
|
+
emit('onload', {
|
|
268
|
+
map,
|
|
269
|
+
center,
|
|
270
|
+
mapId: props.mapId,
|
|
271
|
+
toolbar: {
|
|
272
|
+
addToolbarButton: (opt) => addToolbarButton(map, opt), // 添加单个工具栏按钮
|
|
273
|
+
addToolbarButtons: (opts) => addToolbarButtons(map, opts), // 添加多个工具栏按钮
|
|
274
|
+
removeToolbarButtons, // 移除工具栏按钮
|
|
275
|
+
},
|
|
276
|
+
syncCameraInteractionMode: (mode) => syncCameraInteractionMode(map, props.mapId, mode), // 同步相机交互模式
|
|
277
|
+
})
|
|
200
278
|
|
|
201
|
-
|
|
202
|
-
|
|
279
|
+
map.resize() // 调整地图尺寸
|
|
280
|
+
setInitialCameraView(map, mapOptions.scene.center, sceneMode, props.mapId) // 初始化相机视角
|
|
281
|
+
map.render() // 渲染地图
|
|
203
282
|
|
|
204
|
-
//
|
|
283
|
+
// 如果场景模式为 2D,则同步相机交互模式 (因为初始化相机视角时,已经同步了相机交互模式,保险起见再同步一次)
|
|
284
|
+
if (sceneMode === '2D') {
|
|
285
|
+
nextTick(() => {
|
|
286
|
+
if (!map || map.isDestroyed?.()) return
|
|
287
|
+
syncCameraInteractionMode(map, props.mapId, '2D') // 同步相机交互模式
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// 启动持续渲染循环
|
|
205
292
|
startContinuousRendering(map)
|
|
206
293
|
} catch (error) {
|
|
207
|
-
mapStore.setMapLoadSta(MapLoadStatus.FAILED, props.mapId)
|
|
208
|
-
console.error('Cesium 地图加载失败:', error)
|
|
209
|
-
}
|
|
294
|
+
mapStore.setMapLoadSta(MapLoadStatus.FAILED, props.mapId) // 设置地图加载状态为失败
|
|
295
|
+
console.error('Cesium 地图加载失败:', error)
|
|
210
296
|
}
|
|
211
297
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
298
|
+
let animationId = null
|
|
299
|
+
let lastActiveTime = Date.now()
|
|
300
|
+
const IDLE_TIMEOUT = 30000
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* 启动持续渲染循环
|
|
304
|
+
*
|
|
305
|
+
* 使用 requestAnimationFrame 维持地图持续渲染,确保动画效果流畅
|
|
306
|
+
* 渲染条件:
|
|
307
|
+
* 1. 地图处于活跃状态(isMapActive = true)
|
|
308
|
+
* 2. 页面未隐藏(document.hidden = false)
|
|
309
|
+
* 3. 地图实例存在且未销毁
|
|
310
|
+
* 4. 最近有用户活动(lastActiveTime 小于空闲超时时间)
|
|
311
|
+
*
|
|
312
|
+
* @param {Cesium.Viewer} viewer - Cesium 地图实例
|
|
313
|
+
*/
|
|
215
314
|
function startContinuousRendering(viewer) {
|
|
216
315
|
if (animationId) return
|
|
217
|
-
|
|
316
|
+
function animate() {
|
|
218
317
|
const now = Date.now()
|
|
219
318
|
if (
|
|
220
319
|
isMapActive &&
|
|
@@ -225,11 +324,17 @@ function startContinuousRendering(viewer) {
|
|
|
225
324
|
) {
|
|
226
325
|
viewer.render()
|
|
227
326
|
}
|
|
228
|
-
animationId = requestAnimationFrame(
|
|
327
|
+
animationId = requestAnimationFrame(animate)
|
|
229
328
|
}
|
|
230
|
-
|
|
329
|
+
animate()
|
|
231
330
|
}
|
|
232
331
|
|
|
332
|
+
/**
|
|
333
|
+
* 停止持续渲染循环
|
|
334
|
+
*
|
|
335
|
+
* 取消 requestAnimationFrame,释放资源
|
|
336
|
+
* 用于 keep-alive 缓存、组件卸载或手动暂停渲染场景
|
|
337
|
+
*/
|
|
233
338
|
function stopRenderLoop() {
|
|
234
339
|
if (animationId) {
|
|
235
340
|
cancelAnimationFrame(animationId)
|
|
@@ -237,23 +342,49 @@ function stopRenderLoop() {
|
|
|
237
342
|
}
|
|
238
343
|
}
|
|
239
344
|
|
|
345
|
+
/**
|
|
346
|
+
* 设置用户活动监听器
|
|
347
|
+
*
|
|
348
|
+
* 监听用户的各种交互行为,更新最后活动时间戳
|
|
349
|
+
* 用于配合持续渲染循环判断是否需要继续渲染地图
|
|
350
|
+
*
|
|
351
|
+
* 监听的事件类型:
|
|
352
|
+
* - mousemove:鼠标移动
|
|
353
|
+
* - keydown:键盘按键
|
|
354
|
+
* - touchmove:触摸移动
|
|
355
|
+
* - wheel:滚轮滚动
|
|
356
|
+
* - click:点击
|
|
357
|
+
*/
|
|
240
358
|
function setupActivityListeners() {
|
|
241
|
-
|
|
359
|
+
const activities = ['mousemove', 'keydown', 'touchmove', 'wheel', 'click']
|
|
360
|
+
activities.forEach((event) => {
|
|
242
361
|
document.addEventListener(event, () => {
|
|
243
362
|
lastActiveTime = Date.now()
|
|
244
363
|
})
|
|
245
364
|
})
|
|
246
365
|
}
|
|
247
366
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
367
|
+
/**
|
|
368
|
+
* 从 keep-alive 缓存中恢复地图渲染
|
|
369
|
+
*
|
|
370
|
+
* 当使用 keep-alive 包裹地图组件时,组件被导航离开后会被缓存
|
|
371
|
+
* 当再次导航回来时,需要恢复地图的活跃状态和渲染循环
|
|
372
|
+
*
|
|
373
|
+
* 恢复步骤:
|
|
374
|
+
* 1. 设置当前地图ID到store
|
|
375
|
+
* 2. 标记地图为活跃状态
|
|
376
|
+
* 3. 更新最后活动时间戳
|
|
377
|
+
* 4. 在 nextTick 中执行:
|
|
378
|
+
* - 调整地图尺寸(确保容器大小正确)
|
|
379
|
+
* - 同步相机交互模式(2D/3D)
|
|
380
|
+
* - 执行一次渲染
|
|
381
|
+
* - 启动持续渲染循环(如果尚未启动)
|
|
382
|
+
*/
|
|
251
383
|
function resumeMapAfterCache() {
|
|
252
384
|
if (!map || map.isDestroyed?.()) return
|
|
253
385
|
mapStore.setCurrentMapId(props.mapId)
|
|
254
386
|
isMapActive = true
|
|
255
387
|
lastActiveTime = Date.now()
|
|
256
|
-
|
|
257
388
|
nextTick(() => {
|
|
258
389
|
if (!map || map.isDestroyed?.()) return
|
|
259
390
|
map.resize()
|
|
@@ -264,51 +395,57 @@ function resumeMapAfterCache() {
|
|
|
264
395
|
})
|
|
265
396
|
}
|
|
266
397
|
|
|
267
|
-
//
|
|
268
|
-
// 8. 生命周期
|
|
269
|
-
// ---------------------------------------------------------------------------
|
|
398
|
+
// 宿主传入的 toolbarButtons 变化时,重新挂载工具栏(先移除旧按钮再按新配置添加)
|
|
270
399
|
watch(
|
|
271
400
|
() => props.toolbarButtons,
|
|
272
401
|
() => {
|
|
273
|
-
if (map && !map.isDestroyed?.())
|
|
402
|
+
if (map && !map.isDestroyed?.()) { // 如果地图实例存在且未销毁,则重新挂载工具栏
|
|
403
|
+
mountToolbarButtons(map) // 重新挂载工具栏
|
|
404
|
+
}
|
|
274
405
|
},
|
|
275
|
-
{ deep: true },
|
|
406
|
+
{ deep: true }, // 深度监听:数组项或按钮对象字段变更也会触发
|
|
276
407
|
)
|
|
277
408
|
|
|
278
409
|
onMounted(() => {
|
|
279
|
-
mapStore.setCurrentMapId(props.mapId)
|
|
280
|
-
setupActivityListeners()
|
|
281
|
-
onEvent('map-scene-mode-changed', handleSceneModeChange)
|
|
282
|
-
initCesium()
|
|
410
|
+
mapStore.setCurrentMapId(props.mapId) // 初始化当前地图ID
|
|
411
|
+
setupActivityListeners() // 初始化用户活动监听器,用于判断是否需要继续渲染地图,避免空闲超时导致的渲染暂停
|
|
412
|
+
onEvent('map-scene-mode-changed', handleSceneModeChange) // 监听场景模式变化事件,用于同步相机交互模式
|
|
413
|
+
initCesium() // 初始化 Cesium 地图实例
|
|
283
414
|
})
|
|
284
415
|
|
|
416
|
+
// 组件激活时,恢复地图渲染
|
|
285
417
|
onActivated(() => {
|
|
286
|
-
mapStore.setCurrentMapId(props.mapId)
|
|
287
|
-
if (!map || map.isDestroyed?.()) {
|
|
288
|
-
isMapActive = true
|
|
289
|
-
initCesium()
|
|
418
|
+
mapStore.setCurrentMapId(props.mapId) // 设置当前地图ID
|
|
419
|
+
if (!map || map.isDestroyed?.()) { // 如果地图实例不存在或已销毁,则重新初始化地图
|
|
420
|
+
isMapActive = true // 标记地图为活跃状态
|
|
421
|
+
initCesium() // 重新初始化地图
|
|
290
422
|
return
|
|
291
423
|
}
|
|
292
|
-
resumeMapAfterCache()
|
|
424
|
+
resumeMapAfterCache() // 从 keep-alive 缓存中恢复地图渲染 (恢复地图的活跃状态和渲染循环)
|
|
293
425
|
})
|
|
294
426
|
|
|
427
|
+
// 组件失活时,停止地图渲染
|
|
295
428
|
onDeactivated(() => {
|
|
296
|
-
isMapActive = false
|
|
297
|
-
stopRenderLoop()
|
|
429
|
+
isMapActive = false // 标记地图为非活跃状态
|
|
430
|
+
stopRenderLoop() // 停止持续渲染循环,释放资源
|
|
298
431
|
})
|
|
299
432
|
|
|
433
|
+
// 组件卸载时,销毁地图实例
|
|
300
434
|
onUnmounted(() => {
|
|
301
435
|
isMapActive = false
|
|
302
|
-
stopRenderLoop()
|
|
303
|
-
offEvent('map-scene-mode-changed', handleSceneModeChange)
|
|
304
|
-
if (map)
|
|
305
|
-
|
|
436
|
+
stopRenderLoop() // 停止持续渲染循环,释放资源
|
|
437
|
+
offEvent('map-scene-mode-changed', handleSceneModeChange) // 移除场景模式变化事件监听器
|
|
438
|
+
if (map) {
|
|
439
|
+
teardownOrthographicWheelZoom(map) // 移除正交投影滚轮缩放事件监听器
|
|
440
|
+
}
|
|
441
|
+
removeToolbarButtons(customButtons) // 移除自定义工具栏按钮
|
|
306
442
|
customButtons = []
|
|
307
443
|
if (map) {
|
|
308
444
|
map.destroy()
|
|
309
445
|
map = null
|
|
310
446
|
}
|
|
311
|
-
mapStore.removeMapInstance(props.mapId)
|
|
447
|
+
mapStore.removeMapInstance(props.mapId) // 从 store 中移除地图实例
|
|
448
|
+
mapStore.setCurrentMapId('') // 重置当前地图ID
|
|
312
449
|
})
|
|
313
450
|
</script>
|
|
314
451
|
|
package/js/index.js
CHANGED
|
@@ -36,16 +36,6 @@ 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'
|
|
49
39
|
|
|
50
40
|
export {
|
|
51
41
|
BaseConfig,
|
package/package.json
CHANGED
|
@@ -1,164 +0,0 @@
|
|
|
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
|
-
}
|