580 lines
16 KiB
Vue
580 lines
16 KiB
Vue
<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 origin_monitor = computed(() => mapStore.legend.origin_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: [], // 监控数据
|
||
origin_monitor: [], // 原始监控数据
|
||
UAV: [], // 无人机
|
||
ais_station: [], // ais基站
|
||
environmental: [], // 环境监测
|
||
fence: [] // 电子围栏
|
||
}
|
||
let sectorLayer = null
|
||
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> |