吉安感知网项目-前端
罗广辉
2026-01-27 8a7fb9a519cae32d38ba66058d243aa7e6b6aeb2
applications/drone-command/src/components/map-container/device-map-container.vue
@@ -2,8 +2,9 @@
   <div class="map-shell">
      <CommonCesiumMap ref="mapRef" class="command-cesium map-container" :dom-id="props.containerId" :active="true"
         :flat-mode="false" :terrain="true" :layer-mode="4" :contour="false" :boundary="false"
         :show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true" :cluster-height="100000"
         @ready="handleMapReady" @stage-change="handleStageChange" />
         :show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true"
         :cluster-height="CLUSTER_HEIGHT" :detail-height="DETAIL_HEIGHT" @ready="handleMapReady"
         @stage-change="handleStageChange" />
      <div v-if="props.showLayerControl" class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
         <div class="layer-control-wrap" ref="layerWrapRef">
            <div class="layer-control" @click="toggleLayerPanel">
@@ -63,6 +64,9 @@
   createRadialGradientMaterial,
   getTexturedVertexFormat,
} from './device-map-materials'
const CLUSTER_HEIGHT = 100000
const DETAIL_HEIGHT = 10000
const props = defineProps({
   onlineDevices: {
@@ -157,6 +161,14 @@
   const latitude = Number(latitudeRaw)
   if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) return null
   return { longitude, latitude }
}
const getDeviceRange = item => {
   if (!item) return null
   const rawRange = item.effectiveRangeKm ?? item.range ?? item.coverRadiusM
   const range = Number(rawRange)
   if (!Number.isFinite(range) || range <= 0) return null
   return range
}
const ensureCockpitPrimitiveLayer = () => {
@@ -305,6 +317,20 @@
   if (aggregationSource) aggregationSource.show = visible
}
const setDroneVisibility = visible => {
   if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible
   if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible
   if (!visible && selectedTargetType.value === 'drone') {
      closePopup()
   }
}
const getStageByHeight = height => {
   if (height == null) return 'detail'
   if (height >= CLUSTER_HEIGHT) return 'cluster'
   if (height <= DETAIL_HEIGHT) return 'detail'
   return 'mid'
}
const ensureCountyCenterMap = () => {
   if (countyCenterMap.size) return
   const geojson = JSON.parse(jaGeojsonRaw)
@@ -316,24 +342,44 @@
   })
}
const buildSimulatedTrackPoints = base => {
   ensureCountyCenterMap()
   const anchor = base || { longitude: 116.397, latitude: 39.908 }
   const offsets = [
      [0, 0, 120],
      [0.02, 0.012, 150],
      [0.04, 0.006, 180],
      [0.06, -0.008, 200],
      [0.03, -0.02, 160],
      [0.0, -0.015, 140],
      [-0.02, -0.005, 120],
      [-0.01, 0.01, 130],
const buildSimulatedTrackPoints = () => {
   return [
      {
         longitude: 114.963191,
         latitude: 27.136716,
         height: 120
      },
      {
         longitude:114.957308,
         latitude: 27.138452,
         height: 120
      },
      {
         longitude:114.952,
         latitude: 27.136317,
         height: 120
      },
      {
         longitude:114.949293,
         latitude: 27.133864,
         height: 120
      },
      {
         longitude:114.944666,
         latitude: 27.130526,
         height: 120
      },
      {
         longitude:114.945909,
         latitude:27.127845,
         height: 120
      },
      {
         longitude:114.962974,
         latitude:27.136242,
         height: 120
      },
   ]
   return offsets.map(item => ({
      longitude: anchor.longitude + item[0],
      latitude: anchor.latitude + item[1],
      height: item[2],
   }))
}
@@ -422,13 +468,11 @@
const renderSimulatedDroneTrack = () => {
   if (!viewer) return
   clearDroneTrackEntities()
   ensureCountyCenterMap()
   ensureDroneTrackCollections()
   droneTrackBillboardCollection.show = detailVisible.value
   droneTrackPolylineCollection.show = detailVisible.value
   droneTrackRuntime = []
   const centers = Array.from(countyCenterMap.entries())
   const baseCenters = centers.length
      ? centers.slice(0, 4)
      : [['默认区域', { longitude: 116.397, latitude: 39.908 }]]
   const baseCenters = [['默认区域', { longitude:114.958541, latitude: 27.121917 }]]
   const segmentDuration = 6
   const baseTrackColor = Cesium.Color.fromCssColorString('red')
   baseCenters.forEach(([centerName, center], trackIndex) => {
@@ -454,7 +498,7 @@
      })
      const droneId = `drone-track-${trackIndex}`
      const billboard = droneTrackBillboardCollection.add({
         position: positions[0],
         position: positions[positions.length - 1],
         image: droneIcon,
         width: 36,
         height: 36,
@@ -511,7 +555,8 @@
      const position = getDevicePosition(item)
      if (!position) return
      const entityId = `online-device-${item.id ?? index}-${index}`
      addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle)
      const rangeMeters = getDeviceRange(item)
      addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters)
      const billboard = deviceBillboardCollection.add({
         position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
         image: equipmentIcon,
@@ -528,9 +573,20 @@
   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(0.64),
         Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(0.64)
         Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(alpha),
         Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(alpha),
         {
            gamma: 1.7,
            innerCutoff: innerRatio,
         }
      )
      const primitive = new Cesium.GroundPrimitive({
         geometryInstances: instances,
@@ -629,7 +685,7 @@
const renderDefenseZones = zones => {
   if (!viewer) return
   clearDefenseZoneEntities()
   const result = buildZonePrimitives(zones, '#19D266', '#2AEDBF', '#012B11')
   const result = buildZonePrimitives(zones, '#72F3FF', '#72F3FF', '#0A7C88')
   defenseZonePrimitive = result.primitive
   defenseZoneOutlinePrimitive = result.outlinePrimitive
   if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value
@@ -640,7 +696,7 @@
const renderPartitions = zones => {
   if (!viewer) return
   clearPartitionEntities()
   const result = buildZonePrimitives(zones, '#FFCD2A', '#FFC609', '#583300')
   const result = buildZonePrimitives(zones, '#FFD772', '#FFD772', '#A86B00')
   partitionPrimitive = result.primitive
   partitionOutlinePrimitive = result.outlinePrimitive
   if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
@@ -693,7 +749,7 @@
            width: 66.73,
            height: 43,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            // disableDepthTestDistance: Number.POSITIVE_INFINITY,
         },
         label: {
            text: `${count}`,
@@ -706,7 +762,6 @@
            pixelOffset: new Cesium.Cartesian2(0, -35),
            disableDepthTestDistance: Number.POSITIVE_INFINITY,
            heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
         },
      })
   })
@@ -855,13 +910,14 @@
   const showCluster = stage === 'cluster'
   setClusterVisibility(showCluster)
   setDetailVisibility(!showCluster)
   setDroneVisibility(!showCluster)
}
const handleMapReady = ({ viewer: mapViewer }) => {
   viewer = mapViewer
   ensureCockpitPrimitiveLayer()
   const height = viewer?.camera?.positionCartographic?.height
   const stage = height != null && height >= 100000 ? 'cluster' : 'detail'
   const stage = getStageByHeight(height)
   updateStageDisplay(stage)
   renderDeviceEntities(props.onlineDevices)
   loadDefenseZones()