| | |
| | | <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" |
| | |
| | | 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' |
| | |
| | | |
| | | 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 = { |
| | |
| | | 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 = () => { |
| | |
| | | } |
| | | |
| | | 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 => { |
| | |
| | | }) |
| | | } |
| | | |
| | | 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 = () => { |
| | |
| | | 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) |
| | | ), |
| | | }, |
| | | }) |
| | | ) |
| | | }) |
| | | } |
| | | |
| | |
| | | |
| | | 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 => { |
| | |
| | | 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 () => { |
| | |
| | | |
| | | 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 |
| | |
| | | return |
| | | } |
| | | selectedDevice.value = pickedDevice.data |
| | | selectedDeviceEntity = pickedDevice.entity |
| | | selectedDeviceBillboard = pickedDevice.billboard |
| | | startPopupRender() |
| | | } |
| | | |
| | |
| | | |
| | | function closePopup () { |
| | | selectedDevice.value = null |
| | | selectedDeviceEntity = null |
| | | selectedDeviceBillboard = null |
| | | stopPopupRender() |
| | | } |
| | | |
| | |
| | | |
| | | 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 () => { |
| | |
| | | |
| | | const handleMapReady = ({ viewer: mapViewer }) => { |
| | | viewer = mapViewer |
| | | ensureCockpitPrimitiveLayer() |
| | | const height = viewer?.camera?.positionCartographic?.height |
| | | const stage = height != null && height >= 100000 ? 'cluster' : 'detail' |
| | | updateStageDisplay(stage) |
| | |
| | | loadPartitions() |
| | | loadAggregation() |
| | | loadCommandPosts() |
| | | renderSimulatedDroneTrack() |
| | | initDeviceClickHandler() |
| | | } |
| | | |
| | |
| | | clearPartitionEntities() |
| | | clearAggregationEntities() |
| | | clearCommandPostEntities() |
| | | clearDroneTrackEntities() |
| | | closePopup() |
| | | destroyDeviceClickHandler() |
| | | if (cockpitPrimitiveLayer && viewer?.scene?.primitives) { |
| | | viewer.scene.primitives.remove(cockpitPrimitiveLayer) |
| | | } |
| | | cockpitPrimitiveLayer = null |
| | | viewer = null |
| | | }) |
| | | </script> |
| | |
| | | border-color: #a1a3d4; |
| | | } |
| | | </style> |
| | | |