huweili-cesium 1.0.13 → 1.0.15
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/dronePlannedRoute.js +242 -0
- package/movePointPlannedRoute.js +31 -0
- package/package.json +109 -101
- package/utils/basemapSwitcher.js +252 -0
- package/utils/mapImagery.js +135 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 无人机规划航迹模块
|
|
3
|
+
*
|
|
4
|
+
* 根据前端传入的经纬度+高度数组,在空中绘制航迹连线及航点标注。
|
|
5
|
+
* 无人机实际位置仍由 WebSocket 数据通过 moveDronePoint 驱动。
|
|
6
|
+
*/
|
|
7
|
+
import * as Cesium from 'cesium'
|
|
8
|
+
import { useMapStore } from './stores/mapStore.js'
|
|
9
|
+
|
|
10
|
+
const PLANNED_ROUTE_STORE_SUFFIX = '_planned_route'
|
|
11
|
+
const DEFAULT_LINE_COLOR = 'rgba(0, 191, 255, 0.85)'
|
|
12
|
+
const DEFAULT_LINE_WIDTH = 3
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 将前端航点数组规范为 { lng, lat, height }
|
|
16
|
+
* 支持字段:lng/lon/longitude, lat/latitude, height/alt/altitude
|
|
17
|
+
*/
|
|
18
|
+
function normalizeWaypoints(waypoints) {
|
|
19
|
+
if (!Array.isArray(waypoints)) return []
|
|
20
|
+
|
|
21
|
+
return waypoints
|
|
22
|
+
.map((wp, index) => {
|
|
23
|
+
const lng = Number(wp?.lng ?? wp?.lon ?? wp?.longitude ?? wp?.LONGITUDE)
|
|
24
|
+
const lat = Number(wp?.lat ?? wp?.latitude ?? wp?.LATITUDE)
|
|
25
|
+
const height = Number(wp?.height ?? wp?.alt ?? wp?.altitude ?? wp?.HEIGHT ?? 0)
|
|
26
|
+
if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null
|
|
27
|
+
return {
|
|
28
|
+
lng,
|
|
29
|
+
lat,
|
|
30
|
+
height: Number.isFinite(height) ? height : 0,
|
|
31
|
+
index,
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function getRouteStoreKey(routeId) {
|
|
38
|
+
return `${routeId}${PLANNED_ROUTE_STORE_SUFFIX}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function removeRouteEntities(map, routeData) {
|
|
42
|
+
if (!map || !routeData) return
|
|
43
|
+
|
|
44
|
+
routeData.entities?.forEach((entity) => {
|
|
45
|
+
if (entity) {
|
|
46
|
+
map.entities.remove(entity)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
routeData.entities = []
|
|
50
|
+
routeData.polylineEntity = null
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function dronePlannedRouteConfig() {
|
|
54
|
+
const mapStore = useMapStore()
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 绘制无人机规划航迹(空中折线 + 航点标注)
|
|
58
|
+
* @param {Object} options
|
|
59
|
+
* @param {string} options.routeId 航迹唯一标识,通常与无人机 pointId/uavId 一致
|
|
60
|
+
* @param {string} [options.pointId] 同 routeId,二选一
|
|
61
|
+
* @param {string} options.mapId 地图实例 ID
|
|
62
|
+
* @param {Array<{lng, lat, height}|{longitude, latitude, height}>} options.waypoints 航点数组
|
|
63
|
+
* @param {string} [options.lineColor] 航迹线颜色,默认青色
|
|
64
|
+
* @param {number} [options.lineWidth] 航迹线宽度
|
|
65
|
+
* @param {boolean} [options.showWaypointLabels=true] 是否显示「航点1、航点2…」标签
|
|
66
|
+
* @param {number} [options.waypointPixelSize=10] 航点圆点大小
|
|
67
|
+
* @param {boolean} [options.flyToRoute=false] 绘制后是否飞到航迹范围
|
|
68
|
+
* @returns {Object|null} 航迹数据对象
|
|
69
|
+
*/
|
|
70
|
+
const setDronePlannedRoute = (options = {}) => {
|
|
71
|
+
const routeId = options.routeId || options.pointId
|
|
72
|
+
const { mapId, flyToRoute = false } = options
|
|
73
|
+
|
|
74
|
+
if (!routeId) {
|
|
75
|
+
console.warn('setDronePlannedRoute: 缺少 routeId / pointId')
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const normalized = normalizeWaypoints(options.waypoints)
|
|
80
|
+
if (normalized.length < 1) {
|
|
81
|
+
console.warn('setDronePlannedRoute: 航点数组为空或无效')
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const map = mapStore.getMap(mapId)
|
|
86
|
+
if (!map) {
|
|
87
|
+
console.error('地图实例不存在')
|
|
88
|
+
return null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 已存在则先清除再重建,保证航迹与航点一致
|
|
92
|
+
clearDronePlannedRoute({ routeId, mapId })
|
|
93
|
+
|
|
94
|
+
const lineColor = options.lineColor || DEFAULT_LINE_COLOR
|
|
95
|
+
const lineWidth = Number(options.lineWidth) || DEFAULT_LINE_WIDTH
|
|
96
|
+
const showWaypointLabels = options.showWaypointLabels !== false
|
|
97
|
+
const waypointPixelSize = Number(options.waypointPixelSize) || 10
|
|
98
|
+
const color = Cesium.Color.fromCssColorString(lineColor)
|
|
99
|
+
|
|
100
|
+
const positions = normalized.map((wp) =>
|
|
101
|
+
Cesium.Cartesian3.fromDegrees(wp.lng, wp.lat, wp.height)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const routeData = {
|
|
105
|
+
routeId,
|
|
106
|
+
mapId,
|
|
107
|
+
waypoints: normalized,
|
|
108
|
+
entities: [],
|
|
109
|
+
polylineEntity: null,
|
|
110
|
+
isVisible: true,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (normalized.length >= 2) {
|
|
114
|
+
routeData.polylineEntity = map.entities.add({
|
|
115
|
+
id: `${routeId}_planned_route_line`,
|
|
116
|
+
name: `规划航迹:${routeId}`,
|
|
117
|
+
polyline: {
|
|
118
|
+
positions,
|
|
119
|
+
width: lineWidth,
|
|
120
|
+
material: color,
|
|
121
|
+
arcType: Cesium.ArcType.NONE,
|
|
122
|
+
clampToGround: false,
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
routeData.entities.push(routeData.polylineEntity)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
normalized.forEach((wp, i) => {
|
|
129
|
+
const position = Cesium.Cartesian3.fromDegrees(wp.lng, wp.lat, wp.height)
|
|
130
|
+
const waypointEntity = map.entities.add({
|
|
131
|
+
id: `${routeId}_planned_route_wp_${i}`,
|
|
132
|
+
name: `规划航点${i + 1}:${routeId}`,
|
|
133
|
+
position,
|
|
134
|
+
point: {
|
|
135
|
+
pixelSize: waypointPixelSize,
|
|
136
|
+
color,
|
|
137
|
+
outlineColor: Cesium.Color.WHITE,
|
|
138
|
+
outlineWidth: 2,
|
|
139
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
140
|
+
},
|
|
141
|
+
label: showWaypointLabels
|
|
142
|
+
? {
|
|
143
|
+
text: `航点${i + 1}`,
|
|
144
|
+
font: '14px Microsoft YaHei, sans-serif',
|
|
145
|
+
fillColor: Cesium.Color.WHITE,
|
|
146
|
+
outlineColor: Cesium.Color.BLACK,
|
|
147
|
+
outlineWidth: 2,
|
|
148
|
+
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|
149
|
+
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
|
|
150
|
+
pixelOffset: new Cesium.Cartesian2(0, -12),
|
|
151
|
+
disableDepthTestDistance: Number.POSITIVE_INFINITY,
|
|
152
|
+
}
|
|
153
|
+
: undefined,
|
|
154
|
+
})
|
|
155
|
+
routeData.entities.push(waypointEntity)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
routeData.destroy = () => {
|
|
159
|
+
removeRouteEntities(map, routeData)
|
|
160
|
+
mapStore.removeGraphicMap(getRouteStoreKey(routeId), mapId)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
mapStore.setGraphicMap(getRouteStoreKey(routeId), routeData, mapId)
|
|
164
|
+
|
|
165
|
+
if (flyToRoute && positions.length > 0) {
|
|
166
|
+
const boundingSphere = Cesium.BoundingSphere.fromPoints(positions)
|
|
167
|
+
map.camera.flyToBoundingSphere(boundingSphere, { duration: 1.2 })
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return routeData
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 更新规划航迹(等价于清除后重新绘制)
|
|
175
|
+
*/
|
|
176
|
+
const updateDronePlannedRoute = (options) => setDronePlannedRoute(options)
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 清除规划航迹及航点标注
|
|
180
|
+
* @param {Object} options
|
|
181
|
+
* @param {string} options.routeId 航迹 ID(或 pointId)
|
|
182
|
+
* @param {string} options.mapId 地图实例 ID
|
|
183
|
+
*/
|
|
184
|
+
const clearDronePlannedRoute = (options = {}) => {
|
|
185
|
+
const routeId = options.routeId || options.pointId
|
|
186
|
+
const { mapId } = options
|
|
187
|
+
|
|
188
|
+
if (!routeId) {
|
|
189
|
+
console.warn('clearDronePlannedRoute: 缺少 routeId / pointId')
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const storeKey = getRouteStoreKey(routeId)
|
|
194
|
+
const routeData = mapStore.getGraphicMap(storeKey, mapId)
|
|
195
|
+
if (!routeData) return false
|
|
196
|
+
|
|
197
|
+
const map = mapStore.getMap(mapId)
|
|
198
|
+
removeRouteEntities(map, routeData)
|
|
199
|
+
mapStore.removeGraphicMap(storeKey, mapId)
|
|
200
|
+
return true
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* 显示/隐藏规划航迹
|
|
205
|
+
*/
|
|
206
|
+
const toggleDronePlannedRouteVisibility = (options = {}) => {
|
|
207
|
+
const routeId = options.routeId || options.pointId
|
|
208
|
+
const { mapId, visible } = options
|
|
209
|
+
|
|
210
|
+
if (!routeId || visible === undefined) return false
|
|
211
|
+
|
|
212
|
+
const routeData = mapStore.getGraphicMap(getRouteStoreKey(routeId), mapId)
|
|
213
|
+
if (!routeData) {
|
|
214
|
+
console.warn(`规划航迹不存在,ID: ${routeId}`)
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
routeData.isVisible = visible
|
|
219
|
+
routeData.entities?.forEach((entity) => {
|
|
220
|
+
if (entity) entity.show = visible
|
|
221
|
+
})
|
|
222
|
+
return true
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 获取已绘制的规划航迹数据
|
|
227
|
+
*/
|
|
228
|
+
const getDronePlannedRoute = (options = {}) => {
|
|
229
|
+
const routeId = options.routeId || options.pointId
|
|
230
|
+
const { mapId } = options
|
|
231
|
+
if (!routeId) return null
|
|
232
|
+
return mapStore.getGraphicMap(getRouteStoreKey(routeId), mapId) || null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
setDronePlannedRoute,
|
|
237
|
+
updateDronePlannedRoute,
|
|
238
|
+
clearDronePlannedRoute,
|
|
239
|
+
toggleDronePlannedRouteVisibility,
|
|
240
|
+
getDronePlannedRoute,
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* movePoint 扩展模块(不修改原 movePoint.js)
|
|
3
|
+
*
|
|
4
|
+
* 在原有 movePointConfig 能力基础上,组合规划航迹模块,
|
|
5
|
+
* 并提供同时销毁无人机与规划航迹的方法。
|
|
6
|
+
*/
|
|
7
|
+
import { movePointConfig } from './movePoint.js'
|
|
8
|
+
import { dronePlannedRouteConfig } from './dronePlannedRoute.js'
|
|
9
|
+
|
|
10
|
+
export function movePointPlannedRouteConfig(baseUrl) {
|
|
11
|
+
const movePoint = movePointConfig(baseUrl)
|
|
12
|
+
const plannedRoute = dronePlannedRouteConfig()
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 销毁无人机模型、实时轨迹,并清除对应规划航迹
|
|
16
|
+
*/
|
|
17
|
+
const destroyDroneTrailAndPlannedRoute = (options) => {
|
|
18
|
+
const result = movePoint.destroyDroneTrail(options)
|
|
19
|
+
plannedRoute.clearDronePlannedRoute({
|
|
20
|
+
routeId: options?.pointId,
|
|
21
|
+
mapId: options?.mapId,
|
|
22
|
+
})
|
|
23
|
+
return result
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...movePoint,
|
|
28
|
+
...plannedRoute,
|
|
29
|
+
destroyDroneTrailAndPlannedRoute,
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
CHANGED
|
@@ -1,101 +1,109 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "huweili-cesium",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./index.js",
|
|
7
|
-
"module": "./index.js",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": "./index.js",
|
|
10
|
-
"./basis": "./basis.js",
|
|
11
|
-
"./basis.js": "./basis.js",
|
|
12
|
-
"./captureFenceScreenshot": "./captureFenceScreenshot.js",
|
|
13
|
-
"./captureFenceScreenshot.js": "./captureFenceScreenshot.js",
|
|
14
|
-
"./cardPool": "./cardPool.js",
|
|
15
|
-
"./cardPool.js": "./cardPool.js",
|
|
16
|
-
"./clickHandler": "./clickHandler.js",
|
|
17
|
-
"./clickHandler.js": "./clickHandler.js",
|
|
18
|
-
"./customToolbarButtons": "./customToolbarButtons.js",
|
|
19
|
-
"./customToolbarButtons.js": "./customToolbarButtons.js",
|
|
20
|
-
"./drawFence": "./drawFence.js",
|
|
21
|
-
"./drawFence.js": "./drawFence.js",
|
|
22
|
-
"./drawFenceNew": "./drawFenceNew.js",
|
|
23
|
-
"./drawFenceNew.js": "./drawFenceNew.js",
|
|
24
|
-
"./
|
|
25
|
-
"./
|
|
26
|
-
"./
|
|
27
|
-
"./
|
|
28
|
-
"./
|
|
29
|
-
"./
|
|
30
|
-
"./
|
|
31
|
-
"./
|
|
32
|
-
"./
|
|
33
|
-
"./
|
|
34
|
-
"./
|
|
35
|
-
"./
|
|
36
|
-
"./
|
|
37
|
-
"./
|
|
38
|
-
"./
|
|
39
|
-
"./
|
|
40
|
-
"./
|
|
41
|
-
"./
|
|
42
|
-
"./
|
|
43
|
-
"./
|
|
44
|
-
"./
|
|
45
|
-
"./
|
|
46
|
-
"./
|
|
47
|
-
"./
|
|
48
|
-
"./
|
|
49
|
-
"./
|
|
50
|
-
"./toolbar/
|
|
51
|
-
"./toolbar/
|
|
52
|
-
"./toolbar/
|
|
53
|
-
"./toolbar/
|
|
54
|
-
"./
|
|
55
|
-
"./
|
|
56
|
-
"./
|
|
57
|
-
"./
|
|
58
|
-
"./
|
|
59
|
-
"./
|
|
60
|
-
"./
|
|
61
|
-
"./
|
|
62
|
-
"./
|
|
63
|
-
"./
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"config
|
|
71
|
-
"api
|
|
72
|
-
|
|
73
|
-
"
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "huweili-cesium",
|
|
3
|
+
"version": "1.0.15",
|
|
4
|
+
"description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"module": "./index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./basis": "./basis.js",
|
|
11
|
+
"./basis.js": "./basis.js",
|
|
12
|
+
"./captureFenceScreenshot": "./captureFenceScreenshot.js",
|
|
13
|
+
"./captureFenceScreenshot.js": "./captureFenceScreenshot.js",
|
|
14
|
+
"./cardPool": "./cardPool.js",
|
|
15
|
+
"./cardPool.js": "./cardPool.js",
|
|
16
|
+
"./clickHandler": "./clickHandler.js",
|
|
17
|
+
"./clickHandler.js": "./clickHandler.js",
|
|
18
|
+
"./customToolbarButtons": "./customToolbarButtons.js",
|
|
19
|
+
"./customToolbarButtons.js": "./customToolbarButtons.js",
|
|
20
|
+
"./drawFence": "./drawFence.js",
|
|
21
|
+
"./drawFence.js": "./drawFence.js",
|
|
22
|
+
"./drawFenceNew": "./drawFenceNew.js",
|
|
23
|
+
"./drawFenceNew.js": "./drawFenceNew.js",
|
|
24
|
+
"./dronePlannedRoute": "./dronePlannedRoute.js",
|
|
25
|
+
"./dronePlannedRoute.js": "./dronePlannedRoute.js",
|
|
26
|
+
"./droneRipple": "./droneRipple.js",
|
|
27
|
+
"./droneRipple.js": "./droneRipple.js",
|
|
28
|
+
"./geometry": "./geometry.js",
|
|
29
|
+
"./geometry.js": "./geometry.js",
|
|
30
|
+
"./groundLink": "./groundLink.js",
|
|
31
|
+
"./groundLink.js": "./groundLink.js",
|
|
32
|
+
"./hemisphere": "./hemisphere.js",
|
|
33
|
+
"./hemisphere.js": "./hemisphere.js",
|
|
34
|
+
"./labelDiv": "./labelDiv.js",
|
|
35
|
+
"./labelDiv.js": "./labelDiv.js",
|
|
36
|
+
"./movePath": "./movePath.js",
|
|
37
|
+
"./movePath.js": "./movePath.js",
|
|
38
|
+
"./movePoint": "./movePoint.js",
|
|
39
|
+
"./movePoint.js": "./movePoint.js",
|
|
40
|
+
"./movePointPlannedRoute": "./movePointPlannedRoute.js",
|
|
41
|
+
"./movePointPlannedRoute.js": "./movePointPlannedRoute.js",
|
|
42
|
+
"./qrCodeGenerator": "./qrCodeGenerator.js",
|
|
43
|
+
"./qrCodeGenerator.js": "./qrCodeGenerator.js",
|
|
44
|
+
"./setPath": "./setPath.js",
|
|
45
|
+
"./setPath.js": "./setPath.js",
|
|
46
|
+
"./setPoint": "./setPoint.js",
|
|
47
|
+
"./setPoint.js": "./setPoint.js",
|
|
48
|
+
"./tileProviders": "./tileProviders.js",
|
|
49
|
+
"./tileProviders.js": "./tileProviders.js",
|
|
50
|
+
"./toolbar/basemapSwitcher": "./toolbar/basemapSwitcher.js",
|
|
51
|
+
"./toolbar/basemapSwitcher.js": "./toolbar/basemapSwitcher.js",
|
|
52
|
+
"./toolbar/compass": "./toolbar/compass.js",
|
|
53
|
+
"./toolbar/compass.js": "./toolbar/compass.js",
|
|
54
|
+
"./toolbar/fullscreenController": "./toolbar/fullscreenController.js",
|
|
55
|
+
"./toolbar/fullscreenController.js": "./toolbar/fullscreenController.js",
|
|
56
|
+
"./toolbar/zoomController": "./toolbar/zoomController.js",
|
|
57
|
+
"./toolbar/zoomController.js": "./toolbar/zoomController.js",
|
|
58
|
+
"./stores/mapStore": "./stores/mapStore.js",
|
|
59
|
+
"./stores/mapStore.js": "./stores/mapStore.js",
|
|
60
|
+
"./utils/basemapSwitcher": "./utils/basemapSwitcher.js",
|
|
61
|
+
"./utils/basemapSwitcher.js": "./utils/basemapSwitcher.js",
|
|
62
|
+
"./utils/mapImagery": "./utils/mapImagery.js",
|
|
63
|
+
"./utils/mapImagery.js": "./utils/mapImagery.js",
|
|
64
|
+
"./utils/eventBus": "./utils/eventBus.js",
|
|
65
|
+
"./utils/useEventBus": "./utils/useEventBus.js",
|
|
66
|
+
"./utils/getMapCenterPosition": "./utils/getMapCenterPosition.js",
|
|
67
|
+
"./utils/droneSelection": "./utils/droneSelection.js",
|
|
68
|
+
"./config": "./config/index.js",
|
|
69
|
+
"./config/index": "./config/index.js",
|
|
70
|
+
"./config/hooks": "./config/hooks.js",
|
|
71
|
+
"./api/gaode": "./api/gaode.js"
|
|
72
|
+
},
|
|
73
|
+
"files": [
|
|
74
|
+
"*.js",
|
|
75
|
+
"toolbar/**/*.js",
|
|
76
|
+
"stores/**/*.js",
|
|
77
|
+
"utils/**/*.js",
|
|
78
|
+
"config/**/*.js",
|
|
79
|
+
"api/**/*.js"
|
|
80
|
+
],
|
|
81
|
+
"scripts": {
|
|
82
|
+
"sync": "node scripts/sync-from-source.mjs",
|
|
83
|
+
"prepublishOnly": "node scripts/verify-files.mjs"
|
|
84
|
+
},
|
|
85
|
+
"keywords": [
|
|
86
|
+
"cesium",
|
|
87
|
+
"map",
|
|
88
|
+
"drone",
|
|
89
|
+
"3d",
|
|
90
|
+
"gis"
|
|
91
|
+
],
|
|
92
|
+
"author": "huweili <czxyhuweili@163.com>",
|
|
93
|
+
"license": "MIT",
|
|
94
|
+
"repository": {
|
|
95
|
+
"type": "git",
|
|
96
|
+
"url": ""
|
|
97
|
+
},
|
|
98
|
+
"peerDependencies": {
|
|
99
|
+
"cesium": ">=1.100.0",
|
|
100
|
+
"mitt": "^3.0.0",
|
|
101
|
+
"pinia": "^2.0.0 || ^3.0.0",
|
|
102
|
+
"qrcode": "^1.5.0",
|
|
103
|
+
"vue": "^3.3.0"
|
|
104
|
+
},
|
|
105
|
+
"dependencies": {},
|
|
106
|
+
"engines": {
|
|
107
|
+
"node": ">=18"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 底图切换器(支持 coexistMap 离线/在线共存)
|
|
3
|
+
*/
|
|
4
|
+
import { applyImageryLayers } from './mapImagery.js'
|
|
5
|
+
|
|
6
|
+
export const BasemapIds = window.MapConfig?.basemaps?.reduce((acc, item) => {
|
|
7
|
+
const key = item.id.toUpperCase().replace(/-/g, '_')
|
|
8
|
+
acc[key] = item.id
|
|
9
|
+
return acc
|
|
10
|
+
}, {}) || {}
|
|
11
|
+
|
|
12
|
+
export function createBasemapSwitcher() {
|
|
13
|
+
let currentBasemapId = null
|
|
14
|
+
|
|
15
|
+
const getBasemaps = () => window.MapConfig?.basemaps || []
|
|
16
|
+
const getBasemapById = (id) => getBasemaps().find((b) => b.id === id)
|
|
17
|
+
|
|
18
|
+
const loadBasemap = (viewer, basemapConfig) => {
|
|
19
|
+
if (!viewer || !basemapConfig) return
|
|
20
|
+
applyImageryLayers(viewer, window.MapConfig || {}, basemapConfig)
|
|
21
|
+
currentBasemapId = basemapConfig.id
|
|
22
|
+
window._currentBasemapId = basemapConfig.id
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const switchById = (viewer, id) => {
|
|
26
|
+
const basemap = getBasemapById(id)
|
|
27
|
+
if (!basemap) {
|
|
28
|
+
console.error(`Basemap not found with id: ${id}`)
|
|
29
|
+
return
|
|
30
|
+
}
|
|
31
|
+
loadBasemap(viewer, basemap)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const toggleSatelliteBasemap = (viewer) => {
|
|
35
|
+
if (!viewer || !window.MapConfig) return
|
|
36
|
+
|
|
37
|
+
if (currentBasemapId === BasemapIds.AMAP_SATELLITE) {
|
|
38
|
+
const defaultBasemap = getBasemaps().find((b) => b.show)
|
|
39
|
+
if (defaultBasemap) {
|
|
40
|
+
loadBasemap(viewer, defaultBasemap)
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
switchById(viewer, BasemapIds.AMAP_SATELLITE)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const getCurrentBasemapId = () => currentBasemapId
|
|
48
|
+
const setCurrentBasemapId = (id) => {
|
|
49
|
+
currentBasemapId = id
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const toggleBasemapPickerPanel = (viewer, triggerButton) => {
|
|
53
|
+
if (!viewer?.container) return
|
|
54
|
+
|
|
55
|
+
const PANEL_ID = 'cesium-basemap-picker-panel'
|
|
56
|
+
let panel = document.getElementById(PANEL_ID)
|
|
57
|
+
|
|
58
|
+
const syncPanelViewport = () => {
|
|
59
|
+
if (!panel || panel.style.display === 'none') return
|
|
60
|
+
const buttonRect = triggerButton?.getBoundingClientRect()
|
|
61
|
+
if (!buttonRect) return
|
|
62
|
+
|
|
63
|
+
const margin = 35
|
|
64
|
+
const panelRect = panel.getBoundingClientRect()
|
|
65
|
+
const toolbar = document.querySelector('.cesium-viewer-toolbar')
|
|
66
|
+
const toolbarRect = toolbar?.getBoundingClientRect()
|
|
67
|
+
|
|
68
|
+
let left = (toolbarRect?.left || 0) + (toolbarRect?.width || 0)
|
|
69
|
+
let top = buttonRect.bottom - panelRect.height
|
|
70
|
+
|
|
71
|
+
if (top < 0) top = margin
|
|
72
|
+
if (top + panelRect.height > window.innerHeight) {
|
|
73
|
+
top = window.innerHeight - panelRect.height - margin
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
panel.style.position = 'fixed'
|
|
77
|
+
panel.style.right = 'auto'
|
|
78
|
+
panel.style.bottom = 'auto'
|
|
79
|
+
panel.style.top = `${Math.round(top)}px`
|
|
80
|
+
panel.style.left = `${Math.round(left)}px`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const detachOutside = () => {
|
|
84
|
+
if (panel?._removeOutsideListener) {
|
|
85
|
+
panel._removeOutsideListener()
|
|
86
|
+
panel._removeOutsideListener = null
|
|
87
|
+
}
|
|
88
|
+
if (panel?._onReposition) {
|
|
89
|
+
window.removeEventListener('resize', panel._onReposition)
|
|
90
|
+
window.removeEventListener('scroll', panel._onReposition, true)
|
|
91
|
+
panel._onReposition = null
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const hidePanel = () => {
|
|
96
|
+
detachOutside()
|
|
97
|
+
if (panel) panel.style.display = 'none'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const isPanelVisible = () => panel && panel.style.display !== 'none'
|
|
101
|
+
|
|
102
|
+
if (isPanelVisible()) {
|
|
103
|
+
hidePanel()
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!panel) {
|
|
108
|
+
panel = document.createElement('div')
|
|
109
|
+
panel.id = PANEL_ID
|
|
110
|
+
panel.style.cssText = `
|
|
111
|
+
position: fixed;
|
|
112
|
+
z-index: 2147483647;
|
|
113
|
+
min-width: 280px;
|
|
114
|
+
max-width: 320px;
|
|
115
|
+
max-height: min(70vh, 500px);
|
|
116
|
+
overflow-y: auto;
|
|
117
|
+
overflow-x: hidden;
|
|
118
|
+
background: rgba(28, 32, 40, 0.94);
|
|
119
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
120
|
+
border-radius: 8px;
|
|
121
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
|
|
122
|
+
display: none;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
padding: 8px;
|
|
125
|
+
scrollbar-width: thin;
|
|
126
|
+
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
|
|
127
|
+
`
|
|
128
|
+
const list = document.createElement('div')
|
|
129
|
+
list.setAttribute('data-role', 'basemap-list')
|
|
130
|
+
list.style.cssText = 'display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px;'
|
|
131
|
+
panel.appendChild(list)
|
|
132
|
+
document.body.appendChild(panel)
|
|
133
|
+
|
|
134
|
+
const style = document.createElement('style')
|
|
135
|
+
style.textContent = `
|
|
136
|
+
#${PANEL_ID}::-webkit-scrollbar { width: 6px; }
|
|
137
|
+
#${PANEL_ID}::-webkit-scrollbar-track { background: transparent; }
|
|
138
|
+
#${PANEL_ID}::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.2); border-radius: 3px; }
|
|
139
|
+
#${PANEL_ID}::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.35); }
|
|
140
|
+
`
|
|
141
|
+
document.head.appendChild(style)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const listEl = panel.querySelector('[data-role="basemap-list"]')
|
|
145
|
+
listEl.innerHTML = ''
|
|
146
|
+
|
|
147
|
+
const currentId = getCurrentBasemapId()
|
|
148
|
+
getBasemaps().forEach((bm) => {
|
|
149
|
+
const row = document.createElement('button')
|
|
150
|
+
row.type = 'button'
|
|
151
|
+
const active = bm.id === currentId
|
|
152
|
+
row.style.cssText = `
|
|
153
|
+
display: flex;
|
|
154
|
+
flex-direction: column;
|
|
155
|
+
width: 100%;
|
|
156
|
+
padding: 8px;
|
|
157
|
+
border: none;
|
|
158
|
+
background: ${active ? 'rgba(255, 255, 255, 0.1)' : 'transparent'};
|
|
159
|
+
cursor: pointer;
|
|
160
|
+
gap: 6px;
|
|
161
|
+
align-items: center;
|
|
162
|
+
justify-content: center;
|
|
163
|
+
`
|
|
164
|
+
|
|
165
|
+
const imgContainer = document.createElement('div')
|
|
166
|
+
imgContainer.style.cssText = `
|
|
167
|
+
width: 100%;
|
|
168
|
+
height: 60px;
|
|
169
|
+
border-radius: 4px;
|
|
170
|
+
overflow: hidden;
|
|
171
|
+
border: 2px solid ${active ? '#3B82F6' : 'transparent'};
|
|
172
|
+
position: relative;
|
|
173
|
+
`
|
|
174
|
+
if (bm.customMapColorStyle?.enabled) {
|
|
175
|
+
imgContainer.style.backgroundColor = bm.customMapColorStyle.MapBaseColor
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const img = document.createElement('img')
|
|
179
|
+
img.src = import.meta.env.BASE_URL + bm.thumbnail || ''
|
|
180
|
+
img.style.cssText = `
|
|
181
|
+
width: 100%;
|
|
182
|
+
height: 100%;
|
|
183
|
+
object-fit: cover;
|
|
184
|
+
opacity: ${bm.customMapColorStyle?.enabled ? 0.7 : 1};
|
|
185
|
+
`
|
|
186
|
+
img.onerror = function () {
|
|
187
|
+
this.style.display = 'none'
|
|
188
|
+
}
|
|
189
|
+
imgContainer.appendChild(img)
|
|
190
|
+
|
|
191
|
+
const nameSpan = document.createElement('span')
|
|
192
|
+
nameSpan.textContent = bm.name
|
|
193
|
+
nameSpan.style.cssText = `
|
|
194
|
+
font-size: 12px;
|
|
195
|
+
color: #e8eaed;
|
|
196
|
+
font-weight: ${active ? '600' : '400'};
|
|
197
|
+
text-align: center;
|
|
198
|
+
`
|
|
199
|
+
|
|
200
|
+
row.appendChild(imgContainer)
|
|
201
|
+
row.appendChild(nameSpan)
|
|
202
|
+
row.onmouseenter = () => {
|
|
203
|
+
row.style.background = 'rgba(255, 255, 255, 0.08)'
|
|
204
|
+
}
|
|
205
|
+
row.onmouseleave = () => {
|
|
206
|
+
row.style.background = active ? 'rgba(255, 255, 255, 0.1)' : 'transparent'
|
|
207
|
+
}
|
|
208
|
+
row.onclick = (e) => {
|
|
209
|
+
e.stopPropagation()
|
|
210
|
+
switchById(viewer, bm.id)
|
|
211
|
+
hidePanel()
|
|
212
|
+
}
|
|
213
|
+
listEl.appendChild(row)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
panel.style.display = 'flex'
|
|
217
|
+
requestAnimationFrame(() => {
|
|
218
|
+
requestAnimationFrame(() => syncPanelViewport())
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
detachOutside()
|
|
222
|
+
const onReposition = () => syncPanelViewport()
|
|
223
|
+
panel._onReposition = onReposition
|
|
224
|
+
window.addEventListener('resize', onReposition)
|
|
225
|
+
window.addEventListener('scroll', onReposition, true)
|
|
226
|
+
|
|
227
|
+
const onOutsidePointer = (e) => {
|
|
228
|
+
if (!panel || panel.style.display === 'none') return
|
|
229
|
+
if (panel.contains(e.target)) return
|
|
230
|
+
if (triggerButton && triggerButton.contains(e.target)) return
|
|
231
|
+
hidePanel()
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
requestAnimationFrame(() => {
|
|
235
|
+
requestAnimationFrame(() => {
|
|
236
|
+
document.addEventListener('pointerdown', onOutsidePointer, true)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
panel._removeOutsideListener = () =>
|
|
240
|
+
document.removeEventListener('pointerdown', onOutsidePointer, true)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
switchById,
|
|
245
|
+
toggleSatelliteBasemap,
|
|
246
|
+
getBasemaps,
|
|
247
|
+
getBasemapById,
|
|
248
|
+
getCurrentBasemapId,
|
|
249
|
+
setCurrentBasemapId,
|
|
250
|
+
toggleBasemapPickerPanel,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 底图与离线共存图层加载
|
|
3
|
+
*/
|
|
4
|
+
import * as Cesium from 'cesium'
|
|
5
|
+
import { customColorTileProvider, hexToRgb } from '../tileProviders.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 解析 coexistMap 自定义配色(支持 hex 或已转换的 rgb)
|
|
9
|
+
* @param {Object} coexistMap
|
|
10
|
+
* @returns {Object | null}
|
|
11
|
+
*/
|
|
12
|
+
function resolveCoexistColorStyle(coexistMap) {
|
|
13
|
+
const style = coexistMap?.customMapColorStyle
|
|
14
|
+
if (!style?.enabled) return null
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
enabled: true,
|
|
18
|
+
MapBaseColor:
|
|
19
|
+
typeof style.MapBaseColor === 'string'
|
|
20
|
+
? hexToRgb(style.MapBaseColor)
|
|
21
|
+
: style.MapBaseColor,
|
|
22
|
+
RoadLightColor:
|
|
23
|
+
typeof style.RoadLightColor === 'string'
|
|
24
|
+
? hexToRgb(style.RoadLightColor)
|
|
25
|
+
: style.RoadLightColor,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} basemapConfig
|
|
31
|
+
* @returns {Cesium.ImageryProvider}
|
|
32
|
+
*/
|
|
33
|
+
export function createBasemapProvider(basemapConfig) {
|
|
34
|
+
const tileProviderOptions = {
|
|
35
|
+
url: basemapConfig.url,
|
|
36
|
+
subdomains: ['1', '2', '3', '4'],
|
|
37
|
+
maximumLevel: basemapConfig.maximumLevel ?? 18,
|
|
38
|
+
credit: basemapConfig.name,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (basemapConfig.customMapColorStyle?.enabled) {
|
|
42
|
+
return new customColorTileProvider(tileProviderOptions, basemapConfig.customMapColorStyle)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return new Cesium.UrlTemplateImageryProvider(tileProviderOptions)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 离线瓦片(限定矩形范围,仅在该区域内请求瓦片)
|
|
50
|
+
* @param {Object} coexistMap mapConfig.coexistMap
|
|
51
|
+
* @returns {Cesium.ImageryProvider | null}
|
|
52
|
+
*/
|
|
53
|
+
export function createCoexistOfflineProvider(coexistMap) {
|
|
54
|
+
const offline = coexistMap?.map
|
|
55
|
+
if (!offline?.url) return null
|
|
56
|
+
|
|
57
|
+
const options = {
|
|
58
|
+
url: offline.url,
|
|
59
|
+
minimumLevel: offline.minimumLevel ?? 0,
|
|
60
|
+
maximumLevel: offline.maximumLevel ?? 18,
|
|
61
|
+
credit: offline.name || '离线地图',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const bounds = offline.bounds
|
|
65
|
+
if (bounds) {
|
|
66
|
+
options.rectangle = Cesium.Rectangle.fromDegrees(
|
|
67
|
+
bounds.west,
|
|
68
|
+
bounds.south,
|
|
69
|
+
bounds.east,
|
|
70
|
+
bounds.north,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const colorStyle = resolveCoexistColorStyle(coexistMap)
|
|
75
|
+
if (colorStyle) {
|
|
76
|
+
return new customColorTileProvider(options, colorStyle)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return new Cesium.UrlTemplateImageryProvider(options)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 解析在线底图:coexist 开启且配置了 mapId 时优先用该底图,否则用 show:true 的底图
|
|
84
|
+
* @param {Object} mapOptions
|
|
85
|
+
* @returns {Object | undefined}
|
|
86
|
+
*/
|
|
87
|
+
export function resolveOnlineBasemap(mapOptions) {
|
|
88
|
+
const basemaps = mapOptions?.basemaps || []
|
|
89
|
+
const coexist = mapOptions?.coexistMap
|
|
90
|
+
|
|
91
|
+
if (coexist?.enabled && coexist.mapId) {
|
|
92
|
+
const byId = basemaps.find((b) => b.id === coexist.mapId)
|
|
93
|
+
if (byId) return byId
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return basemaps.find((b) => b.show === true)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 在线底图(底层,全球)+ 离线底图(顶层,仅 bounds 内加载)
|
|
101
|
+
* @param {import('cesium').Viewer} viewer
|
|
102
|
+
* @param {Object} mapOptions
|
|
103
|
+
* @param {Object} basemapConfig
|
|
104
|
+
* @returns {{ onlineLayer: Cesium.ImageryLayer, offlineLayer: Cesium.ImageryLayer | null }}
|
|
105
|
+
*/
|
|
106
|
+
export function applyImageryLayers(viewer, mapOptions, basemapConfig) {
|
|
107
|
+
viewer.imageryLayers.removeAll()
|
|
108
|
+
|
|
109
|
+
const onlineLayer = new Cesium.ImageryLayer(createBasemapProvider(basemapConfig))
|
|
110
|
+
viewer.imageryLayers.add(onlineLayer)
|
|
111
|
+
|
|
112
|
+
let offlineLayer = null
|
|
113
|
+
const coexist = mapOptions?.coexistMap
|
|
114
|
+
if (coexist?.enabled) {
|
|
115
|
+
const offlineProvider = createCoexistOfflineProvider(coexist)
|
|
116
|
+
if (offlineProvider) {
|
|
117
|
+
offlineLayer = new Cesium.ImageryLayer(offlineProvider)
|
|
118
|
+
viewer.imageryLayers.add(offlineLayer)
|
|
119
|
+
console.log(
|
|
120
|
+
`离线共存已启用:${coexist.map?.name},范围外使用在线底图「${basemapConfig.name}」`,
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { onlineLayer, offlineLayer }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 底图切换后重新叠加离线层(仅替换在线层,保留离线层逻辑:先清再全量加载)
|
|
130
|
+
*/
|
|
131
|
+
export function switchBasemapWithCoexist(viewer, mapOptions, basemapConfig) {
|
|
132
|
+
const layers = applyImageryLayers(viewer, mapOptions, basemapConfig)
|
|
133
|
+
window._currentBasemapId = basemapConfig.id
|
|
134
|
+
return layers
|
|
135
|
+
}
|