吉安感知网项目-前端
shuishen
2026-01-31 8a94964353200eb29c320513b85681e602aed283
feat:无人机轨迹增加动画
1 files modified
209 ■■■■ changed files
applications/drone-command/src/components/map-container/device-map-container.vue 209 ●●●● patch | view | raw | blame | history
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 = 10 * 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
@@ -224,7 +229,7 @@
        entity: null,
    }
    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),
@@ -233,6 +238,15 @@
        },
    })
    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 +425,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 +552,10 @@
        }
        droneTrackPolylineCollection = null
    }
    if (droneTrackSource) {
        viewer.dataSources.remove(droneTrackSource)
        droneTrackSource = null
    }
    droneTrackRuntime = []
    if (favoritePulseEntities.length) {
        favoritePulseEntities.forEach(pulse => {
@@ -570,44 +589,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 +708,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 +722,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 +733,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 +1142,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