吉安感知网项目-前端
罗广辉
2026-01-31 a8b8b40b8b849def3a02cd3d655bdfd449d1c44f
Merge remote-tracking branch 'origin/master'
1 files modified
164 ■■■■ changed files
applications/drone-command/src/components/map-container/device-map-container.vue 164 ●●●● 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 = 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 startDroneTrackAnimation = () => {
const ensureDroneTrackSource = () => {
    if (!viewer) return
    stopDroneTrackAnimation()
    droneTrackStartTime = Cesium.JulianDate.now()
    droneTrackTickHandler = clock => {
        if (!viewer || viewer.isDestroyed?.() || !viewer.scene) {
            stopDroneTrackAnimation()
            return
    if (!droneTrackSource) {
        droneTrackSource = new Cesium.CustomDataSource('droneTrackSource')
        viewer.dataSources.add(droneTrackSource)
        }
        if (!droneTrackRuntime.length) return
        const current = clock.currentTime
        const elapsed = Cesium.JulianDate.secondsDifference(current, droneTrackStartTime)
}
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.billboard._scene) return
        if (!track?.polyline) return
            if (!track.positions || track.positions.length < 2) return
            const duration = track.duration
            if (duration <= 0) return
            const t = ((elapsed % duration) + duration) % duration
        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 pos = Cesium.Cartesian3.lerp(
        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]
        })
    }
    viewer.clock.onTick.addEventListener(droneTrackTickHandler)
const startDroneTrackAnimation = () => {
    if (!viewer) return
    stopDroneTrackAnimation()
    droneTrackStartTime = viewer.clock.startTime
    viewer.clock.shouldAnimate = true
    droneTrackAnimStartAt = performance.now()
    droneTrackLastTickAt = droneTrackAnimStartAt
    const renderTick = now => {
        if (!viewer || viewer.isDestroyed?.()) {
            droneTrackRafId = null
            return
        }
        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()
            )
            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)
    }
    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,21 +740,45 @@
            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],
            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)
            })
            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,
                },
            })
            billboard.id = droneId
            const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
        dronePickMap.set(droneId, {
            data: {
@@ -684,19 +788,19 @@
                longitude: item.longitude ?? points[0].longitude,
                latitude: item.latitude ?? points[0].latitude,
            },
            billboard,
                billboard: entity,
        })
        if (isFavorited(item)) {
            createFavoritePulseEntity(billboard)
                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