吉安感知网项目-前端
shuishen
2026-01-30 d23783800ae500572d713a530d98443f4e4a831c
feat:数据驾驶舱调整相关
3 files modified
1 files added
306 ■■■■ changed files
applications/drone-command/src/components/map-container/device-map-container.vue 68 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/deviceRange.js 117 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue 117 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/index.vue 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -61,13 +61,12 @@
import DevicePopup from './components/DevicePopup.vue'
import DronePopup from './components/DronePopup.vue'
import {
    RING_STYLES,
    addDeviceRings,
    createDroneTrackMaterial,
    createRadialGradientMaterial,
    getTexturedVertexFormat,
} from './device-map-materials'
import { getPointPositionsHeight } from '@/utils/cesium/mapUtil'
import { createDeviceRangePrimitiveWithHeight } from '@/utils/cesium/deviceRange'
import dzIcon from '@/assets/images/dataCockpit/map/dz-map-layer.png'
import yxIcon from '@/assets/images/dataCockpit/map/yx-map-layer.png'
@@ -248,6 +247,7 @@
    if (!Number.isFinite(range) || range <= 0) return null
    return range
}
const ensureCockpitPrimitiveLayer = () => {
    if (!viewer) return
@@ -697,7 +697,7 @@
    reorderCockpitPrimitives()
}
const renderDeviceEntities = devices => {
const renderDeviceEntities = async devices => {
    if (!viewer) return
    ensureCockpitPrimitiveLayer()
    ensureDeviceCollections()
@@ -712,14 +712,14 @@
    }
    devicePickMap.clear()
    deviceBillboardCollection.show = detailVisible.value
    const ringFillInstancesByStyle = RING_STYLES.map(() => [])
    const ringOutlineInstancesByStyle = RING_STYLES.map(() => [])
    const deviceEntries = []
    const devicePositions = []
    devices.forEach((item, index) => {
        const position = getDevicePosition(item)
        if (!position) return
        const entityId = `online-device-${item.id ?? index}-${index}`
        const rangeMeters = getDeviceRange(item)
        addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters)
        devicePositions.push({ lng: position.longitude, lat: position.latitude })
        const billboard = deviceBillboardCollection.add({
            position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
            image: equipmentIcon,
@@ -729,49 +729,31 @@
        })
        billboard.id = entityId
        devicePickMap.set(entityId, { data: item, billboard })
        deviceEntries.push({ position, rangeMeters, billboard })
    })
    if (selectedTargetType.value === 'device' && selectedDeviceBillboard && !devicePickMap.has(selectedDeviceBillboard.id)) {
        closePopup()
    }
    ringFillInstancesByStyle.forEach((instances, index) => {
        if (!instances.length) return
        const ring = RING_STYLES[index]
        const alpha = ring.alpha ?? 0.64
        const innerRatio =
            typeof ring.innerRatio === 'number'
                ? ring.innerRatio
                : ring.inner && ring.outer
                    ? Math.min(Math.max(ring.inner / ring.outer, 0), 0.9)
                    : 0
        const material = createRadialGradientMaterial(
            Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(alpha),
            Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(alpha),
            {
                gamma: 1.7,
                innerCutoff: innerRatio,
    if (deviceEntries.length) {
        const heights = await getPointPositionsHeight(devicePositions, viewer)
        deviceEntries.forEach((entry, index) => {
            const height = Number(heights?.[index]?.ASL)
            const centerHeight = Number.isFinite(height) ? height : 0
            if (entry.billboard) {
                entry.billboard.position = Cesium.Cartesian3.fromDegrees(
                    entry.position.longitude,
                    entry.position.latitude,
                    centerHeight
                )
            }
        )
        const primitive = new Cesium.GroundPrimitive({
            geometryInstances: instances,
            appearance: new Cesium.MaterialAppearance({
                material,
                translucent: true,
            }),
            if (!Number.isFinite(entry.rangeMeters) || entry.rangeMeters <= 0) return
            const primitive = createDeviceRangePrimitiveWithHeight(entry.position, entry.rangeMeters, centerHeight)
            if (!primitive) return
            primitive.show = detailVisible.value
            addCockpitPrimitive(primitive)
            deviceRingFillPrimitives.push(primitive)
        })
        primitive.show = detailVisible.value
        addCockpitPrimitive(primitive)
        deviceRingFillPrimitives.push(primitive)
    })
    ringOutlineInstancesByStyle.forEach(instances => {
        if (!instances.length) return
        const primitive = new Cesium.GroundPolylinePrimitive({
            geometryInstances: instances,
            appearance: new Cesium.PolylineColorAppearance(),
        })
        primitive.show = detailVisible.value
        addCockpitPrimitive(primitive)
        deviceRingOutlinePrimitives.push(primitive)
    })
    }
    reorderCockpitPrimitives()
}
applications/drone-command/src/utils/cesium/deviceRange.js
New file
@@ -0,0 +1,117 @@
import * as Cesium from 'cesium'
import { getPointPositionsHeight } from './mapUtil'
const DEVICE_RANGE_MATERIAL_TYPE = 'DeviceRangeEllipsoid'
let deviceRangeMaterialRegistered = false
export const registerDeviceRangeMaterial = () => {
    if (deviceRangeMaterialRegistered || !Cesium?.Material) return
    deviceRangeMaterialRegistered = true
    Cesium.Material._materialCache.addMaterial(DEVICE_RANGE_MATERIAL_TYPE, {
        fabric: {
            type: DEVICE_RANGE_MATERIAL_TYPE,
            uniforms: {
                color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
                speed: 3.0,
                globalAlpha: 0.9
            },
            source: `
                uniform vec4 color;
                uniform float speed;
                uniform float globalAlpha;
                czm_material czm_getMaterial(czm_materialInput materialInput)
                {
                czm_material material = czm_getDefaultMaterial(materialInput);
                vec2 st = materialInput.st;
                float time = fract(czm_frameNumber * speed / 1000.0);
                if (st.t < 0.5) {
                    discard;
                }
                float alphaMars3D = abs(smoothstep(0.5, 1.0, fract(-st.t - time)));
                alphaMars3D += 0.1;
                material.alpha = alphaMars3D * globalAlpha;
                material.diffuse = color.rgb;
                return material;
                }
            `,
        },
        translucent: () => true,
    })
}
export const createDeviceRangeMaterial = () => {
    registerDeviceRangeMaterial()
    return Cesium.Material.fromType(DEVICE_RANGE_MATERIAL_TYPE, {
        color: Cesium.Color.fromBytes(0, 255, 255, 155),
    })
}
export const getDeviceLngLat = device => {
    const lngRaw = device?.longitude ?? device?.lng ?? device?.lon
    const latRaw = device?.latitude ?? device?.lat
    const lng = Number(lngRaw)
    const lat = Number(latRaw)
    if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null
    return { lng, lat }
}
export const normalizeDeviceRange = value => {
    const range = Number(value)
    if (!Number.isFinite(range) || range <= 0) return 2000
    return range
}
export const resolveDeviceTerrainHeight = async (viewer, position) => {
    if (!viewer || !position) return 0
    const lng = position.lng ?? position.longitude
    const lat = position.lat ?? position.latitude
    if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return 0
    try {
        const results = await getPointPositionsHeight([{ lng: Number(lng), lat: Number(lat) }], viewer)
        const height = Number(results?.[0]?.ASL)
        return Number.isFinite(height) ? height : 0
    } catch (error) {
        return 0
    }
}
export const createDeviceRangePrimitiveWithHeight = (position, rangeMeters, height, material = null) => {
    if (!position || !Number.isFinite(rangeMeters)) return null
    const lng = position.lng ?? position.longitude
    const lat = position.lat ?? position.latitude
    if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
    const center = Cesium.Cartesian3.fromDegrees(Number(lng), Number(lat), Number(height) || 0)
    const radii = new Cesium.Cartesian3(rangeMeters, rangeMeters, rangeMeters)
    const geometry = new Cesium.EllipsoidGeometry({
        radii,
        vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED?.vertexFormat
            || Cesium.VertexFormat.POSITION_AND_NORMAL,
    })
    const instance = new Cesium.GeometryInstance({
        geometry,
        modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(center),
    })
    return new Cesium.Primitive({
        geometryInstances: instance,
        appearance: new Cesium.MaterialAppearance({
            material: material || createDeviceRangeMaterial(),
            closed: true,
            translucent: true,
            flat: true,
            faceForward: true,
        }),
        asynchronous: false
    })
}
export const createDeviceRangePrimitive = async (viewer, position, rangeMeters, material = null) => {
    if (!viewer) return null
    const height = await resolveDeviceTerrainHeight(viewer, position)
    return createDeviceRangePrimitiveWithHeight(position, rangeMeters, height, material)
}
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -234,7 +234,13 @@
import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import { DrawManager, EditManager, buildEllipsePositions } from '@/utils/cesium/shapeTools'
import { cartesian3Convert, getPointPositionsHeight } from '@/utils/cesium/mapUtil'
import { cartesian3Convert } from '@/utils/cesium/mapUtil'
import {
    createDeviceRangePrimitive,
    getDeviceLngLat,
    normalizeDeviceRange,
    resolveDeviceTerrainHeight,
} from '@/utils/cesium/deviceRange'
import * as Cesium from 'cesium'
import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
@@ -294,7 +300,6 @@
let viewEntity
let shapeDisplaySource
const deviceRangePrimitiveMap = new Map()
let deviceRangeMaterialRegistered = false
const activeToolMode = ref('')
const rules = {
@@ -455,42 +460,6 @@
    return shapeDisplaySource
}
function registerDeviceRangeMaterial () {
    if (deviceRangeMaterialRegistered || !Cesium?.Material) return
    deviceRangeMaterialRegistered = true
    Cesium.Material._materialCache.addMaterial('DeviceRangeEllipsoid', {
        fabric: {
            type: 'DeviceRangeEllipsoid',
            uniforms: {
                color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
                speed: 3.0,
            },
            source: `
                uniform vec4 color;
                uniform float speed;
                czm_material czm_getMaterial(czm_materialInput materialInput){
                    czm_material material = czm_getDefaultMaterial(materialInput);
                    vec2 st = materialInput.st;
                    float time = fract(czm_frameNumber * speed / 1000.0);
                    float alpha = abs(smoothstep(0.5,1.,fract( -st.t - time)));
                    alpha += .1;
                    material.alpha = alpha;
                    material.diffuse = color.rgb;
                    return material;
                }
            `,
        },
        translucent: () => true,
    })
}
function createDeviceRangeMaterial () {
    registerDeviceRangeMaterial()
    return Cesium.Material.fromType('DeviceRangeEllipsoid', {
        color: Cesium.Color.fromBytes(0, 255, 255, 155),
    })
}
function clearDeviceRangePrimitives () {
    if (!viewer || !deviceRangePrimitiveMap.size) return
    for (const primitive of deviceRangePrimitiveMap.values()) {
@@ -499,67 +468,13 @@
    deviceRangePrimitiveMap.clear()
}
function normalizeDeviceRange (value) {
    const range = Number(value)
    if (!Number.isFinite(range) || range <= 0) return 2000
    return range
}
function getDeviceLngLat (device) {
    const lngRaw = device?.longitude ?? device?.lng ?? device?.lon
    const latRaw = device?.latitude ?? device?.lat
    const lng = Number(lngRaw)
    const lat = Number(latRaw)
    if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null
    return { lng, lat }
}
async function resolveDeviceTerrainHeight (position) {
    if (!viewer) return 0
    try {
        const results = await getPointPositionsHeight([{ lng: position.lng, lat: position.lat }], viewer)
        const height = Number(results?.[0]?.ASL)
        return Number.isFinite(height) ? height : 0
    } catch (error) {
        return 0
    }
}
async function createDeviceRangePrimitive (device) {
    if (!viewer) return null
    if (!device) return null
    const position = getDeviceLngLat(device)
    if (!position) return null
    const rangeMeters = normalizeDeviceRange(device.effectiveRangeKm)
    const centerHeight = await resolveDeviceTerrainHeight(position)
    const center = Cesium.Cartesian3.fromDegrees(position.lng, position.lat, centerHeight)
    const radii = new Cesium.Cartesian3(rangeMeters, rangeMeters, rangeMeters)
    const geometry = new Cesium.EllipsoidGeometry({
        radii,
        vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED?.vertexFormat
            || Cesium.VertexFormat.POSITION_AND_NORMAL,
    })
    const instance = new Cesium.GeometryInstance({
        geometry,
        modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(center),
    })
    return new Cesium.Primitive({
        geometryInstances: instance,
        appearance: new Cesium.MaterialAppearance({
            material: createDeviceRangeMaterial(),
            closed: true,
            translucent: true,
            flat: true,
            faceForward: true,
        }),
        asynchronous: false
    })
}
async function renderDeviceRangeSingle (device) {
    if (!viewer) return
    clearDeviceRangePrimitives()
    const primitive = await createDeviceRangePrimitive(device)
    const position = getDeviceLngLat(device)
    if (!position) return
    const rangeMeters = normalizeDeviceRange(device.effectiveRangeKm)
    const primitive = await createDeviceRangePrimitive(viewer, position, rangeMeters)
    if (!primitive) return
    deviceRangePrimitiveMap.set(String(device?.id ?? Date.now()), primitive)
    viewer.scene.primitives.add(primitive)
@@ -582,7 +497,10 @@
    if (!pendingRows.length) return
    const primitives = await Promise.all(
        pendingRows.map(async row => {
            const primitive = await createDeviceRangePrimitive(row)
            const position = getDeviceLngLat(row)
            if (!position) return { row, primitive: null }
            const rangeMeters = normalizeDeviceRange(row.effectiveRangeKm)
            const primitive = await createDeviceRangePrimitive(viewer, position, rangeMeters)
            return { row, primitive }
        })
    )
@@ -598,8 +516,9 @@
    if (!viewer) return
    const position = getDeviceLngLat(device)
    if (!position) return
    const height = await resolveDeviceTerrainHeight(position)
    const destination = Cesium.Cartesian3.fromDegrees(position.lng, position.lat, height + device.effectiveRangeKm * 3)
    const height = await resolveDeviceTerrainHeight(viewer, position)
    const rangeMeters = normalizeDeviceRange(device?.effectiveRangeKm)
    const destination = Cesium.Cartesian3.fromDegrees(position.lng, position.lat, height + rangeMeters * 3)
    viewer.camera.flyTo({
        destination,
        orientation: {
applications/drone-command/src/views/areaManage/partition/index.vue
@@ -30,7 +30,7 @@
                    <el-table-column type="selection" width="46" />
                    <el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
                    <el-table-column prop="areaName" show-overflow-tooltip width="150" label="区域名称" />
                    <el-table-column prop="areaTypeKeys" show-overflow-tooltip width="120" label="区域类型">
                    <el-table-column prop="areaTypeKeys" show-overflow-tooltip width="196" label="区域类型">
                        <template v-slot="{ row }">
                            {{ getDictLabel(row.areaTypeKeys, dictObj.areaType) }}
                        </template>
@@ -47,7 +47,7 @@
                        </template>
                    </el-table-column>
                    <el-table-column prop="policeStationName" show-overflow-tooltip label="关联派出所" />
                    <el-table-column show-overflow-tooltip label="可飞行时间段">
                    <el-table-column show-overflow-tooltip width="300" label="可飞行时间段">
                        <template v-slot="{ row }">
                            {{ formatFlyDate(row) }}
                        </template>