huweili-cesium 1.2.11 → 1.2.13

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.
@@ -106,14 +106,6 @@ export function createCustomToolbarButtons() {
106
106
  isCompass: true,
107
107
  onClick: (v) => resetCompassDirection(v)
108
108
  },
109
- {
110
- title: '机型统计',
111
- iconSrc: `${import.meta.env.BASE_URL}/images/new/tj.svg`,
112
- onClick: () => {
113
- // 通过事件总线通知切换机型统计柱状图显示状态
114
- emit('toggleBarChartControl', 'toggle')
115
- }
116
- },
117
109
  {
118
110
  title: '返回初始点位',
119
111
  iconSrc: `${import.meta.env.BASE_URL}/images/new/home.svg`,
package/js/index.js CHANGED
@@ -8,6 +8,7 @@ export { getClickHandler, openDroneDetailById, closeDroneDetailById, bindDroneLa
8
8
  export { createCustomToolbarButtons } from './customToolbarButtons.js'
9
9
  export { drawFence } from './drawFence.js'
10
10
  export { drawFenceNew } from './drawFenceNew.js'
11
+ export { measureDistance } from './measureDistance.js'
11
12
  export { createDroneRippleDom } from './droneRipple.js'
12
13
  export { geometryConfig } from './geometry.js'
13
14
  export { groundLinkConfig } from './groundLink.js'
@@ -0,0 +1,382 @@
1
+ /**
2
+ * 两点地面直线测距模块
3
+ *
4
+ * 点击地图上第一个点作为起点,再点击第二个点作为终点,测量两点之间的地面直线距离。
5
+ * 无论地图处于 2D 还是 3D,采样点都会被转换为贴地坐标(height = 0),不携带高度。
6
+ *
7
+ * @author huweili
8
+ * @email czxyhuweili@163.com
9
+ * @version 1.0.0
10
+ */
11
+ import * as Cesium from 'cesium'
12
+ import { useMapStore } from './stores/mapStore.js'
13
+
14
+ export function measureDistance() {
15
+ const mapStore = useMapStore()
16
+
17
+ const formatDistance = (distance) => {
18
+ if (!Number.isFinite(distance)) return '0 m'
19
+ if (distance >= 1000) return `${(distance / 1000).toFixed(2)} km`
20
+ return `${distance.toFixed(2)} m`
21
+ }
22
+
23
+ const createMouseTipEntity = (map, options) => {
24
+ return map.entities.add({
25
+ id: options.id,
26
+ position: new Cesium.CallbackProperty(() => options.getPosition(), false),
27
+ label: {
28
+ text: new Cesium.CallbackProperty(() => options.getText(), false),
29
+ font: options.font || '10px Microsoft YaHei',
30
+ fillColor: options.fillColor || Cesium.Color.WHITE,
31
+ showBackground: true,
32
+ backgroundColor: Cesium.Color.fromCssColorString(options.backgroundColor || 'rgba(0, 0, 0, 0.6)'),
33
+ backgroundPadding: options.backgroundPadding || new Cesium.Cartesian2(10, 6),
34
+ verticalOrigin: options.verticalOrigin || Cesium.VerticalOrigin.BOTTOM,
35
+ horizontalOrigin: options.horizontalOrigin || Cesium.HorizontalOrigin.LEFT,
36
+ pixelOffset: options.pixelOffset || new Cesium.Cartesian2(16, -16),
37
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
38
+ distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, options.maxDistance || Number.POSITIVE_INFINITY)
39
+ }
40
+ })
41
+ }
42
+
43
+ /**
44
+ * 从屏幕坐标拾取地面点,并强制转换为 height = 0 的地面坐标。
45
+ * @param {Cesium.Viewer} map Cesium Viewer 实例
46
+ * @param {Cesium.Cartesian2} screenPosition 屏幕坐标
47
+ * @returns {Cesium.Cartesian3|null}
48
+ */
49
+ const getGroundCartesianFromScreen = (map, screenPosition) => {
50
+ if (!screenPosition) return null
51
+
52
+ const scene = map.scene
53
+ let cartesian = null
54
+
55
+ if (scene.pickPositionSupported) {
56
+ cartesian = scene.pickPosition(screenPosition)
57
+ }
58
+
59
+ if (!Cesium.defined(cartesian)) {
60
+ const ray = map.camera.getPickRay(screenPosition)
61
+ if (!ray) return null
62
+ cartesian = scene.globe.pick(ray, scene)
63
+ }
64
+
65
+ if (!Cesium.defined(cartesian)) return null
66
+
67
+ const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
68
+ return Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, 0)
69
+ }
70
+
71
+ const cartesianToLngLatHeight = (cartesian) => {
72
+ const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
73
+ return {
74
+ lng: Cesium.Math.toDegrees(cartographic.longitude),
75
+ lat: Cesium.Math.toDegrees(cartographic.latitude),
76
+ height: 0
77
+ }
78
+ }
79
+
80
+ const getGroundDistance = (startPosition, endPosition) => {
81
+ if (!startPosition || !endPosition) return 0
82
+ return Cesium.Cartesian3.distance(startPosition, endPosition)
83
+ }
84
+
85
+ /**
86
+ * 开始两点地面直线测距
87
+ * @param {Object} options 配置项
88
+ * @param {string} [options.mapId] 地图实例 ID
89
+ * @param {string} options.id 测距图形唯一标识
90
+ * @param {string} [options.color='#00ffff'] 线和点颜色
91
+ * @param {number} [options.width=3] 线宽
92
+ * @param {(result: Object) => void} [options.onFinish] 测距完成回调
93
+ * @param {(result: Object) => void} [options.onChange] 测距过程回调
94
+ * @param {() => void} [options.onCancel] 取消回调
95
+ * @returns {Object|null} 测距控制器
96
+ */
97
+ const startMeasure = (options = {}) => {
98
+ const map = mapStore.getMap(options.mapId)
99
+ if (!map) {
100
+ console.error('地图实例不存在')
101
+ return null
102
+ }
103
+
104
+ if (!options.id) {
105
+ console.error('测距时必须提供 id')
106
+ return null
107
+ }
108
+
109
+ if (mapStore.hasGraphicMap(options.id, options.mapId)) {
110
+ console.warn(`id: ${options.id} 测距结果已存在`)
111
+ return null
112
+ }
113
+
114
+ const color = Cesium.Color.fromCssColorString(options.color || '#00ffff')
115
+ const width = options.width ?? 3
116
+ const clampToGround = options.clampToGround ?? true
117
+
118
+ let startPosition = null
119
+ let endPosition = null
120
+ let floatingPosition = null
121
+ let cursorPosition = null
122
+ let startPointEntity = null
123
+ let endPointEntity = null
124
+ let lineEntity = null
125
+ let distanceLabelEntity = null
126
+ let tipEntity = null
127
+ let handler = null
128
+ let isFinished = false
129
+
130
+ const tempIds = {
131
+ startPoint: `${options.id}_measure_start_point`,
132
+ endPoint: `${options.id}_measure_end_point`,
133
+ line: `${options.id}_measure_line`,
134
+ label: `${options.id}_measure_label`,
135
+ tip: `${options.id}_measure_tip`
136
+ }
137
+
138
+ const getLinePositions = () => {
139
+ if (!startPosition) return []
140
+ const targetPosition = endPosition || floatingPosition
141
+ if (!targetPosition) return [startPosition]
142
+ return [startPosition, targetPosition]
143
+ }
144
+
145
+ const getCurrentDistance = () => getGroundDistance(startPosition, endPosition || floatingPosition)
146
+
147
+ const getMidPosition = () => {
148
+ const targetPosition = endPosition || floatingPosition
149
+ if (!startPosition || !targetPosition) return startPosition || targetPosition || cursorPosition || null
150
+ const startCartographic = Cesium.Cartographic.fromCartesian(startPosition)
151
+ const endCartographic = Cesium.Cartographic.fromCartesian(targetPosition)
152
+ return Cesium.Cartesian3.fromRadians(
153
+ (startCartographic.longitude + endCartographic.longitude) / 2,
154
+ (startCartographic.latitude + endCartographic.latitude) / 2,
155
+ 0
156
+ )
157
+ }
158
+
159
+ const getTipText = () => {
160
+ if (!startPosition) return '点击确定起点'
161
+ if (!endPosition) return `点击确定终点,当前距离:${formatDistance(getCurrentDistance())}`
162
+ return `测距完成:${formatDistance(getCurrentDistance())}`
163
+ }
164
+
165
+ const buildResult = () => ({
166
+ id: options.id,
167
+ start: startPosition ? cartesianToLngLatHeight(startPosition) : null,
168
+ end: endPosition ? cartesianToLngLatHeight(endPosition) : null,
169
+ distance: getGroundDistance(startPosition, endPosition),
170
+ distanceText: formatDistance(getGroundDistance(startPosition, endPosition))
171
+ })
172
+
173
+ const emitChange = () => {
174
+ if (typeof options.onChange !== 'function' || !startPosition) return
175
+ options.onChange({
176
+ id: options.id,
177
+ start: cartesianToLngLatHeight(startPosition),
178
+ end: (endPosition || floatingPosition) ? cartesianToLngLatHeight(endPosition || floatingPosition) : null,
179
+ distance: getCurrentDistance(),
180
+ distanceText: formatDistance(getCurrentDistance())
181
+ })
182
+ }
183
+
184
+ const ensurePreviewEntities = () => {
185
+ if (!startPointEntity) {
186
+ startPointEntity = map.entities.add({
187
+ id: tempIds.startPoint,
188
+ position: startPosition,
189
+ point: {
190
+ pixelSize: 10,
191
+ color,
192
+ outlineColor: Cesium.Color.WHITE,
193
+ outlineWidth: 2,
194
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
195
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
196
+ }
197
+ })
198
+ }
199
+
200
+ if (!lineEntity) {
201
+ lineEntity = map.entities.add({
202
+ id: tempIds.line,
203
+ polyline: {
204
+ positions: new Cesium.CallbackProperty(() => getLinePositions(), false),
205
+ width,
206
+ material: color,
207
+ clampToGround
208
+ }
209
+ })
210
+ }
211
+
212
+ if (!distanceLabelEntity) {
213
+ distanceLabelEntity = map.entities.add({
214
+ id: tempIds.label,
215
+ position: new Cesium.CallbackProperty(() => getMidPosition(), false),
216
+ label: {
217
+ text: new Cesium.CallbackProperty(() => formatDistance(getCurrentDistance()), false),
218
+ font: '14px Microsoft YaHei',
219
+ fillColor: Cesium.Color.WHITE,
220
+ showBackground: true,
221
+ backgroundColor: Cesium.Color.fromCssColorString('rgba(0, 0, 0, 0.65)'),
222
+ backgroundPadding: new Cesium.Cartesian2(10, 6),
223
+ verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
224
+ horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
225
+ pixelOffset: new Cesium.Cartesian2(0, -12),
226
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
227
+ distanceDisplayCondition: new Cesium.DistanceDisplayCondition(0, Number.POSITIVE_INFINITY)
228
+ }
229
+ })
230
+ }
231
+ }
232
+
233
+ const createEndPointEntity = () => {
234
+ if (endPointEntity || !endPosition) return
235
+ endPointEntity = map.entities.add({
236
+ id: tempIds.endPoint,
237
+ position: endPosition,
238
+ point: {
239
+ pixelSize: 10,
240
+ color,
241
+ outlineColor: Cesium.Color.WHITE,
242
+ outlineWidth: 2,
243
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
244
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
245
+ }
246
+ })
247
+ }
248
+
249
+ const removeTemporaryEntities = () => {
250
+ ;[startPointEntity, endPointEntity, lineEntity, distanceLabelEntity, tipEntity].forEach((entity) => {
251
+ if (entity) map.entities.remove(entity)
252
+ })
253
+ startPointEntity = null
254
+ endPointEntity = null
255
+ lineEntity = null
256
+ distanceLabelEntity = null
257
+ tipEntity = null
258
+ }
259
+
260
+ const destroyHandler = () => {
261
+ if (handler) {
262
+ handler.destroy()
263
+ handler = null
264
+ }
265
+ }
266
+
267
+ const cancel = () => {
268
+ if (isFinished) return
269
+ destroyHandler()
270
+ removeTemporaryEntities()
271
+ if (typeof options.onCancel === 'function') options.onCancel()
272
+ }
273
+
274
+ const finalize = () => {
275
+ if (isFinished || !startPosition || !endPosition) return null
276
+ isFinished = true
277
+ destroyHandler()
278
+ if (tipEntity) {
279
+ map.entities.remove(tipEntity)
280
+ tipEntity = null
281
+ }
282
+ createEndPointEntity()
283
+ const result = buildResult()
284
+ const graphic = {
285
+ id: options.id,
286
+ startPointEntity,
287
+ endPointEntity,
288
+ lineEntity,
289
+ labelEntity: distanceLabelEntity,
290
+ result,
291
+ type: 'measureDistance'
292
+ }
293
+ mapStore.setGraphicMap(options.id, graphic, options.mapId)
294
+ if (typeof options.onFinish === 'function') options.onFinish(result)
295
+ return result
296
+ }
297
+
298
+ tipEntity = createMouseTipEntity(map, {
299
+ id: tempIds.tip,
300
+ getPosition: () => cursorPosition || endPosition || floatingPosition || startPosition,
301
+ getText: getTipText
302
+ })
303
+
304
+ handler = new Cesium.ScreenSpaceEventHandler(map.scene.canvas)
305
+
306
+ handler.setInputAction((movement) => {
307
+ if (isFinished) return
308
+ cursorPosition = getGroundCartesianFromScreen(map, movement.endPosition)
309
+ if (startPosition && cursorPosition) {
310
+ floatingPosition = cursorPosition
311
+ emitChange()
312
+ }
313
+ }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
314
+
315
+ handler.setInputAction((click) => {
316
+ if (isFinished) return
317
+ const cartesian = getGroundCartesianFromScreen(map, click.position)
318
+ if (!cartesian) return
319
+
320
+ if (!startPosition) {
321
+ startPosition = cartesian
322
+ floatingPosition = cartesian
323
+ cursorPosition = cartesian
324
+ ensurePreviewEntities()
325
+ emitChange()
326
+ return
327
+ }
328
+
329
+ endPosition = cartesian
330
+ floatingPosition = null
331
+ createEndPointEntity()
332
+ finalize()
333
+ }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
334
+
335
+ handler.setInputAction(() => {
336
+ cancel()
337
+ }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
338
+
339
+ return {
340
+ id: options.id,
341
+ finish: finalize,
342
+ cancel,
343
+ destroy: cancel,
344
+ getStart: () => (startPosition ? cartesianToLngLatHeight(startPosition) : null),
345
+ getEnd: () => (endPosition ? cartesianToLngLatHeight(endPosition) : null),
346
+ getDistance: () => getGroundDistance(startPosition, endPosition || floatingPosition),
347
+ getDistanceText: () => formatDistance(getGroundDistance(startPosition, endPosition || floatingPosition))
348
+ }
349
+ }
350
+
351
+ /**
352
+ * 删除测距结果
353
+ * @param {string} id 测距图形 ID
354
+ * @param {string} [mapId] 地图实例 ID
355
+ * @returns {boolean}
356
+ */
357
+ const destroyMeasure = (id, mapId) => {
358
+ const map = mapStore.getMap(mapId)
359
+ if (!map) {
360
+ console.error('地图实例不存在')
361
+ return false
362
+ }
363
+
364
+ const graphic = mapStore.getGraphicMap(id, mapId)
365
+ if (!graphic) {
366
+ console.warn(`id: ${id} 测距结果不存在`)
367
+ return false
368
+ }
369
+
370
+ ;[graphic.startPointEntity, graphic.endPointEntity, graphic.lineEntity, graphic.labelEntity].forEach((entity) => {
371
+ if (entity) map.entities.remove(entity)
372
+ })
373
+ mapStore.removeGraphicMap(id, mapId)
374
+ return true
375
+ }
376
+
377
+ return {
378
+ startMeasure,
379
+ destroyMeasure,
380
+ formatDistance
381
+ }
382
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huweili-cesium",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
5
5
  "type": "module",
6
6
  "main": "./index.js",
@@ -39,6 +39,8 @@
39
39
  "./drawFence.js": "./js/drawFence.js",
40
40
  "./drawFenceNew": "./js/drawFenceNew.js",
41
41
  "./drawFenceNew.js": "./js/drawFenceNew.js",
42
+ "./measureDistance": "./js/measureDistance.js",
43
+ "./measureDistance.js": "./js/measureDistance.js",
42
44
  "./dronePlannedRoute": "./js/dronePlannedRoute.js",
43
45
  "./dronePlannedRoute.js": "./js/dronePlannedRoute.js",
44
46
  "./droneRipple": "./js/droneRipple.js",