From a8b8b40b8b849def3a02cd3d655bdfd449d1c44f Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Sat, 31 Jan 2026 17:21:50 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'
---
applications/drone-command/src/components/map-container/device-map-container.vue | 218 ++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 161 insertions(+), 57 deletions(-)
diff --git a/applications/drone-command/src/components/map-container/device-map-container.vue b/applications/drone-command/src/components/map-container/device-map-container.vue
index 829f2e9..650d950 100644
--- a/applications/drone-command/src/components/map-container/device-map-container.vue
+++ b/applications/drone-command/src/components/map-container/device-map-container.vue
@@ -77,6 +77,7 @@
const CLUSTER_HEIGHT = 100000
const DETAIL_HEIGHT = 10000
const POLYGON_HEIGHT_M = 0.2
+const DRONE_TRACK_DURATION_S = 30 * 60
const props = defineProps({
allDevices: {
@@ -119,8 +120,12 @@
let droneTrackBillboardCollection = null
let droneTrackPolylineCollection = null
let droneTrackTickHandler = null
+let droneTrackRafId = null
let droneTrackStartTime = null
+let droneTrackAnimStartAt = 0
+let droneTrackLastTickAt = 0
let droneTrackRuntime = []
+let droneTrackSource = null
let mapReadyHandled = false
let favoritePulseRafId = null
let favoritePulseStartAt = 0
@@ -223,16 +228,32 @@
offset: Math.random() * PULSE_DURATION_S,
entity: null,
}
+ const getPulseHeight = () => {
+ const pos = getBillboardPosition(billboard)
+ if (!pos) return 0
+ const carto = Cesium.Cartographic.fromCartesian(pos)
+ return carto?.height ?? 0
+ }
pulse.entity = viewer.entities.add({
- position: new Cesium.CallbackProperty(() => billboard.position, false),
+ position: new Cesium.CallbackProperty(() => getBillboardPosition(billboard), false),
ellipse: {
semiMajorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false),
semiMinorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false),
material,
- heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ height: new Cesium.CallbackProperty(() => getPulseHeight(), false),
+ heightReference: Cesium.HeightReference.NONE,
},
})
favoritePulseEntities.push(pulse)
+}
+
+const getBillboardPosition = billboard => {
+ if (!viewer || !billboard) return null
+ const position = billboard.position
+ if (position?.getValue) {
+ return position.getValue(viewer.clock.currentTime)
+ }
+ return position
}
const getDevicePosition = item => {
@@ -411,6 +432,7 @@
const setDroneVisibility = visible => {
if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible
if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible
+ if (droneTrackSource) droneTrackSource.show = visible
if (favoritePulseEntities.length) {
favoritePulseEntities.forEach(pulse => {
if (pulse?.entity) pulse.entity.show = visible
@@ -537,6 +559,10 @@
}
droneTrackPolylineCollection = null
}
+ if (droneTrackSource) {
+ viewer.dataSources.remove(droneTrackSource)
+ droneTrackSource = null
+ }
droneTrackRuntime = []
if (favoritePulseEntities.length) {
favoritePulseEntities.forEach(pulse => {
@@ -570,44 +596,96 @@
}
}
+const ensureDroneTrackSource = () => {
+ if (!viewer) return
+ if (!droneTrackSource) {
+ droneTrackSource = new Cesium.CustomDataSource('droneTrackSource')
+ viewer.dataSources.add(droneTrackSource)
+ }
+}
+
+const setupDroneTrackClock = () => {
+ if (!viewer) return
+ const startTime = Cesium.JulianDate.now()
+ const stopTime = Cesium.JulianDate.addSeconds(startTime, DRONE_TRACK_DURATION_S, new Cesium.JulianDate())
+ viewer.clock.startTime = startTime
+ viewer.clock.stopTime = stopTime
+ viewer.clock.currentTime = startTime
+ viewer.clock.multiplier = 1
+ viewer.clock.clockStep = Cesium.ClockStep.SYSTEM_CLOCK_MULTIPLIER
+ viewer.clock.clockRange = Cesium.ClockRange.CLAMPED
+}
+
+const updateDroneTrackPositions = elapsed => {
+ if (!viewer || !viewer.scene || !droneTrackRuntime.length) return
+ droneTrackRuntime.forEach(track => {
+ if (!track?.billboard || track.billboard.isDestroyed?.()) return
+ if (!track?.polyline) return
+ if (!track.positions || track.positions.length < 2) return
+ const duration = track.duration
+ if (duration <= 0) return
+ const t = Math.min(Math.max(elapsed, 0), duration)
+ const seg = Math.min(track.positions.length - 2, Math.floor(t / track.segmentDuration))
+ const ratio = (t - seg * track.segmentDuration) / track.segmentDuration
+ const isEntityPosition = typeof track.billboard?.position?.getValue === 'function'
+ const pos = isEntityPosition
+ ? getBillboardPosition(track.billboard)
+ : Cesium.Cartesian3.lerp(
+ track.positions[seg],
+ track.positions[seg + 1],
+ ratio,
+ new Cesium.Cartesian3()
+ )
+ if (!pos) return
+ if (!isEntityPosition) {
+ track.billboard.position = pos
+ }
+ const pathCount = Math.min(track.positions.length - 1, Math.floor(t / track.segmentDuration))
+ const pathPositions = track.positions.slice(0, pathCount + 1)
+ track.polyline.positions = [...pathPositions, pos]
+ })
+}
+
const startDroneTrackAnimation = () => {
if (!viewer) return
stopDroneTrackAnimation()
- droneTrackStartTime = Cesium.JulianDate.now()
- droneTrackTickHandler = clock => {
- if (!viewer || viewer.isDestroyed?.() || !viewer.scene) {
- stopDroneTrackAnimation()
+ droneTrackStartTime = viewer.clock.startTime
+ viewer.clock.shouldAnimate = true
+ droneTrackAnimStartAt = performance.now()
+ droneTrackLastTickAt = droneTrackAnimStartAt
+ const renderTick = now => {
+ if (!viewer || viewer.isDestroyed?.()) {
+ droneTrackRafId = null
return
}
- if (!droneTrackRuntime.length) return
- const current = clock.currentTime
- const elapsed = Cesium.JulianDate.secondsDifference(current, droneTrackStartTime)
- droneTrackRuntime.forEach(track => {
- if (!track?.billboard || track.billboard.isDestroyed?.()) return
- if (!track.billboard._scene) return
- if (!track.positions || track.positions.length < 2) return
- const duration = track.duration
- if (duration <= 0) return
- const t = ((elapsed % duration) + duration) % duration
- const seg = Math.min(track.positions.length - 2, Math.floor(t / track.segmentDuration))
- const ratio = (t - seg * track.segmentDuration) / track.segmentDuration
- const pos = Cesium.Cartesian3.lerp(
- track.positions[seg],
- track.positions[seg + 1],
- ratio,
- new Cesium.Cartesian3()
+ const deltaSeconds = Math.max(0, (now - droneTrackLastTickAt) / 1000)
+ droneTrackLastTickAt = now
+ if (viewer.clock?.currentTime && viewer.clock?.stopTime) {
+ const nextTime = Cesium.JulianDate.addSeconds(
+ viewer.clock.currentTime,
+ deltaSeconds * (viewer.clock.multiplier || 1),
+ new Cesium.JulianDate()
)
- track.billboard.position = pos
- })
+ if (Cesium.JulianDate.greaterThan(nextTime, viewer.clock.stopTime)) {
+ viewer.clock.currentTime = viewer.clock.stopTime
+ } else {
+ viewer.clock.currentTime = nextTime
+ }
+ }
+ const elapsed = Cesium.JulianDate.secondsDifference(viewer.clock.currentTime, droneTrackStartTime)
+ updateDroneTrackPositions(elapsed)
+ viewer.scene?.requestRender?.()
+ droneTrackRafId = requestAnimationFrame(renderTick)
}
- viewer.clock.onTick.addEventListener(droneTrackTickHandler)
- viewer.clock.shouldAnimate = true
+ droneTrackRafId = requestAnimationFrame(renderTick)
}
const stopDroneTrackAnimation = () => {
- if (!viewer || !droneTrackTickHandler) return
- viewer.clock.onTick.removeEventListener(droneTrackTickHandler)
droneTrackTickHandler = null
+ if (droneTrackRafId) {
+ cancelAnimationFrame(droneTrackRafId)
+ droneTrackRafId = null
+ }
}
const startFavoritePulseAnimation = () => {
@@ -637,10 +715,11 @@
clearDroneTrackEntities()
if (!list?.length) return
ensureDroneTrackCollections()
+ ensureDroneTrackSource()
+ setupDroneTrackClock()
droneTrackBillboardCollection.show = detailVisible.value
droneTrackPolylineCollection.show = detailVisible.value
droneTrackRuntime = []
- const segmentDuration = 6
const baseTrackColor = Cesium.Color.fromCssColorString('red')
; (list || []).forEach((item, trackIndex) => {
const position = getDevicePosition(item)
@@ -650,6 +729,7 @@
const positions = points.map(point =>
Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
)
+ const segmentDuration = DRONE_TRACK_DURATION_S / Math.max(positions.length - 1, 1)
let trackMaterial = createDroneTrackMaterial({
color: baseTrackColor,
speed: 4.5,
@@ -660,43 +740,67 @@
if (!trackMaterial) {
trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
}
- droneTrackPolylineCollection.add({
- positions,
+ const polyline = droneTrackPolylineCollection.add({
+ positions: [positions[0]],
width: 3,
material: trackMaterial,
})
const droneId = `drone-alarm-${item?.alarmRecordId ?? item?.id ?? trackIndex}`
- const billboard = droneTrackBillboardCollection.add({
- position: positions[positions.length - 1],
- image: droneIcon,
- width: 36,
- height: 36,
- verticalOrigin: Cesium.VerticalOrigin.CENTER,
- disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ const startTime = viewer.clock.startTime
+ const stopTime = viewer.clock.stopTime
+ const positionProperty = new Cesium.SampledPositionProperty()
+ positions.forEach((pos, index) => {
+ const sampleTime = Cesium.JulianDate.addSeconds(
+ startTime,
+ index * segmentDuration,
+ new Cesium.JulianDate()
+ )
+ positionProperty.addSample(sampleTime, pos)
})
- billboard.id = droneId
+ positionProperty.setInterpolationOptions({
+ interpolationDegree: 1,
+ interpolationAlgorithm: Cesium.LinearApproximation,
+ })
+ const entity = droneTrackSource.entities.add({
+ id: droneId,
+ availability: new Cesium.TimeIntervalCollection([
+ new Cesium.TimeInterval({
+ start: startTime,
+ stop: stopTime,
+ }),
+ ]),
+ position: positionProperty,
+ orientation: new Cesium.VelocityOrientationProperty(positionProperty),
+ billboard: {
+ image: droneIcon,
+ width: 36,
+ height: 36,
+ verticalOrigin: Cesium.VerticalOrigin.CENTER,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ })
const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
- dronePickMap.set(droneId, {
- data: {
- ...item,
- flightHeightM: item.flightHeightM ?? points[0].height,
- flightSpeedMs: item.flightSpeedMs ?? speedMs,
- longitude: item.longitude ?? points[0].longitude,
- latitude: item.latitude ?? points[0].latitude,
- },
- billboard,
- })
- if (isFavorited(item)) {
- createFavoritePulseEntity(billboard)
- }
+ dronePickMap.set(droneId, {
+ data: {
+ ...item,
+ flightHeightM: item.flightHeightM ?? points[0].height,
+ flightSpeedMs: item.flightSpeedMs ?? speedMs,
+ longitude: item.longitude ?? points[0].longitude,
+ latitude: item.latitude ?? points[0].latitude,
+ },
+ billboard: entity,
+ })
+ if (isFavorited(item)) {
+ createFavoritePulseEntity(entity)
+ }
droneTrackRuntime.push({
positions,
- billboard,
+ polyline,
+ billboard: entity,
segmentDuration,
- duration: (points.length - 1) * segmentDuration,
+ duration: DRONE_TRACK_DURATION_S,
})
})
- viewer.clock.multiplier = 1
startDroneTrackAnimation()
startFavoritePulseAnimation()
reorderCockpitPrimitives()
@@ -1045,7 +1149,7 @@
const updatePopupPosition = () => {
if (!viewer || !selectedDeviceBillboard) return
- const cartesian = selectedDeviceBillboard.position
+ const cartesian = getBillboardPosition(selectedDeviceBillboard)
if (!cartesian) return
const screenPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian)
if (!screenPosition) return
--
Gitblit v1.9.3