From da563dc595339d42e2135f63167906d6fa20e06c Mon Sep 17 00:00:00 2001
From: 张含笑 <zhx18749296735@163.com>
Date: Fri, 23 Jan 2026 09:34:55 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/drone-command/src/components/map-container/device-map-container.vue |  580 ++++++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 444 insertions(+), 136 deletions(-)

diff --git a/applications/drone-command/src/components/map-container/device-map-container.vue b/applications/drone-command/src/components/map-container/device-map-container.vue
index d34c53a..6e584a0 100644
--- a/applications/drone-command/src/components/map-container/device-map-container.vue
+++ b/applications/drone-command/src/components/map-container/device-map-container.vue
@@ -1,4 +1,4 @@
-<template>
+<template>
 	<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"
@@ -93,6 +93,7 @@
 import { cockpitAggregationApi } from '@/api/dataCockpit'
 import layerControlIcon from '@/assets/images/dataCockpit/layerControl.png'
 import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png'
+import droneIcon from '@/assets/images/dataCockpit/map/drone.png'
 import aggregationIcon from '@/assets/images/dataCockpit/map/aggregation.png'
 import commandPostIcon from '@/assets/images/dataCockpit/map/command-post.png'
 import popupClose from '@/assets/images/dataCockpit/map/popup-close.png'
@@ -121,19 +122,29 @@
 
 const mapRef = ref(null)
 let viewer = null
-const deviceEntityIds = new Set()
-const deviceEntityMap = new Map()
-let defenseZoneSource = null
-let partitionSource = null
+let cockpitPrimitiveLayer = null
+const devicePickMap = new Map()
+let deviceBillboardCollection = null
+let deviceRingOutlinePrimitives = []
+let deviceRingFillPrimitives = []
+let defenseZonePrimitive = null
+let defenseZoneOutlinePrimitive = null
+let partitionPrimitive = null
+let partitionOutlinePrimitive = null
 let aggregationSource = null
-let commandPostSource = null
+let commandPostBillboardCollection = null
+let droneTrackBillboardCollection = null
+let droneTrackPolylineCollection = null
+let droneTrackTickHandler = null
+let droneTrackStartTime = null
+let droneTrackRuntime = []
 const detailVisible = ref(true)
 const clusterVisible = ref(false)
 const countyCenterMap = new Map()
 const showLayerPanel = ref(false)
 const layerWrapRef = ref(null)
 const selectedDevice = ref(null)
-let selectedDeviceEntity = null
+let selectedDeviceBillboard = null
 let deviceClickHandler = null
 let popupRenderHandler = null
 const layerTreeProps = {
@@ -180,23 +191,101 @@
 	return { longitude, latitude }
 }
 
+const ensureCockpitPrimitiveLayer = () => {
+	if (!viewer) return
+	if (!cockpitPrimitiveLayer) {
+		cockpitPrimitiveLayer = new Cesium.PrimitiveCollection({ destroyPrimitives: false })
+		viewer.scene.primitives.add(cockpitPrimitiveLayer)
+	}
+}
+
+const addCockpitPrimitive = primitive => {
+	if (!primitive) return
+	if (cockpitPrimitiveLayer) {
+		cockpitPrimitiveLayer.add(primitive)
+		return
+	}
+	viewer?.scene?.primitives?.add(primitive)
+}
+
+const removeCockpitPrimitive = (primitive, destroy = true) => {
+	if (!primitive) return
+	if (cockpitPrimitiveLayer) {
+		cockpitPrimitiveLayer.remove(primitive, destroy)
+		return
+	}
+	viewer?.scene?.primitives?.remove(primitive)
+}
+
+const reorderCockpitPrimitives = () => {
+	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 (deviceRingFillPrimitives.length) {
+		deviceRingFillPrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
+	}
+	if (deviceRingOutlinePrimitives.length) {
+		deviceRingOutlinePrimitives.forEach(primitive => cockpitPrimitiveLayer.add(primitive))
+	}
+	if (droneTrackPolylineCollection) cockpitPrimitiveLayer.add(droneTrackPolylineCollection)
+	if (droneTrackBillboardCollection) cockpitPrimitiveLayer.add(droneTrackBillboardCollection)
+	if (deviceBillboardCollection) cockpitPrimitiveLayer.add(deviceBillboardCollection)
+	if (commandPostBillboardCollection) cockpitPrimitiveLayer.add(commandPostBillboardCollection)
+}
+
 const clearDeviceEntities = () => {
 	if (!viewer) return
-	deviceEntityIds.forEach(id => {
-		viewer.entities.removeById(id)
-	})
-	deviceEntityIds.clear()
-	deviceEntityMap.clear()
+	if (deviceBillboardCollection) {
+		deviceBillboardCollection.removeAll()
+		removeCockpitPrimitive(deviceBillboardCollection)
+		deviceBillboardCollection = null
+	}
+	if (deviceRingFillPrimitives.length) {
+		deviceRingFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+		deviceRingFillPrimitives = []
+	}
+	if (deviceRingOutlinePrimitives.length) {
+		deviceRingOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+		deviceRingOutlinePrimitives = []
+	}
+	devicePickMap.clear()
+}
+
+const ensureDeviceCollections = () => {
+	if (!viewer) return
+	ensureCockpitPrimitiveLayer()
+	if (!deviceBillboardCollection) {
+		deviceBillboardCollection = new Cesium.BillboardCollection()
+		addCockpitPrimitive(deviceBillboardCollection)
+	}
 }
 
 const clearDefenseZoneEntities = () => {
-	if (!defenseZoneSource) return
-	defenseZoneSource.entities.removeAll()
+	if (!viewer) return
+	if (defenseZonePrimitive) {
+		removeCockpitPrimitive(defenseZonePrimitive)
+		defenseZonePrimitive = null
+	}
+	if (defenseZoneOutlinePrimitive) {
+		removeCockpitPrimitive(defenseZoneOutlinePrimitive)
+		defenseZoneOutlinePrimitive = null
+	}
 }
 
 const clearPartitionEntities = () => {
-	if (!partitionSource) return
-	partitionSource.entities.removeAll()
+	if (!viewer) return
+	if (partitionPrimitive) {
+		removeCockpitPrimitive(partitionPrimitive)
+		partitionPrimitive = null
+	}
+	if (partitionOutlinePrimitive) {
+		removeCockpitPrimitive(partitionOutlinePrimitive)
+		partitionOutlinePrimitive = null
+	}
 }
 
 const clearAggregationEntities = () => {
@@ -205,20 +294,42 @@
 }
 
 const clearCommandPostEntities = () => {
-	if (!commandPostSource) return
-	commandPostSource.entities.removeAll()
+	if (!viewer) return
+	if (commandPostBillboardCollection) {
+		commandPostBillboardCollection.removeAll()
+		removeCockpitPrimitive(commandPostBillboardCollection)
+		commandPostBillboardCollection = null
+	}
+}
+
+const ensureCommandPostCollection = () => {
+	if (!viewer) return
+	ensureCockpitPrimitiveLayer()
+	if (!commandPostBillboardCollection) {
+		commandPostBillboardCollection = new Cesium.BillboardCollection()
+		addCockpitPrimitive(commandPostBillboardCollection)
+	}
 }
 
 const setDetailVisibility = visible => {
 	detailVisible.value = visible
-	if (defenseZoneSource) defenseZoneSource.show = visible
-	if (partitionSource) partitionSource.show = visible
-	if (commandPostSource) commandPostSource.show = visible
+	if (defenseZonePrimitive) defenseZonePrimitive.show = visible
+	if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = visible
+	if (partitionPrimitive) partitionPrimitive.show = visible
+	if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = visible
+	if (commandPostBillboardCollection) commandPostBillboardCollection.show = visible
+	if (deviceBillboardCollection) deviceBillboardCollection.show = visible
+	if (deviceRingFillPrimitives.length) {
+		deviceRingFillPrimitives.forEach(primitive => {
+			primitive.show = visible
+		})
+	}
+	if (deviceRingOutlinePrimitives.length) {
+		deviceRingOutlinePrimitives.forEach(primitive => {
+			primitive.show = visible
+		})
+	}
 	if (!visible) closePopup()
-	deviceEntityIds.forEach(id => {
-		const entity = viewer?.entities?.getById(id)
-		if (entity) entity.show = visible
-	})
 }
 
 const setClusterVisibility = visible => {
@@ -237,12 +348,139 @@
 	})
 }
 
+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],
+	]
+	return offsets.map(item => ({
+		longitude: anchor.longitude + item[0],
+		latitude: anchor.latitude + item[1],
+		height: item[2],
+	}))
+}
+
+
+const clearDroneTrackEntities = () => {
+	if (!viewer) return
+	if (droneTrackBillboardCollection) {
+		droneTrackBillboardCollection.removeAll()
+		removeCockpitPrimitive(droneTrackBillboardCollection)
+		droneTrackBillboardCollection = null
+	}
+	if (droneTrackPolylineCollection) {
+		droneTrackPolylineCollection.removeAll()
+		removeCockpitPrimitive(droneTrackPolylineCollection)
+		droneTrackPolylineCollection = null
+	}
+	droneTrackRuntime = []
+	stopDroneTrackAnimation()
+}
+
+
+const ensureDroneTrackCollections = () => {
+	if (!viewer) return
+	ensureCockpitPrimitiveLayer()
+	if (!droneTrackBillboardCollection) {
+		droneTrackBillboardCollection = new Cesium.BillboardCollection()
+		addCockpitPrimitive(droneTrackBillboardCollection)
+	}
+	if (!droneTrackPolylineCollection) {
+		droneTrackPolylineCollection = new Cesium.PolylineCollection()
+		addCockpitPrimitive(droneTrackPolylineCollection)
+	}
+}
+
+const startDroneTrackAnimation = () => {
+	if (!viewer) return
+	stopDroneTrackAnimation()
+	droneTrackStartTime = Cesium.JulianDate.now()
+	droneTrackTickHandler = clock => {
+		if (!droneTrackRuntime.length) return
+		const current = clock.currentTime
+		const elapsed = Cesium.JulianDate.secondsDifference(current, droneTrackStartTime)
+		droneTrackRuntime.forEach(track => {
+			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()
+			)
+			track.billboard.position = pos
+		})
+	}
+	viewer.clock.onTick.addEventListener(droneTrackTickHandler)
+	viewer.clock.shouldAnimate = true
+}
+
+const stopDroneTrackAnimation = () => {
+	if (!viewer || !droneTrackTickHandler) return
+	viewer.clock.onTick.removeEventListener(droneTrackTickHandler)
+	droneTrackTickHandler = null
+}
+
+const renderSimulatedDroneTrack = () => {
+	if (!viewer) return
+	ensureCountyCenterMap()
+	ensureDroneTrackCollections()
+	droneTrackBillboardCollection.removeAll()
+	droneTrackPolylineCollection.removeAll()
+	droneTrackRuntime = []
+	const centers = Array.from(countyCenterMap.values())
+	const baseCenters = centers.length ? centers.slice(0, 4) : [{ longitude: 116.397, latitude: 39.908 }]
+	const segmentDuration = 6
+	baseCenters.forEach((center, trackIndex) => {
+		const points = buildSimulatedTrackPoints(center)
+		if (points.length < 2) return
+		const positions = points.map(point =>
+			Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
+		)
+		droneTrackPolylineCollection.add({
+			positions,
+			width: 2,
+			material: Cesium.Material.fromType('Color', {
+				color: Cesium.Color.fromCssColorString('#2AEDBF'),
+			}),
+		})
+		const billboard = droneTrackBillboardCollection.add({
+			position: positions[0],
+			image: droneIcon,
+			width: 36,
+			height: 36,
+			verticalOrigin: Cesium.VerticalOrigin.CENTER,
+		})
+		droneTrackRuntime.push({
+			positions,
+			billboard,
+			segmentDuration,
+			duration: (points.length - 1) * segmentDuration,
+		})
+	})
+	viewer.clock.multiplier = 1
+	startDroneTrackAnimation()
+	reorderCockpitPrimitives()
+}
 
 const RING_STYLES = [
 	{ inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] }
 ]
 
 const MATERIAL_TYPE = 'RadialGradientMaterial'
+const TEXTURED_VERTEX_FORMAT =
+	Cesium.MaterialAppearance?.MaterialSupport?.TEXTURED?.vertexFormat || Cesium.VertexFormat.POSITION_AND_ST
 let materialRegistered = false
 
 const registerRadialGradientMaterial = () => {
@@ -287,27 +525,39 @@
 	return positions
 }
 
-const addDeviceRings = (center, baseId, visible) => {
+const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle) => {
+	if (!ringFillInstancesByStyle) return
+	const centerPosition = Cesium.Cartesian3.fromDegrees(center.longitude, center.latitude, 0)
 	RING_STYLES.forEach((ring, index) => {
-		const outerPositions = buildCirclePositions(center, ring.outer)
-		const holes = ring.inner
-			? [new Cesium.PolygonHierarchy(buildCirclePositions(center, ring.inner))]
-			: []
-		const entityId = `${baseId}-ring-${index}`
-		deviceEntityIds.add(entityId)
-		registerRadialGradientMaterial()
-		const [startColor, endColor] = ring.gradient
-		const color1 = Cesium.Color.fromCssColorString(startColor).withAlpha(0.34)
-		const color2 = Cesium.Color.fromCssColorString(endColor).withAlpha(0.34)
-		const material = new RadialGradientMaterialProperty(color1, color2)
-		viewer.entities.add({
-			id: entityId,
-			show: visible,
-			polygon: {
-				hierarchy: new Cesium.PolygonHierarchy(outerPositions, holes),
-				material,
-			},
-		})
+		if (!ringFillInstancesByStyle[index]) return
+		ringFillInstancesByStyle[index].push(
+			new Cesium.GeometryInstance({
+				geometry: new Cesium.EllipseGeometry({
+					center: centerPosition,
+					semiMajorAxis: ring.outer,
+					semiMinorAxis: ring.outer,
+					vertexFormat: TEXTURED_VERTEX_FORMAT,
+				}),
+			})
+		)
+	})
+	if (!ringOutlineInstancesByStyle) return
+	RING_STYLES.forEach((ring, index) => {
+		if (!ringOutlineInstancesByStyle[index]) return
+		const positions = buildCirclePositions(center, ring.outer)
+		ringOutlineInstancesByStyle[index].push(
+			new Cesium.GeometryInstance({
+				geometry: new Cesium.GroundPolylineGeometry({
+					positions,
+					width: 2,
+				}),
+				attributes: {
+					color: Cesium.ColorGeometryInstanceAttribute.fromColor(
+						Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.8)
+					),
+				},
+			})
+		)
 	})
 }
 
@@ -348,29 +598,69 @@
 
 const renderDeviceEntities = devices => {
 	if (!viewer) return
-	clearDeviceEntities()
+	ensureCockpitPrimitiveLayer()
+	ensureDeviceCollections()
+	deviceBillboardCollection.removeAll()
+	if (deviceRingFillPrimitives.length) {
+		deviceRingFillPrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+		deviceRingFillPrimitives = []
+	}
+	if (deviceRingOutlinePrimitives.length) {
+		deviceRingOutlinePrimitives.forEach(primitive => removeCockpitPrimitive(primitive))
+		deviceRingOutlinePrimitives = []
+	}
+	devicePickMap.clear()
+	deviceBillboardCollection.show = detailVisible.value
+	const ringFillInstancesByStyle = RING_STYLES.map(() => [])
+	const ringOutlineInstancesByStyle = RING_STYLES.map(() => [])
 	devices.forEach((item, index) => {
 		const position = getDevicePosition(item)
 		if (!position) return
 		const entityId = `online-device-${item.id ?? index}-${index}`
-		deviceEntityIds.add(entityId)
-		addDeviceRings(position, entityId, detailVisible.value)
-		const entity = viewer.entities.add({
-			id: entityId,
-			show: detailVisible.value,
+		addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle)
+		const billboard = deviceBillboardCollection.add({
 			position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
-			billboard: {
-				image: equipmentIcon,
-				width: 40,
-				height: 56,
-				verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-			},
+			image: equipmentIcon,
+			width: 40,
+			height: 56,
+			verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
 		})
-		deviceEntityMap.set(entityId, { data: item, entity })
+		billboard.id = entityId
+		devicePickMap.set(entityId, { data: item, billboard })
 	})
-	if (selectedDeviceEntity && !deviceEntityMap.has(selectedDeviceEntity.id)) {
+	if (selectedDeviceBillboard && !devicePickMap.has(selectedDeviceBillboard.id)) {
 		closePopup()
 	}
+	registerRadialGradientMaterial()
+	ringFillInstancesByStyle.forEach((instances, index) => {
+		if (!instances.length) return
+		const ring = RING_STYLES[index]
+		const material = Cesium.Material.fromType(MATERIAL_TYPE, {
+			color1: Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.64),
+			color2: Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(0.64),
+		})
+		const primitive = new Cesium.GroundPrimitive({
+			geometryInstances: instances,
+			appearance: new Cesium.MaterialAppearance({
+				material,
+				translucent: true,
+			}),
+		})
+		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 => {
@@ -385,70 +675,87 @@
 	return list.map(item => Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude))
 }
 
-const renderZonePolygons = ({ zones, source, idPrefix, lineColor, fillGradient }) => {
-	if (!viewer || !source) return
+const buildZonePrimitives = (zones, lineColor, fillColor1, fillColor2) => {
+	if (!viewer) return { primitive: null, outlinePrimitive: null }
+	const polygonInstances = []
+	const lineInstances = []
 	registerRadialGradientMaterial()
-	const borderColor = Cesium.Color.fromCssColorString(lineColor)
-	const fillColorStart = Cesium.Color.fromCssColorString(fillGradient[0]).withAlpha(0.34)
-	const fillColorEnd = Cesium.Color.fromCssColorString(fillGradient[1]).withAlpha(0.34)
-	const fillMaterial = new RadialGradientMaterialProperty(fillColorStart, fillColorEnd)
-	zones.forEach((zone, index) => {
+	; (zones || []).forEach((zone, index) => {
 		if (!zone?.geom) return
 		const positions = getDefenseZonePositions(zone.geom)
 		if (!positions.length) return
-		const entityId = `${idPrefix}-${zone.id ?? index}`
-		const linePositions = positions.length > 1 ? [...positions, positions[0]] : positions
-		source.entities.add({
-			id: entityId,
-			polygon: {
-				hierarchy: new Cesium.PolygonHierarchy(positions),
-				material: fillMaterial,
-				outline: true,
-				outlineColor: borderColor,
-				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
-			},
-			polyline: {
-				positions: linePositions,
-				clampToGround: true,
-				width: 2,
-				material: borderColor,
-			},
+		const polygon = new Cesium.PolygonGeometry({
+			polygonHierarchy: new Cesium.PolygonHierarchy(positions),
+			vertexFormat: TEXTURED_VERTEX_FORMAT,
 		})
+		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 = Cesium.Material.fromType(MATERIAL_TYPE, {
+			color1: baseColor1.withAlpha(0.64),
+			color2: 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 renderDefenseZones = zones => {
 	if (!viewer) return
-	if (!defenseZoneSource) {
-		defenseZoneSource = new Cesium.CustomDataSource('defenseZoneSource')
-		viewer.dataSources.add(defenseZoneSource)
-	}
 	clearDefenseZoneEntities()
-	defenseZoneSource.show = detailVisible.value
-	renderZonePolygons({
-		zones,
-		source: defenseZoneSource,
-		idPrefix: 'defense-zone',
-		lineColor: '#19D266',
-		fillGradient: ['#2AEDBF', '#012B11'],
-	})
+	const result = buildZonePrimitives(zones, '#19D266', '#2AEDBF', '#012B11')
+	defenseZonePrimitive = result.primitive
+	defenseZoneOutlinePrimitive = result.outlinePrimitive
+	if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value
+	if (defenseZoneOutlinePrimitive) defenseZoneOutlinePrimitive.show = detailVisible.value
+	reorderCockpitPrimitives()
 }
 
 const renderPartitions = zones => {
 	if (!viewer) return
-	if (!partitionSource) {
-		partitionSource = new Cesium.CustomDataSource('partitionSource')
-		viewer.dataSources.add(partitionSource)
-	}
 	clearPartitionEntities()
-	partitionSource.show = detailVisible.value
-	renderZonePolygons({
-		zones,
-		source: partitionSource,
-		idPrefix: 'partition-zone',
-		lineColor: '#FFCD2A',
-		fillGradient: ['#FFC609', '#583300'],
-	})
+	const result = buildZonePrimitives(zones, '#FFCD2A', '#FFC609', '#583300')
+	partitionPrimitive = result.primitive
+	partitionOutlinePrimitive = result.outlinePrimitive
+	if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
+	if (partitionOutlinePrimitive) partitionOutlinePrimitive.show = detailVisible.value
+	reorderCockpitPrimitives()
 }
 
 const loadDefenseZones = async () => {
@@ -517,19 +824,18 @@
 
 const getPickedDevice = picks => {
 	for (const pick of picks) {
-		const entityId = typeof pick?.id === 'string' ? pick.id : pick?.id?.id
-		if (entityId && deviceEntityMap.has(entityId)) {
-			return deviceEntityMap.get(entityId)
+		const pickId = pick?.id
+		const resolvedId = typeof pickId === 'string' ? pickId : pickId?.id
+		if (resolvedId && devicePickMap.has(resolvedId)) {
+			return devicePickMap.get(resolvedId)
 		}
 	}
 	return null
 }
 
 const updatePopupPosition = () => {
-	if (!viewer || !selectedDeviceEntity) return
-	const cartesian =
-		selectedDeviceEntity.position?.getValue?.(Cesium.JulianDate.now()) ||
-		selectedDeviceEntity.position?._value
+	if (!viewer || !selectedDeviceBillboard) return
+	const cartesian = selectedDeviceBillboard.position
 	if (!cartesian) return
 	const screenPosition = viewer.scene.cartesianToCanvasCoordinates(cartesian)
 	if (!screenPosition) return
@@ -558,7 +864,7 @@
 		return
 	}
 	selectedDevice.value = pickedDevice.data
-	selectedDeviceEntity = pickedDevice.entity
+	selectedDeviceBillboard = pickedDevice.billboard
 	startPopupRender()
 }
 
@@ -576,7 +882,7 @@
 
 function closePopup () {
 	selectedDevice.value = null
-	selectedDeviceEntity = null
+	selectedDeviceBillboard = null
 	stopPopupRender()
 }
 
@@ -654,27 +960,21 @@
 
 const renderCommandPosts = list => {
 	if (!viewer) return
-	if (!commandPostSource) {
-		commandPostSource = new Cesium.CustomDataSource('commandPostSource')
-		viewer.dataSources.add(commandPostSource)
-	}
-	clearCommandPostEntities()
-	commandPostSource.show = detailVisible.value
-		; (list || []).forEach((item, index) => {
-			const position = getDevicePosition(item)
-			if (!position) return
-			commandPostSource.entities.add({
-				id: `command-post-${item.id ?? index}`,
-				position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
-				billboard: {
-					image: commandPostIcon,
-					width: 40,
-					height: 56,
-					verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-					heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
-				},
-			})
+	ensureCommandPostCollection()
+	commandPostBillboardCollection.removeAll()
+	commandPostBillboardCollection.show = detailVisible.value
+	; (list || []).forEach((item, index) => {
+		const position = getDevicePosition(item)
+		if (!position) return
+		commandPostBillboardCollection.add({
+			position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
+			image: commandPostIcon,
+			width: 40,
+			height: 56,
+			verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
 		})
+	})
+	reorderCockpitPrimitives()
 }
 
 const loadAggregation = async () => {
@@ -724,6 +1024,7 @@
 
 const handleMapReady = ({ viewer: mapViewer }) => {
 	viewer = mapViewer
+	ensureCockpitPrimitiveLayer()
 	const height = viewer?.camera?.positionCartographic?.height
 	const stage = height != null && height >= 100000 ? 'cluster' : 'detail'
 	updateStageDisplay(stage)
@@ -732,6 +1033,7 @@
 	loadPartitions()
 	loadAggregation()
 	loadCommandPosts()
+	renderSimulatedDroneTrack()
 	initDeviceClickHandler()
 }
 
@@ -759,8 +1061,13 @@
 	clearPartitionEntities()
 	clearAggregationEntities()
 	clearCommandPostEntities()
+	clearDroneTrackEntities()
 	closePopup()
 	destroyDeviceClickHandler()
+	if (cockpitPrimitiveLayer && viewer?.scene?.primitives) {
+		viewer.scene.primitives.remove(cockpitPrimitiveLayer)
+	}
+	cockpitPrimitiveLayer = null
 	viewer = null
 })
 </script>
@@ -1092,3 +1399,4 @@
 	border-color: #a1a3d4;
 }
 </style>
+

--
Gitblit v1.9.3