huweili-cesium 1.2.70 → 1.2.71

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/js/geometry.js CHANGED
@@ -15,6 +15,205 @@ export function geometryConfig() {
15
15
  // 获取地图store实例
16
16
  const mapStore = useMapStore()
17
17
 
18
+ /**
19
+ * 创建锥形波效果
20
+ * @param {Object} options - 锥形波配置选项
21
+ * @param {string} options.id - 效果唯一标识符
22
+ * @param {number[]} options.positions - 位置数组 [lng, lat, height]
23
+ * @param {number} options.heading - 指向方向(度)
24
+ * @param {number} options.pitch - 俯仰角度(度)
25
+ * @param {number} options.length - 圆锥高
26
+ * @param {number} options.bottomRadius - 底部半径
27
+ * @param {number} options.thickness - 厚度
28
+ * @param {string} options.color - 颜色(默认 '#00FFFF')
29
+ * @param {string} options.mapId - 地图实例ID
30
+ * @returns {Cesium.Primitive|null} 创建的锥形波实体,若创建失败则返回null
31
+ */
32
+ const conicalWave = (options) => {
33
+ const map = mapStore.getMap(options.mapId)
34
+ if (!map) {
35
+ console.error('地图实例不存在')
36
+ return null
37
+ }
38
+
39
+ if (mapStore.getGraphicMap(options.id, options.mapId)) {
40
+ console.warn(`id: ${options.id} 效果已存在`)
41
+ return null
42
+ }
43
+
44
+ const [lng, lat, height = 0] = options.positions
45
+ const vertexPosition = Cesium.Cartesian3.fromDegrees(lng, lat, height)
46
+ const heading = Cesium.Math.toRadians(options.heading || 0)
47
+ const pitch = Cesium.Math.toRadians(options.pitch || 0)
48
+ const hpr = new Cesium.HeadingPitchRoll(heading, pitch, 0)
49
+ const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(vertexPosition, hpr)
50
+ const translationMatrix = Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0, 0, -(options.length || 0) / 2))
51
+ Cesium.Matrix4.multiply(modelMatrix, translationMatrix, modelMatrix)
52
+
53
+ const primitive = new Cesium.Primitive({
54
+ geometryInstances: new Cesium.GeometryInstance({
55
+ geometry: new Cesium.CylinderGeometry({
56
+ length: options.length,
57
+ topRadius: 0,
58
+ bottomRadius: options.bottomRadius
59
+ })
60
+ }),
61
+ appearance: new Cesium.MaterialAppearance({
62
+ material: new Cesium.Material({
63
+ fabric: {
64
+ uniforms: {
65
+ color: Cesium.Color.fromCssColorString(options.color || '#00FFFF').withAlpha(0.7),
66
+ duration: 6000,
67
+ repeat: 30,
68
+ offset: 0,
69
+ thickness: options.thickness || 0.3
70
+ },
71
+ source: `
72
+ uniform vec4 color;
73
+ uniform float duration;
74
+ uniform float repeat;
75
+ uniform float offset;
76
+ uniform float thickness;
77
+
78
+ czm_material czm_getMaterial(czm_materialInput materialInput) {
79
+ czm_material material = czm_getDefaultMaterial(materialInput);
80
+ float sp = 1.0/repeat;
81
+ vec2 st = materialInput.st;
82
+ float dis = distance(st, vec2(0.5));
83
+ float time = mod((czm_frameNumber / 60.0) / (duration / 1000.0), 1.0);
84
+ float m = mod(dis + offset - time, sp);
85
+ float a = step(sp*(1.0-thickness), m);
86
+ material.diffuse = color.rgb;
87
+ material.alpha = a * color.a;
88
+ return material;
89
+ }
90
+ `
91
+ },
92
+ translucent: true
93
+ }),
94
+ translucent: true
95
+ }),
96
+ modelMatrix,
97
+ asynchronous: false
98
+ })
99
+
100
+ primitive._originalOptions = { ...options }
101
+ mapStore.setGraphicMap(options.id, primitive, options.mapId)
102
+ map.scene.primitives.add(primitive)
103
+ return primitive
104
+ }
105
+
106
+ /**
107
+ * 更新圆锥体高度 / 位置
108
+ * @param options
109
+ * id 圆锥体唯一标识
110
+ * length 新的高度(米)(可选)
111
+ * positions 新的 [lng, lat, height](可选)
112
+ * mapId 地图实例ID
113
+ * @returns boolean 成功 true / 失败 false
114
+ */
115
+ const updateConeLengthOrPosition = (options) => {
116
+ const { id, length, positions, mapId } = options
117
+ const map = mapStore.getMap(mapId)
118
+ if (!map) {
119
+ console.error('地图实例不存在')
120
+ return false
121
+ }
122
+
123
+ if (length === undefined && positions === undefined) {
124
+ console.error('更新圆锥体高度 / 位置:必须提供高度或位置')
125
+ return false
126
+ }
127
+
128
+ const oldPrimitive = mapStore.getGraphicMap(id, mapId)
129
+ if (!oldPrimitive) {
130
+ console.error(`id: ${id} 圆锥体不存在`)
131
+ return false
132
+ }
133
+
134
+ try {
135
+ const opts = { ...oldPrimitive._originalOptions }
136
+ if (length !== undefined) opts.length = length
137
+ if (positions !== undefined) opts.positions = positions
138
+
139
+ const [lng, lat, height = 0] = opts.positions
140
+ const vertexPos = Cesium.Cartesian3.fromDegrees(lng, lat, height)
141
+ const hpr = new Cesium.HeadingPitchRoll(
142
+ Cesium.Math.toRadians(opts.heading || 0),
143
+ Cesium.Math.toRadians(opts.pitch || 0),
144
+ 0
145
+ )
146
+ const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(vertexPos, hpr)
147
+ const trans = Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0, 0, -(opts.length || 0) / 2))
148
+ Cesium.Matrix4.multiply(modelMatrix, trans, modelMatrix)
149
+
150
+ const newPrimitive = new Cesium.Primitive({
151
+ geometryInstances: new Cesium.GeometryInstance({
152
+ geometry: new Cesium.CylinderGeometry({
153
+ length: opts.length,
154
+ topRadius: 0,
155
+ bottomRadius: opts.bottomRadius
156
+ })
157
+ }),
158
+ appearance: oldPrimitive.appearance,
159
+ modelMatrix,
160
+ asynchronous: false
161
+ })
162
+
163
+ newPrimitive._originalOptions = opts
164
+ map.scene.primitives.remove(oldPrimitive)
165
+ map.scene.primitives.add(newPrimitive)
166
+ mapStore.setGraphicMap(id, newPrimitive, mapId)
167
+ return true
168
+ } catch (e) {
169
+ console.error('更新圆锥体失败:', e)
170
+ return false
171
+ }
172
+ }
173
+
174
+ /**
175
+ * 更新圆锥体姿态
176
+ * @param {Object} options 更新配置参数
177
+ * @param {string} options.id - 圆锥体ID
178
+ * @param {number} options.heading - 新的指向方向(度)
179
+ * @param {number} options.pitch - 新的俯仰角度(度)
180
+ * @param {string} options.mapId - 地图实例ID
181
+ * @returns {boolean} 更新成功返回true,否则返回false
182
+ */
183
+ const updateConePose = (options) => {
184
+ const { id, heading = 0, pitch = 0, mapId } = options
185
+ const map = mapStore.getMap(mapId)
186
+ if (!map) {
187
+ console.error('地图实例不存在')
188
+ return false
189
+ }
190
+
191
+ const primitive = mapStore.getGraphicMap(id, mapId)
192
+ if (!primitive) {
193
+ console.error(`id: ${id} 圆锥体不存在`)
194
+ return false
195
+ }
196
+
197
+ try {
198
+ const opts = primitive._originalOptions || {}
199
+ const [lng, lat, height = 0] = opts.positions || [0, 0, 0]
200
+ const vertexPos = Cesium.Cartesian3.fromDegrees(lng, lat, height)
201
+ const hpr = new Cesium.HeadingPitchRoll(Cesium.Math.toRadians(heading), Cesium.Math.toRadians(pitch), 0)
202
+ const modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(vertexPos, hpr)
203
+ const trans = Cesium.Matrix4.fromTranslation(new Cesium.Cartesian3(0, 0, -(opts.length || 0) / 2))
204
+ Cesium.Matrix4.multiply(modelMatrix, trans, modelMatrix)
205
+
206
+ primitive.modelMatrix = modelMatrix
207
+ opts.heading = heading
208
+ opts.pitch = pitch
209
+ mapStore.setGraphicMap(id, primitive, mapId)
210
+ return true
211
+ } catch (error) {
212
+ console.error(`更新圆锥体姿态失败:`, error)
213
+ return false
214
+ }
215
+ }
216
+
18
217
  /**
19
218
  * 创建四棱锥波效果
20
219
  * @param {string} options.id - 效果唯一标识符
@@ -293,6 +492,9 @@ export function geometryConfig() {
293
492
  };
294
493
 
295
494
  return {
495
+ conicalWave,
496
+ updateConeLengthOrPosition,
497
+ updateConePose,
296
498
  rectangularPyramidWave,
297
499
  updateRectangularPyramidWavePose,
298
500
  updateRectangularPyramidPoseFast,
package/js/movePath.js CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import * as Cesium from 'cesium'
12
12
  import { useMapStore } from './stores/mapStore.js'
13
+ import { DroneConfig } from './config/index.js'
13
14
 
14
15
  const scratchCutoffTime = new Cesium.JulianDate()
15
16
 
@@ -47,18 +48,25 @@ export function movePathConfig() {
47
48
 
48
49
  const positions = trailData.positions
49
50
  const renderPositions = trailData.renderPositions || (trailData.renderPositions = positions.map(point => point.position).filter(Boolean))
51
+ const lastPoint = positions[positions.length - 1]
52
+ const lastPosition = lastPoint?.position
53
+ const minTrailDistanceSquared = trailData.minTrailDistanceSquared || (trailData.minTrailDistanceSquared = DroneConfig.minTrailDistance * DroneConfig.minTrailDistance)
50
54
 
51
- // 轨迹必须和无人机完全同步:每次直接复用同一个点,避免出现一帧延迟的间隙。
55
+ // 降低轨迹追加频率:距离小于5米且距离上次追加不足300ms时,不追加新轨迹点。
56
+ if (lastPoint && lastPosition) {
57
+ const distanceSquared = Cesium.Cartesian3.distanceSquared(lastPosition, newPosition)
58
+ const elapsedSeconds = Cesium.JulianDate.secondsDifference(currentTime, lastPoint.timestamp)
59
+ if (distanceSquared < minTrailDistanceSquared || elapsedSeconds < DroneConfig.minTrailIntervalSeconds) {
60
+ return trailData
61
+ }
62
+ }
63
+
64
+ // 添加新的轨迹点,包含位置和时间戳
52
65
  const clonedPosition = Cesium.Cartesian3.clone(newPosition)
53
66
  positions.push({ position: clonedPosition, timestamp: Cesium.JulianDate.clone(currentTime) })
54
67
  renderPositions.push(clonedPosition)
55
68
  trailData.lastUpdateTime = currentTime
56
69
 
57
- // 立刻通知 Cesium 重新取轨迹数组,避免 CallbackProperty 在下一帧才刷新。
58
- if (trailData.entity?.polyline) {
59
- trailData.entity.polyline.positions = renderPositions
60
- }
61
-
62
70
  // 清理旧轨迹点,只保留最近指定时间的轨迹
63
71
  const trailTime = mapStore.getTrailTime(mapId)
64
72
  if (trailTime === -1 || positions.length <= 1) return trailData
package/js/movePoint.js CHANGED
@@ -240,6 +240,12 @@ export function movePointConfig(baseUrl) {
240
240
  return modelEntity
241
241
  }
242
242
 
243
+ // 初始化复用对象,避免上百个无人机高频移动时反复创建临时对象
244
+ modelEntity.scratchPosition ||= new Cesium.Cartesian3()
245
+ modelEntity.scratchTargetPosition ||= new Cesium.Cartesian3()
246
+ modelEntity.scratchFlightEndTime ||= new Cesium.JulianDate()
247
+ modelEntity.scratchFarFutureTime ||= new Cesium.JulianDate()
248
+
243
249
  // 如果存在实体,确保实体属性实时同步
244
250
  const info = modelEntity.info || (modelEntity.info = {})
245
251
  info.pointId = pointId
@@ -254,46 +260,112 @@ export function movePointConfig(baseUrl) {
254
260
  info.distance = options.distance || ''
255
261
  info.receiveTime = options.receiveTime || ''
256
262
 
257
- // 约定:收到一次无人机数据,就立刻把无人机定位到这个点,同时拖尾追加同一个点。
258
- // 不再做平滑插值,避免无人机和轨迹之间出现滞后。
259
- const targetPosition = Cesium.Cartesian3.fromDegrees(lng, lat, height)
263
+ // ========== 实时位置获取开始 ==========
264
+ const positionProperty = modelEntity.positionProperty || modelEntity.entity?.position
265
+ let currentRealPosition = positionProperty?.getValue?.(currentTime, modelEntity.scratchPosition)
266
+
267
+ if (!currentRealPosition && modelEntity.currentPosition) {
268
+ // 插值属性取不到值时,回退到上一次缓存的真实位置。
269
+ currentRealPosition = Cesium.Cartesian3.clone(modelEntity.currentPosition, modelEntity.scratchPosition)
270
+ }
271
+
272
+ if (!currentRealPosition) {
273
+ // 仍没有当前位置时,使用本次传入坐标作为起点。
274
+ currentRealPosition = Cesium.Cartesian3.fromDegrees(lng, lat, height, modelEntity.scratchPosition)
275
+ }
276
+
277
+ // getValue 复用 scratchPosition,后续 targetPosition 也会复用其它 scratch,先把当前位置固定到独立对象,避免被后续计算覆盖。
278
+ currentRealPosition = Cesium.Cartesian3.clone(currentRealPosition, modelEntity.currentPosition || new Cesium.Cartesian3())
279
+ modelEntity.currentPosition = currentRealPosition
280
+ // ========== 实时位置获取结束 ==========
281
+
282
+ // ========== 保留轨迹 ==========
283
+ // 在飞行开始前,先记录当前点,保证轨迹不会从新目标点突兀开始。
284
+ moveDroneTrail({
285
+ pointId,
286
+ newPosition: currentRealPosition,
287
+ mapId
288
+ })
260
289
 
261
- // 终止旧飞行监听,避免历史插值逻辑继续影响当前定位。
290
+ // ========== 终止当前所有飞行状态 ==========
291
+ // 终止旧飞行监听;飞行插值和轨迹更新都在同一个 onTick 中处理。
262
292
  if (modelEntity.flightEndListener) {
263
293
  map.clock.onTick.removeEventListener(modelEntity.flightEndListener)
264
294
  modelEntity.flightEndListener = null
265
295
  }
266
296
 
267
297
  modelEntity.isFlying = false
268
- modelEntity.currentPosition = Cesium.Cartesian3.clone(targetPosition, modelEntity.currentPosition || new Cesium.Cartesian3())
269
- modelEntity.positionProperty = {
270
- getValue: () => modelEntity.currentPosition
298
+ // 每次移动都重建采样属性,只保留“当前位置 -> 新目标点”的最新飞行段。
299
+ modelEntity.positionProperty = new Cesium.SampledPositionProperty()
300
+ modelEntity.positionProperty.forwardExtrapolationType = Cesium.ExtrapolationType.HOLD
301
+ modelEntity.positionProperty.backwardExtrapolationType = Cesium.ExtrapolationType.HOLD
302
+ modelEntity.positionProperty.forwardExtrapolationDuration = Number.POSITIVE_INFINITY
303
+ modelEntity.positionProperty.backwardExtrapolationDuration = Number.POSITIVE_INFINITY
304
+ modelEntity.positionProperty.addSample(currentTime, currentRealPosition)
305
+ modelEntity.entity.position = modelEntity.positionProperty
306
+
307
+ // ========== 计算新的飞行路径 ==========
308
+ const targetPosition = Cesium.Cartesian3.fromDegrees(lng, lat, height)
309
+ const distanceSquared = Cesium.Cartesian3.distanceSquared(currentRealPosition, targetPosition)
310
+
311
+ // 距离过近则直接定位
312
+ if (distanceSquared < MIN_DRONE_MOVE_DISTANCE_SQUARED) {
313
+ modelEntity.currentPosition = Cesium.Cartesian3.clone(targetPosition, modelEntity.currentPosition)
314
+ modelEntity.positionProperty.addSample(currentTime, modelEntity.currentPosition)
315
+
316
+ moveDroneTrail({
317
+ pointId,
318
+ newPosition: modelEntity.currentPosition,
319
+ mapId
320
+ })
321
+ return modelEntity
271
322
  }
272
- modelEntity.info = modelEntity.info || {}
273
- modelEntity.info.pointId = pointId
274
- modelEntity.info.lng = lng
275
- modelEntity.info.lat = lat
276
- modelEntity.info.height = height
277
- modelEntity.info.speed = speed
278
- modelEntity.info.flyingHandName = options.flyingHandName || ''
279
- modelEntity.info.flyingHandPhone = options.flyingHandPhone || ''
280
- modelEntity.info.flyingHandPosition = options.flyingHandPosition || ''
281
- modelEntity.info.frequencyBand = options.frequencyBand || ''
282
- modelEntity.info.distance = options.distance || ''
283
- modelEntity.info.receiveTime = options.receiveTime || ''
284
-
285
- // 直接使用当前点覆盖实体位置,不再依赖 SampledPositionProperty 做插值。
286
- if (modelEntity.entity) {
287
- modelEntity.entity.position = targetPosition
323
+
324
+ // 按距离和速度计算本段飞行时长,由 SampledPositionProperty 自动插值位置。
325
+ const flightDuration = Math.sqrt(distanceSquared) / speed
326
+ const flightEndTime = Cesium.JulianDate.addSeconds(currentTime, flightDuration, new Cesium.JulianDate())
327
+
328
+ if (!map.clock.shouldAnimate) {
329
+ map.clock.shouldAnimate = true
288
330
  }
289
- modelEntity.positionProperty = null
290
331
 
291
- // 拖尾直接追加同一个点。
292
- moveDroneTrail({
293
- pointId,
294
- newPosition: modelEntity.currentPosition,
295
- mapId
296
- })
332
+ // 不再为每个无人机改全局 clock.startTime/stopTime,避免多无人机互相抢时钟范围。
333
+ modelEntity.positionProperty.addSample(flightEndTime, targetPosition)
334
+ modelEntity.isFlying = true
335
+
336
+ const flightTickListener = (clock) => {
337
+ if (!modelEntity.isFlying) return
338
+
339
+ const isFinished = Cesium.JulianDate.compare(clock.currentTime, flightEndTime) >= 0
340
+ const tickPosition = isFinished
341
+ ? targetPosition
342
+ : modelEntity.positionProperty.getValue(clock.currentTime, modelEntity.scratchPosition)
343
+
344
+ if (tickPosition) {
345
+ // 每帧缓存插值后的真实位置,并同步追加轨迹点。
346
+ modelEntity.currentPosition = Cesium.Cartesian3.clone(tickPosition, modelEntity.currentPosition)
347
+ moveDroneTrail({
348
+ pointId,
349
+ newPosition: modelEntity.currentPosition,
350
+ mapId
351
+ })
352
+ }
353
+
354
+ if (!isFinished) return
355
+
356
+ modelEntity.currentPosition = Cesium.Cartesian3.clone(targetPosition, modelEntity.currentPosition)
357
+ modelEntity.isFlying = false
358
+
359
+ // 到达目标后追加远期采样点,让实体在终点保持不动。
360
+ const farFutureTime = Cesium.JulianDate.addSeconds(flightEndTime, DEFAULT_FAR_FUTURE_SECONDS, new Cesium.JulianDate())
361
+ modelEntity.positionProperty.addSample(farFutureTime, modelEntity.currentPosition)
362
+
363
+ clock.onTick.removeEventListener(flightTickListener)
364
+ modelEntity.flightEndListener = null
365
+ }
366
+
367
+ modelEntity.flightEndListener = flightTickListener
368
+ map.clock.onTick.addEventListener(flightTickListener)
297
369
 
298
370
  return modelEntity
299
371
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "huweili-cesium",
3
- "version": "1.2.70",
3
+ "version": "1.2.71",
4
4
  "description": "基于 Cesium 的地图工具库(无人机态势、轨迹、围栏、工具栏等)",
5
5
  "type": "module",
6
6
  "main": "./index.js",