吉安感知网项目-前端
chenyao
2026-01-30 fb39a7bb69cc55d2e1ce61d47e0677aa40540f95
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -47,9 +47,9 @@
<script setup>
import * as Cesium from 'cesium'
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import { geomAnalysis } from '@ztzf/utils'
import { fwDefenseZonePageApi } from '@/views/areaManage/defenseZone/defenseZoneApi'
import { fwAreaDividePageApi } from '@/views/areaManage/partition/partitionApi'
import { buildEllipsePositions } from '@/utils/cesium/shapeTools'
import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
import { fwAreaDivideListApi } from '@/views/areaManage/partition/partitionApi'
import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi'
import { cockpitAggregationApi } from '@/api/dataCockpit'
import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
@@ -61,17 +61,18 @@
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'
const CLUSTER_HEIGHT = 100000
const DETAIL_HEIGHT = 10000
const POLYGON_HEIGHT_M = 0.2
const props = defineProps({
   onlineDevices: {
@@ -97,7 +98,6 @@
})
const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite'])
const DEFAULT_ZONE_PAGE_SIZE = 999
const mapRef = ref(null)
let viewer = null
@@ -108,10 +108,8 @@
let deviceBillboardCollection = null
let deviceRingOutlinePrimitives = []
let deviceRingFillPrimitives = []
let defenseZonePrimitive = null
let defenseZoneOutlinePrimitive = null
let partitionPrimitive = null
let partitionOutlinePrimitive = null
let partitionFillPrimitives = []
let partitionOutlinePrimitives = []
let aggregationSource = null
let commandPostBillboardCollection = null
let droneTrackBillboardCollection = null
@@ -250,6 +248,7 @@
   return range
}
const ensureCockpitPrimitiveLayer = () => {
   if (!viewer) return
   if (!cockpitPrimitiveLayer) {
@@ -280,10 +279,12 @@
   if (!viewer) return
   ensureCockpitPrimitiveLayer()
   cockpitPrimitiveLayer.removeAll(false)
   if (defenseZonePrimitive) cockpitPrimitiveLayer.add(defenseZonePrimitive)
   if (defenseZoneOutlinePrimitive) cockpitPrimitiveLayer.add(defenseZoneOutlinePrimitive)
   if (partitionPrimitive) cockpitPrimitiveLayer.add(partitionPrimitive)
   if (partitionOutlinePrimitive) cockpitPrimitiveLayer.add(partitionOutlinePrimitive)
   if (partitionFillPrimitives.length) {
      partitionFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
   }
   if (partitionOutlinePrimitives.length) {
      partitionOutlinePrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
   }
   if (deviceRingFillPrimitives.length) {
      deviceRingFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
   }
@@ -323,27 +324,16 @@
   }
}
const clearDefenseZoneEntities = () => {
   if (!viewer) return
   if (defenseZonePrimitive) {
      removeCockpitPrimitive(defenseZonePrimitive)
      defenseZonePrimitive = null
   }
   if (defenseZoneOutlinePrimitive) {
      removeCockpitPrimitive(defenseZoneOutlinePrimitive)
      defenseZoneOutlinePrimitive = null
   }
}
const clearPartitionEntities = () => {
   if (!viewer) return
   if (partitionPrimitive) {
      removeCockpitPrimitive(partitionPrimitive)
      partitionPrimitive = null
   if (partitionFillPrimitives.length) {
      partitionFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
      partitionFillPrimitives = []
   }
   if (partitionOutlinePrimitive) {
      removeCockpitPrimitive(partitionOutlinePrimitive)
      partitionOutlinePrimitive = null
   if (partitionOutlinePrimitives.length) {
      partitionOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
      partitionOutlinePrimitives = []
   }
}
@@ -378,10 +368,16 @@
const setDetailVisibility = visible => {
   detailVisible.value = visible
   if (defenseZonePrimitive) defenseZonePrimitive.show = visible
   if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = visible
   if (partitionPrimitive) partitionPrimitive.show = visible
   if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = visible
   if (partitionFillPrimitives.length) {
      partitionFillPrimitives.forEach(primitive => {
         primitive.show = visible
      })
   }
   if (partitionOutlinePrimitives.length) {
      partitionOutlinePrimitives.forEach(primitive => {
         primitive.show = visible
      })
   }
   if (commandPostBillboardCollection) commandPostBillboardCollection.show = visible
   if (deviceBillboardCollection) deviceBillboardCollection.show = visible
   if (deviceRingFillPrimitives.length) {
@@ -701,7 +697,7 @@
   reorderCockpitPrimitives()
}
const renderDeviceEntities = devices => {
const renderDeviceEntities = async devices => {
   if (!viewer) return
   ensureCockpitPrimitiveLayer()
   ensureDeviceCollections()
@@ -716,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,
@@ -733,161 +729,249 @@
      })
      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()
}
const getDefenseZonePositions = geom => {
   const points = geomAnalysis(geom)
   if (points.length < 3) return []
   const first = points[0]
   const last = points[points.length - 1]
   const list =
      first && last && first.longitude === last.longitude && first.latitude === last.latitude
         ? points.slice(0, -1)
         : points
   return list.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude))
const elevatePositions = async (positions, height = POLYGON_HEIGHT_M) => {
   if (!Array.isArray(positions) || !positions.length) return []
   if (!viewer) return positions
   const points = positions
      .map(pos => {
         const carto = Cesium.Cartographic.fromCartesian(pos)
         if (!carto) return null
         return {
            lng: Cesium.Math.toDegrees(carto.longitude),
            lat: Cesium.Math.toDegrees(carto.latitude),
         }
      })
      .filter(Boolean)
   if (!points.length) return positions
   const heights = await getPointPositionsHeight(points, viewer)
   return heights.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude, (item.ASL ?? 0) + height))
}
const buildZonePrimitives = (zones, lineColor, fillColor1, fillColor2) => {
   if (!viewer) return { primitive: null, outlinePrimitive: null }
   const polygonInstances = []
   const lineInstances = []
   const texturedVertexFormat = getTexturedVertexFormat()
      ; (zones || []).forEach((zone, index) => {
         if (!zone?.geom) return
         const positions = getDefenseZonePositions(zone.geom)
         if (!positions.length) return
         const polygon = new Cesium.PolygonGeometry({
            polygonHierarchy: new Cesium.PolygonHierarchy(positions),
            vertexFormat: texturedVertexFormat,
const getAreaTypeStyle = areaType => {
   return AREA_TYPE_STYLE_MAP?.[String(areaType)] || DEFAULT_AREA_STYLE
}
const buildPartitionPrimitives = async shapes => {
   if (!viewer) return { primitives: [], outlinePrimitives: [] }
   const vertexFormat = getTexturedVertexFormat()
   const groups = new Map()
   const tasks = []
      ; (shapes || []).forEach(shape => {
         if (!shape?.positions || shape.positions.length < 3) return
         const outlineColor = shape.outlineColor
         const fillColor = shape.fillColor
         const key = `${outlineColor.toCssColorString()}|${fillColor.toCssColorString()}`
         if (!groups.has(key)) {
            groups.set(key, { outlineColor, fillColor, polygonInstances: [], lineInstances: [] })
         }
         const group = groups.get(key)
         const positions = shape.positions
         tasks.push(
            elevatePositions(positions).then(elevatedPositions => {
               if (!elevatedPositions.length) return
               const polygon = new Cesium.PolygonGeometry({
                  polygonHierarchy: new Cesium.PolygonHierarchy(elevatedPositions),
                  perPositionHeight: true,
                  vertexFormat,
               })
               group.polygonInstances.push(
                  new Cesium.GeometryInstance({
                     geometry: polygon,
                  })
               )
               const linePositions =
                  elevatedPositions.length > 1
                     ? [...elevatedPositions, elevatedPositions[0]]
                     : elevatedPositions
               group.lineInstances.push(
                  new Cesium.GeometryInstance({
                     geometry: new Cesium.PolylineGeometry({
                        positions: linePositions,
                        width: 2,
                     }),
                     attributes: {
                        color: Cesium.ColorGeometryInstanceAttribute.fromColor(outlineColor),
                     },
                  })
               )
            })
         )
      })
   if (tasks.length) await Promise.all(tasks)
   const primitives = []
   const outlinePrimitives = []
   groups.forEach(group => {
      if (group.polygonInstances.length) {
         const material = createRadialGradientMaterial(group.outlineColor, group.fillColor, {
            gamma: 1.7,
            innerCutoff: 0,
         })
         polygonInstances.push(
            new Cesium.GeometryInstance({
               geometry: polygon,
            })
         )
         const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions
         lineInstances.push(
            new Cesium.GeometryInstance({
               geometry: new Cesium.GroundPolylineGeometry({
                  positions: linePositions,
                  width: 2,
               }),
               attributes: {
                  color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                     Cesium.Color.fromCssColorString(lineColor)
                  ),
               },
            })
         )
      })
   let primitive = null
   if (polygonInstances.length) {
      const baseColor1 = Cesium.Color.fromCssColorString(fillColor1)
      const baseColor2 = Cesium.Color.fromCssColorString(fillColor2)
      const material = createRadialGradientMaterial(baseColor1.withAlpha(0.64), baseColor2.withAlpha(0.64))
      primitive = new Cesium.GroundPrimitive({
         geometryInstances: polygonInstances,
         appearance: new Cesium.MaterialAppearance({
            material,
            translucent: true,
         }),
      })
      addCockpitPrimitive(primitive)
   }
   let outlinePrimitive = null
   if (lineInstances.length) {
      outlinePrimitive = new Cesium.GroundPolylinePrimitive({
         geometryInstances: lineInstances,
         appearance: new Cesium.PolylineColorAppearance(),
      })
      addCockpitPrimitive(outlinePrimitive)
   }
   return { primitive, outlinePrimitive }
         const primitive = new Cesium.Primitive({
            geometryInstances: group.polygonInstances,
            appearance: new Cesium.MaterialAppearance({
               material,
               translucent: true,
            }),
         })
         addCockpitPrimitive(primitive)
         primitives.push(primitive)
      }
      if (group.lineInstances.length) {
         const outlinePrimitive = new Cesium.Primitive({
            geometryInstances: group.lineInstances,
            appearance: new Cesium.PolylineColorAppearance(),
         })
         addCockpitPrimitive(outlinePrimitive)
         outlinePrimitives.push(outlinePrimitive)
      }
   })
   return { primitives, outlinePrimitives }
}
const renderDefenseZones = zones => {
   if (!viewer) return
   clearDefenseZoneEntities()
   const result = buildZonePrimitives(zones, '#72F3FF', '#72F3FF', '#0A7C88')
   defenseZonePrimitive = result.primitive
   defenseZoneOutlinePrimitive = result.outlinePrimitive
   if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value
   if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = detailVisible.value
   reorderCockpitPrimitives()
const parseGeomJson = geomJson => {
   if (!geomJson) return null
   if (typeof geomJson === 'object') return geomJson
   if (typeof geomJson !== 'string') return null
   const trimmed = geomJson.trim()
   if (!trimmed) return null
   try {
      return JSON.parse(trimmed)
   } catch (error) {}
   return null
}
const renderPartitions = zones => {
const normalizeShapePoint = point => {
   if (!point) return null
   const lng = point?.lng ?? point?.longitude
   const lat = point?.lat ?? point?.latitude
   const height = Number.isFinite(Number(point?.height)) ? Number(point.height) : 0
   if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
   return { lng: Number(lng), lat: Number(lat), height }
}
const buildShapePositions = (points = []) => {
   const normalized = points.map(normalizeShapePoint).filter(Boolean)
   return normalized.map(point => Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.height))
}
const getShapeDisplayPoints = shape => {
   if (Array.isArray(shape?.displayPoints) && shape.displayPoints.length) {
      return shape.displayPoints
   }
   return shape?.points || []
}
const resolvePartitionShapes = areas => {
   const shapes = []
      ; (areas || []).forEach(area => {
         const extList = Array.isArray(area?.fwAreaDivideExtList) ? area.fwAreaDivideExtList : []
         extList.forEach((item, index) => {
            const isShapePayload = item?.drawType || item?.points
            const parsed = isShapePayload ? item : parseGeomJson(item?.geomJson)
            if (!parsed) return
            const shape = {
               id: parsed?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
               drawType: parsed?.drawType ?? 'polygon',
               areaType: parsed?.areaType ?? item?.areaTypeKey ?? item?.areaType ?? '',
               points: Array.isArray(parsed?.points) ? parsed.points : [],
               displayPoints: Array.isArray(parsed?.displayPoints) ? parsed.displayPoints : null,
               meta: parsed?.meta ?? null,
            }
            if (shape.drawType === 'buffer' && shape.meta?.bufferRadii?.length && shape.meta?.center) {
               const center = shape.meta.center
               const centerCartesian = Cesium.Cartesian3.fromDegrees(
                  center.lng,
                  center.lat,
                  center.height || 0
               )
               const radii = shape.meta.bufferRadii
                  .map(radius => Number(radius))
                  .filter(radius => Number.isFinite(radius) && radius > 0)
               radii.forEach((radius, levelIndex) => {
                  const positions = buildEllipsePositions(centerCartesian, radius, radius)
               const style = BUFFER_LEVEL_STYLES[levelIndex] || BUFFER_LEVEL_STYLES[BUFFER_LEVEL_STYLES.length - 1]
                  if (positions.length >= 3) {
                     shapes.push({
                        positions,
                        fillColor: style.fill,
                        outlineColor: style.outline,
                     })
                  }
               })
               return
            }
            const positions = buildShapePositions(getShapeDisplayPoints(shape))
            if (positions.length >= 3) {
               const style = getAreaTypeStyle(shape.areaType)
               shapes.push({
                  positions,
                  fillColor: style.fill,
                  outlineColor: style.outline,
               })
            }
         })
      })
   return shapes
}
const renderPartitions = async zones => {
   if (!viewer) return
   clearPartitionEntities()
   const result = buildZonePrimitives(zones, '#FFD772', '#FFD772', '#A86B00')
   partitionPrimitive = result.primitive
   partitionOutlinePrimitive = result.outlinePrimitive
   if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
   if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = detailVisible.value
   reorderCockpitPrimitives()
}
const loadDefenseZones = async () => {
   if (!viewer) return
   try {
      const res = await fwDefenseZonePageApi({ current: 1, size: DEFAULT_ZONE_PAGE_SIZE })
      renderDefenseZones(res?.data?.data?.records ?? [])
   } catch (error) {
      renderDefenseZones([])
   const shapes = resolvePartitionShapes(zones)
   const result = await buildPartitionPrimitives(shapes)
   partitionFillPrimitives = result.primitives
   partitionOutlinePrimitives = result.outlinePrimitives
   if (partitionFillPrimitives.length) {
      partitionFillPrimitives.forEach(primitive => {
         primitive.show = detailVisible.value
      })
   }
   if (partitionOutlinePrimitives.length) {
      partitionOutlinePrimitives.forEach(primitive => {
         primitive.show = detailVisible.value
      })
   }
   reorderCockpitPrimitives()
}
const loadPartitions = async () => {
   if (!viewer) return
   try {
      const res = await fwAreaDividePageApi({ current: 1, size: DEFAULT_ZONE_PAGE_SIZE })
      renderPartitions(res?.data?.data?.records ?? [])
      const res = await fwAreaDivideListApi()
      await renderPartitions(res?.data?.data ?? [])
   } catch (error) {
      renderPartitions([])
      await renderPartitions([])
   }
}
@@ -1125,7 +1209,6 @@
   const stage = getStageByHeight(height)
   updateStageDisplay(stage)
   renderDeviceEntities(props.onlineDevices)
   loadDefenseZones()
   loadPartitions()
   loadAggregation()
   loadCommandPosts()
@@ -1153,7 +1236,6 @@
onBeforeUnmount(() => {
   document.removeEventListener('click', handleClickOutside)
   clearDeviceEntities()
   clearDefenseZoneEntities()
   clearPartitionEntities()
   clearAggregationEntities()
   clearCommandPostEntities()