Files
erqi-web/src/components/Map/index.vue

577 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div id="map"></div>
<div class="map-mask"></div>
<!-- 四周边框 -->
<div class="map-border"></div>
<img class="map-bottom" src="@/assets/images/common/icon-bottom.png" alt="">
<img class="AI" src="@/assets/images/common/icon-AI.png" alt="" @click="toModel">
<div class="AI AI-text" @click="toModel"></div>
<!-- 信息弹窗 -->
<InfoWindowComponent/>
<!-- 渔船信息弹窗 -->
<TrawlerInfoWindowComponent/>
</template>
<script setup>
import { computed, nextTick, onMounted, onUnmounted, watch } from 'vue'
import * as maptalks from 'maptalks'
import GlobalMap from './js/GlobalMap'
import useMapStore from '@/store/modules/map'
import * as BoatUtil from './lbtbox/boatTerminal'
import * as $configs from './map-config.js'
import { monitors, uavs, stations, environmentals, fences, detailFences } from './js/mock.js'
import InfoWindowComponent from '@/components/Map/window/index.vue'
import TrawlerInfoWindowComponent from '@/components/Map/window/trawler.vue'
import { dsVideoList, findUavPage, getVideoInfo } from '@/api/device.js'
const mapStore = useMapStore()
const UAV = computed(() => mapStore.legend.UAV)
const monitor = computed(() => mapStore.legend.monitor)
const ais_station = computed(() => mapStore.legend.ais_station)
const environmental = computed(() => mapStore.legend.environmental)
const fence = computed(() => mapStore.legend.fence)
const sector = computed(() => mapStore.sector)
const locateData = computed(() => mapStore.locate.data)
let globalMap = null
let vector = {}
const geography = {
monitor: [], // 监控数据
UAV: [], // 无人机
ais_station: [], // ais基站
environmental: [], // 环境监测
fence: [] // 电子围栏
}
const initMap = () => {
const mapDom = document.getElementById('map')
globalMap = new GlobalMap(mapDom)//, { seamlessZoom: false })
}
const initLayerToMap = (type) => { // 地理图层注册
if (vector[type]) {
vector[type].remove()
vector[type] = null
}
vector[type] = new maptalks.VectorLayer(type).addTo(globalMap.map)
}
// 叠加电子围栏数据
const addFenceToMap = (type) => {
initLayerToMap(type)
geography[type].forEach((e) => {
if (e.positionInfo) {
const params = []
const symbol = [
{
lineColor: e.lineColor,
lineWidth: e.lineWidth - 0,
lineDasharray: [ 4, 4 ],
polygonFill: e.fillColor,
polygonOpacity: e.diaphaneity - 0,
textFill: 'black',
textHaloFill: 'white',
textHaloRadius: 2,
textName: e.warnAreaName,
textSize: {
stops: [
[ 10, 0 ],
[ 11, 12 ]
]
}
}
]
// 点位数据处理
const arr = e.positionInfo.split(';').map((pair) => {
const [ x, y ] = pair.split(',').map(Number)
return { x, y }
})
params.push(arr)
params.push({ symbol, zIndex: 1 })
const geometry = new maptalks.Polygon(...params)
vector[type].addGeometry(geometry)
}
})
}
/**
* 叠加监控数据
* @param type
* @param prop 监控类型
*/
const addMonitorToMap = () => {
initLayerToMap('monitor')
initLayerToMap('sectors_monitor')
geography.monitor.forEach(item => {
if (item.longitude && item.latitude) {
const marker = new maptalks.Marker(
{
x: item.longitude,
y: item.latitude
},
{
id: item.id,
symbol: $configs.getDevicePointSymbol('_monitor', { ...item, name: item.videoName }),
properties: item,
zIndex: 2
}
)
marker.addTo(vector.monitor)
const visionDistance = item.visionDistance * 1000 || 5 * 1000
const pvalue = item.ptzcfg?.pValue || 0
drawSector('_monitor', [ item.longitude, item.latitude ], visionDistance, item.id, (90 - pvalue) % 360)
marker.on('click', (evt) => {
// 先隐藏所有扇形
changeSectorsInLayer('sectors_monitor', false)
// 确保扇形图层是可见的
if (!vector.sectors_monitor.isVisible()) {
vector.sectors_monitor.show()
}
// 显示当前点击项的所有相关扇形(圆、椭圆、线)
const baseId = `sector__monitor${item.id}`
vector.sectors_monitor.getGeometryById(baseId + '_circle')?.show()
vector.sectors_monitor.getGeometryById(baseId + '_ellipse')?.show()
vector.sectors_monitor.getGeometryById(baseId + '_line')?.show()
mapStore.updateWindowInfo({ visible: true, type: '_monitor', data: { ...item } })
})
}
})
vector.monitor.show()
}
// 要素/视频定位
const locateMoitor = (data) => {
const name = 'locate-monitor'
let layer = vector[name]
if (!layer) {
initLayerToMap('locate-monitor')
layer = vector['locate-monitor']
} else {
layer.clear()
}
const monitors = vector.monitor
const marker = monitors.getGeometryById(data.id)
if (marker) {
const coordinates = marker.getCoordinates()
geography.monitor.forEach((item) => {
if (item.id === data.id) {
// globalMap.map.animateTo(
// {
// center: [ coordinates.x, coordinates.y ]
// },
// {
// duration: 1000 * 0.5,
// easing: 'out'
// }
// )
// 先隐藏所有扇形
changeSectorsInLayer('sectors_monitor', false)
getVideoInfo({ id: item.id }).then(res => {
const visionDistance = item.visionDistance * 1000 || 5 * 1000 // 视角距离
const pvalue = res.data.ptzcfg.pValue || 0 // 旋转角度
drawSector('_monitor', [ item.longitude, item.latitude ], visionDistance, item.id, (90 - pvalue) % 360)
// 确保扇形图层是可见的
if (!vector.sectors_monitor.isVisible()) {
vector.sectors_monitor.show()
}
// 显示当前点击项的所有相关扇形(圆、椭圆、线)
const baseId = `sector__monitor${item.id}`
vector.sectors_monitor.getGeometryById(baseId + '_circle')?.show()
vector.sectors_monitor.getGeometryById(baseId + '_ellipse')?.show()
vector.sectors_monitor.getGeometryById(baseId + '_line')?.show()
})
}
})
}
}
/**
* 叠加无人机数据
* @param type
* @param prop 无人机类型
*/
const addUAVToMap = () => {
initLayerToMap('UAV')
initLayerToMap('sectors_UAV')
geography.UAV.forEach(item => {
if (item.longitude && item.latitude) {
const marker = new maptalks.Marker(
{
x: item.longitude,
y: item.latitude
},
{
id: item.id,
symbol: $configs.getDevicePointSymbol('_UAV', { ...item, name: item.videoName }),
properties: item,
zIndex: 2
}
)
marker.addTo(vector.UAV)
drawSector('_UAV', [ item.longitude, item.latitude ], 5 * 1000, item.id)
marker.on('click', (evt) => {
// 先隐藏所有扇形
changeSectorsInLayer('sectors_UAV', false)
// 确保扇形图层是可见的
if (!vector.sectors_UAV.isVisible()) {
vector.sectors_UAV.show()
}
// 显示当前点击项的所有相关扇形(圆、椭圆、线)
const baseId = `sector__UAV${item.id}`
const circleGeometry = vector.sectors_UAV.getGeometryById(baseId + '_circle')
if (circleGeometry) {
circleGeometry.show()
}
if(item.sourceType === '1' || item.sourceType === '2') {
mapStore.updateWindowInfo({ visible: true, type: '_UAV', data: { ...item } })
}
})
}
})
vector.UAV.show()
}
// 添加辅助函数来隐藏所有扇形
const changeSectorsInLayer = (layerName, show) => {
if (vector[layerName]) {
const allGeometries = vector[layerName].getGeometries()
allGeometries.forEach(geometry => {
if(show) {
geometry.show()
}else{
geometry.hide()
}
})
}
}
// 需要监控的起始角度和结束角度,修改视野范围可以传递经纬度坐标
const drawSector = (type, center, radius, id, angle) => {
const sectorLayerName = 'sectors' + type
// 检查是否存在扇形图层,不存在则初始化
if (!vector[sectorLayerName]) {
initLayerToMap(sectorLayerName.replace('sectors', 'sectors_')) // 例如 sectors_monitor -> sectors_monitor
}
const sectorId = `sector_${type}${id}`
// 如果已存在同ID的扇形则先移除
const existingCircle = vector[sectorLayerName].getGeometryById(sectorId + '_circle')
const existingEllipse = vector[sectorLayerName].getGeometryById(sectorId + '_ellipse')
const existingLine = vector[sectorLayerName].getGeometryById(sectorId + '_line')
if (existingCircle) {
vector[sectorLayerName].removeGeometry(existingCircle)
}
if (existingEllipse) {
vector[sectorLayerName].removeGeometry(existingEllipse)
}
if (existingLine) {
vector[sectorLayerName].removeGeometry(existingLine)
}
let circle = new maptalks.Circle(center, radius, {
id: sectorId + '_circle',
symbol: {
lineColor: '#1CA8FF',
lineWidth: 1,
lineOpacity: 1,
polygonFill: '#1ca8ff',
polygonOpacity: 0.16
}
})
if(typeof angle === 'number' && !isNaN(angle)) {
let ellipse = new maptalks.Sector(center, radius, angle - 10, angle + 10, {
id: sectorId + '_ellipse',
symbol: {
lineColor: '#FF8D1C',
polygonFill: '#ff8d1c29'
}
})
let line = new maptalks.Sector(center, radius, angle, angle, {
id: sectorId + '_line',
symbol: {
lineColor: '#FF8D1C',
polygonFill: '#ff8d1c29',
lineDasharray: [ 5, 10 ]
}
})
vector['sectors' + type].addGeometry([ circle, ellipse, line ])
}else{
vector['sectors' + type].addGeometry(circle)
}
// 添加到可视域图层
changeSectorsInLayer('sectors' + type, false)
}
/**
* 叠加ais基站数据
* @param type
* @param prop ais基站类型
*/
const addAisStationToMap = () => {
initLayerToMap('ais_station')
geography.ais_station.forEach(item => {
if (item.longitude && item.latitude) {
const marker = new maptalks.Marker(
{
x: item.longitude,
y: item.latitude
},
{
id: item.id,
symbol: $configs.getDevicePointSymbol('_ais_station', { ...item }),
properties: item,
zIndex: 2
}
)
marker.addTo(vector.ais_station)
}
})
vector.ais_station.show()
}
/**
* 叠加环境监测数据
* @param type
* @param prop 环境监测类型
*/
const addEnvironmentalToMap = () => {
initLayerToMap('environmental')
geography.environmental.forEach(item => {
if (item.longitude && item.latitude) {
const marker = new maptalks.Marker(
{
x: item.longitude,
y: item.latitude
},
{
id: item.id,
symbol: $configs.getDevicePointSymbol('_environmental', { ...item }),
properties: item,
zIndex: 2
}
)
marker.addTo(vector.environmental)
}
})
vector.environmental.show()
}
const initUAV = () => {
const params = {
pageNo: 1,
pageSize: 9999
}
findUavPage(params).then(res => {
if (res.success) {
geography.UAV = res.result.records
addUAVToMap()
}
})
}
const initMonitor = () => {
const params = {}
dsVideoList(params).then(res => {
geography.monitor = res.data
addMonitorToMap()
})
}
const initAisStation = () => {
geography.ais_station = stations
addAisStationToMap()
}
const initEnvironmental = () => {
geography.environmental = environmentals
addEnvironmentalToMap()
}
const initFence = () => {
geography.fence = fences
geography.detailFence = detailFences
addFenceToMap('fence')
addFenceToMap('detailFence')
}
const toModel = () => {
mapStore.updateDialog({ visible: true, type: 'largeModel' })
}
onMounted(() => {
initMap()
initFence()
initUAV()
initMonitor()
initAisStation()
initEnvironmental()
// 渔船链接
BoatUtil.init(mapStore)
BoatUtil.getShip()
// 轨迹图层
initLayerToMap('track')
})
watch(() => UAV.value, () => {
if(UAV.value) {
initUAV()
}else{
vector.UAV?.hide()
changeSectorsInLayer('sectors_UAV', false)
}
})
watch(() => monitor.value, () => {
if(monitor.value) {
initMonitor()
}else{
vector.monitor?.hide()
changeSectorsInLayer('sectors_monitor', false)
}
})
watch(() => ais_station.value, () => {
if(ais_station.value) {
initAisStation()
}else{
vector.ais_station?.hide()
}
})
watch(() => environmental.value, () => {
if(environmental.value) {
initEnvironmental()
}else{
vector.environmental?.hide()
}
})
watch(() => fence.value, () => {
if(fence.value) {
initFence()
}else{
vector.fence?.hide()
vector.detailFence?.hide()
}
})
watch(() => sector.value.monitor, (newVal) => {
if(vector.sectors_monitor) {
if(newVal) {
// vector.sectors_monitor.show()
changeSectorsInLayer('sectors_monitor', true)
} else {
// vector.sectors_monitor.hide()
changeSectorsInLayer('sectors_monitor', false)
}
}
})
watch(() => sector.value.UAV, (newVal) => {
if(vector.sectors_UAV) {
if(newVal) {
// vector.sectors_UAV.show()
changeSectorsInLayer('sectors_UAV', true)
} else {
// vector.sectors_UAV.hide()
changeSectorsInLayer('sectors_UAV', false)
}
}
})
// 定位数据变化
watch(() => locateData.value.videoCode, (newVal) => {
nextTick(() => {
locateMoitor(locateData.value)
})
})
onUnmounted(() => {
globalMap.destroy()
globalMap = null
BoatUtil.destroyWebsocket()
})
</script>
<style lang="scss" scoped>
#map {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.map-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('@/assets/images/common/map-mask.png');
background-size: 100% 100%;
background-repeat: no-repeat;
pointer-events: none;
}
.map-border {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
height: calc(100% - 18px);
background-image: url('@/assets/images/common/map-border.png');
background-size: 100% 100%;
background-repeat: no-repeat;
pointer-events: none;
}
.map-bottom {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%);
}
.AI{
position: absolute;
bottom: 15px;
left: 50%;
transform: translate(-50%);
cursor: pointer;
z-index: 2;
}
.AI-text{
font-family: 'YouSheBiaoTiHei';
font-size: 22px;
-webkit-text-stroke: 1px rgba(4,176,244,0.4);
text-align: left;
font-style: normal;
text-transform: none;
background-image: linear-gradient(180deg, #FFFFFF 5%, #2DE5FF 100%);
-webkit-background-clip: text;
bottom: 33px;
-webkit-text-fill-color: transparent;
}
// 弹窗样式
:deep(.dialog-wrapper.el-dialog){
// height: 900px;
background: linear-gradient(90deg, rgba(12, 25, 41, 0.8) 0%, rgba(12, 25, 41, 0.6) 100%);
border: 1px solid #00C0FF;
border-radius: 0;
padding: 0;
.el-dialog__header{
display: flex;
justify-content: space-between;
padding: 0 16px;
background: linear-gradient(0deg, rgba(10, 169, 255, 0) 0%, rgba(10, 169, 255, 0.5) 100%);
.title{
display: flex;
justify-content: space-between;
align-items: center;
font-size: 20px;
color: #FFFFFF;
text-align: left;
height: 56px;
.icon-group img {
cursor: pointer;
&:active {
opacity: .8;
}
}
}
}
.el-dialog__body {
padding: 16px;
box-sizing: border-box;
}
}
</style>