| | |
| | | <template> |
| | | <div class="command-cesium map-container" :id="props.containerId"></div> |
| | | <CommonCesiumMap |
| | | ref="mapRef" |
| | | class="command-cesium map-container" |
| | | :dom-id="props.containerId" |
| | | :active="true" |
| | | :flat-mode="false" |
| | | :terrain="false" |
| | | :layer-mode="4" |
| | | :contour="false" |
| | | :boundary="false" |
| | | :show-admin-boundary="true" |
| | | :zoom-to-boundary="true" |
| | | @ready="handleMapReady" |
| | | /> |
| | | <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"> |
| | |
| | | |
| | | <script setup> |
| | | import * as Cesium from 'cesium' |
| | | import { PublicCesium } from '@/utils/cesium/publicCesium' |
| | | 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 layerControlIcon from '@/assets/images/dataCockpit/layerControl.png' |
| | | import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png' |
| | | |
| | |
| | | }, |
| | | }) |
| | | |
| | | let viewInstance = null |
| | | const DEFAULT_ZONE_PAGE_SIZE = 999 |
| | | |
| | | const mapRef = ref(null) |
| | | let viewer = null |
| | | const deviceEntityIds = new Set() |
| | | let defenseZoneSource = null |
| | | let partitionSource = null |
| | | const showLayerPanel = ref(false) |
| | | const layerWrapRef = ref(null) |
| | | const layerTreeProps = { |
| | |
| | | deviceEntityIds.clear() |
| | | } |
| | | |
| | | const clearDefenseZoneEntities = () => { |
| | | if (!defenseZoneSource) return |
| | | defenseZoneSource.entities.removeAll() |
| | | } |
| | | |
| | | const clearPartitionEntities = () => { |
| | | if (!partitionSource) return |
| | | partitionSource.entities.removeAll() |
| | | } |
| | | |
| | | |
| | | const RING_STYLES = [ |
| | | { inner: 0, outer: 5000, gradient: ['#FF361C', '#360B00'] }, |
| | | { inner: 5000, outer: 8000, gradient: ['#FFC609', '#583300'] }, |
| | | { inner: 8000, outer: 10000, gradient: ['#2AEDBF', '#012B11'] }, |
| | | { inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] } |
| | | ] |
| | | |
| | | const MATERIAL_TYPE = 'RadialGradientMaterial' |
| | |
| | | }) |
| | | } |
| | | |
| | | 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 renderZonePolygons = ({ zones, source, idPrefix, lineColor, fillGradient }) => { |
| | | if (!viewer || !source) return |
| | | 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) => { |
| | | 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 renderDefenseZones = zones => { |
| | | if (!viewer) return |
| | | if (!defenseZoneSource) { |
| | | defenseZoneSource = new Cesium.CustomDataSource('defenseZoneSource') |
| | | viewer.dataSources.add(defenseZoneSource) |
| | | } |
| | | clearDefenseZoneEntities() |
| | | renderZonePolygons({ |
| | | zones, |
| | | source: defenseZoneSource, |
| | | idPrefix: 'defense-zone', |
| | | lineColor: '#19D266', |
| | | fillGradient: ['#2AEDBF', '#012B11'], |
| | | }) |
| | | } |
| | | |
| | | const renderPartitions = zones => { |
| | | if (!viewer) return |
| | | if (!partitionSource) { |
| | | partitionSource = new Cesium.CustomDataSource('partitionSource') |
| | | viewer.dataSources.add(partitionSource) |
| | | } |
| | | clearPartitionEntities() |
| | | renderZonePolygons({ |
| | | zones, |
| | | source: partitionSource, |
| | | idPrefix: 'partition-zone', |
| | | lineColor: '#FFCD2A', |
| | | fillGradient: ['#FFC609', '#583300'], |
| | | }) |
| | | } |
| | | |
| | | 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 loadPartitions = async () => { |
| | | if (!viewer) return |
| | | try { |
| | | const res = await fwAreaDividePageApi({ current: 1, size: DEFAULT_ZONE_PAGE_SIZE }) |
| | | renderPartitions(res?.data?.data?.records ?? []) |
| | | } catch (error) { |
| | | renderPartitions([]) |
| | | } |
| | | } |
| | | |
| | | |
| | | watch( |
| | | () => props.onlineDevices, |
| | | devices => { |
| | |
| | | showLayerPanel.value = !showLayerPanel.value |
| | | } |
| | | |
| | | const handleMapReady = ({ viewer: mapViewer }) => { |
| | | viewer = mapViewer |
| | | renderDeviceEntities(props.onlineDevices) |
| | | loadDefenseZones() |
| | | loadPartitions() |
| | | } |
| | | |
| | | const handleClickOutside = event => { |
| | | if (!showLayerPanel.value) return |
| | | const target = event.target |
| | |
| | | |
| | | onMounted(() => { |
| | | document.addEventListener('click', handleClickOutside) |
| | | viewInstance = new PublicCesium({ |
| | | dom: props.containerId, |
| | | flatMode: false, |
| | | terrain: false, |
| | | layerMode: 4, |
| | | contour: false, |
| | | }) |
| | | |
| | | viewer = viewInstance.getViewer() |
| | | renderDeviceEntities(props.onlineDevices) |
| | | const map = mapRef.value?.getMap() |
| | | if (map?.viewer) handleMapReady(map) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | document.removeEventListener('click', handleClickOutside) |
| | | clearDeviceEntities() |
| | | if (viewInstance) { |
| | | viewInstance?.viewerDestroy() |
| | | viewInstance = null |
| | | } |
| | | |
| | | clearDefenseZoneEntities() |
| | | clearPartitionEntities() |
| | | viewer = null |
| | | }) |
| | | </script> |
| | |
| | | |
| | | .layer-control-root { |
| | | position: absolute; |
| | | left: 411px; |
| | | left: 337px; |
| | | bottom: 22px; |
| | | z-index: 9; |
| | | transition: transform 0.3s ease-in-out; |
| | | pointer-events: none; |
| | | |
| | | &.collapsed { |
| | | transform: translateX(-401px); |
| | | transform: translateX(-317px); |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | .layer-control { |
| | | width: 34px; |
| | | height: 34px; |
| | | width: 46px; |
| | | height: 46px; |
| | | cursor: pointer; |
| | | |
| | | img { |
| | |
| | | display: flex; |
| | | flex-direction: column; |
| | | position: absolute; |
| | | left: 46px; |
| | | left: 66px; |
| | | bottom: 0; |
| | | width: 160px; |
| | | max-height: 442px; |
| | |
| | | line-height: 30px !important; |
| | | |
| | | .el-tree-node__content { |
| | | padding-left: 0 !important; |
| | | display: flex; |
| | | align-items: center; |
| | | height: 40px !important; |
| | |
| | | .el-tree-node__children { |
| | | .el-tree-node__content { |
| | | border: none; |
| | | } |
| | | } |
| | | |
| | | &:focus { |
| | | .el-tree-node__content { |
| | | background: transparent !important; |
| | | } |
| | | } |
| | | } |
| | |
| | | display: none; |
| | | } |
| | | |
| | | .layer-panel :deep(.el-tree-node__content:hover) { |
| | | .layer-panel :deep(.el-tree-node__content:hover), |
| | | .layer-panel :deep(.el-tree-node__content:focus) { |
| | | background: transparent !important; |
| | | } |
| | | |