From fb39a7bb69cc55d2e1ce61d47e0677aa40540f95 Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Fri, 30 Jan 2026 16:20:33 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/drone-command/src/components/map-container/device-map-container.vue |  424 +++++++++++++++++++++++++++++++---------------------
 1 files changed, 253 insertions(+), 171 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 f7fb7e9..659f8ca 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
@@ -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()

--
Gitblit v1.9.3