2023-09-20 21:33:11 +08:00
|
|
|
|
<template>
|
|
|
|
|
<div id="map">
|
|
|
|
|
<slot name="content"></slot>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script>
|
|
|
|
|
import mapboxgl from 'mapbox-gl'
|
2025-06-18 19:58:16 +08:00
|
|
|
|
import { MapboxStyleSwitcherControl, FollowControl, CustomFullscreenControl, NoFlyControl, RestrictflyControl, SaveToFileControl, PolygonToggleControl } from '@/utils/mapboxgl_plugs'
|
2023-09-20 21:33:11 +08:00
|
|
|
|
import planeIcon from '@/assets/svg/plane.svg'
|
2025-06-23 01:00:21 +08:00
|
|
|
|
// import unlineIcon from '@/assets/svg/plane_unline.svg'
|
2025-06-20 15:03:24 +08:00
|
|
|
|
import civilIcon from '@/assets/svg/civil.svg'
|
2023-09-20 21:33:11 +08:00
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
name: 'MapBox',
|
|
|
|
|
data () {
|
|
|
|
|
return {
|
2024-10-09 12:14:52 +08:00
|
|
|
|
token: 'pk.eyJ1Ijoic3pkb3QiLCJhIjoiY2xhN2pkMWFoMHJ4eTN3cXp6bmlzaHZ0NCJ9.3hH-EAUr0wQCaLvIM2lBMQ',
|
2023-09-20 21:33:11 +08:00
|
|
|
|
map: null,
|
2025-06-20 15:03:24 +08:00
|
|
|
|
ADSBMarkers: [], // ADSB民航飞机对象
|
2023-09-20 21:33:11 +08:00
|
|
|
|
planes: [], // 飞机对象
|
2024-09-19 13:09:43 +08:00
|
|
|
|
lonLats: [], // 航线 所有航点
|
|
|
|
|
wayLonLats: [], // 航线 不包括起点航点
|
|
|
|
|
takeoffLonLats: [], // 航线 第一个航点 起点 最后一个航点
|
2024-07-25 05:13:30 +08:00
|
|
|
|
isflow: false, // 飞机经纬度变化时是否跟随飞机
|
2024-07-25 21:33:52 +08:00
|
|
|
|
currentStyleIndex: 0, // 当前选中的地图样式索引
|
2025-06-22 06:06:18 +08:00
|
|
|
|
guidedMarker: null // 指点飞行地图标记
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
2024-08-08 14:40:30 +08:00
|
|
|
|
props: {
|
2024-10-11 12:42:56 +08:00
|
|
|
|
enableGuided: { // 是否开启 飞机点飞功能
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
2024-08-08 14:40:30 +08:00
|
|
|
|
enableFollow: { // 开启跟随组件
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
|
|
|
|
enableSwitch: { // 开启 地图选择
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true
|
|
|
|
|
},
|
|
|
|
|
enableZoom: { // 开启 地图放大组件
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true
|
|
|
|
|
},
|
|
|
|
|
enblueTranslate: { // 开启 地图移动旋转 组件
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: true
|
|
|
|
|
},
|
|
|
|
|
enblueScale: { // 开启 地图 距离比例 组件
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
2025-06-03 03:50:56 +08:00
|
|
|
|
},
|
2025-06-16 16:41:03 +08:00
|
|
|
|
enableSaveToFile: { // 开启 保存数据到文件 组件
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
|
|
|
|
getData: { // 从父级 获取数据
|
|
|
|
|
type: Function,
|
|
|
|
|
default: null
|
|
|
|
|
},
|
2025-06-03 03:50:56 +08:00
|
|
|
|
enableNofly: { // 禁飞区 设置 组件
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
2025-06-17 23:17:46 +08:00
|
|
|
|
},
|
2025-06-18 19:58:16 +08:00
|
|
|
|
enableRestrictfly: { // 限飞区 设置 组件
|
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
|
|
|
|
},
|
2025-06-18 03:59:14 +08:00
|
|
|
|
enableShowNofly: { // 显示 禁飞区 限飞区 组件
|
2025-06-17 23:17:46 +08:00
|
|
|
|
type: Boolean,
|
|
|
|
|
default: false
|
2024-08-08 14:40:30 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
2023-09-20 21:33:11 +08:00
|
|
|
|
computed: {
|
2024-09-19 13:09:43 +08:00
|
|
|
|
defaultLonlat () {
|
|
|
|
|
return this.$store.getters['app/getDefaultLonLat']
|
2023-09-20 21:33:11 +08:00
|
|
|
|
},
|
|
|
|
|
defaultZoom () {
|
|
|
|
|
return this.$store.getters['app/getDefaultZoom']
|
2025-06-22 06:06:18 +08:00
|
|
|
|
},
|
|
|
|
|
mapStyleList () {
|
|
|
|
|
return this.$store.state.mapStyleList
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
2025-06-22 06:06:18 +08:00
|
|
|
|
watch: {
|
|
|
|
|
},
|
|
|
|
|
async mounted () {
|
|
|
|
|
// 等待 异步 加载 后端地图源 地址
|
|
|
|
|
await this.$store.dispatch('fetchMapStyleList', this.$store.state.settings.host)
|
|
|
|
|
// 等待 异步 地图初始化
|
|
|
|
|
await this.init()
|
|
|
|
|
|
|
|
|
|
// 地图初始化之后
|
|
|
|
|
this.map.on('load', () => {
|
|
|
|
|
/* 更新样式,添加自定义 sprite */
|
|
|
|
|
|
|
|
|
|
// 星空背景 大气层
|
|
|
|
|
this.map.setFog({
|
|
|
|
|
range: [1.0, 8.0],
|
|
|
|
|
color: 'white',
|
|
|
|
|
'horizon-blend': 0.01
|
2023-09-20 21:33:11 +08:00
|
|
|
|
})
|
2025-06-22 06:06:18 +08:00
|
|
|
|
// // 添加等高线图层
|
|
|
|
|
// this.map.addSource('contours', {
|
|
|
|
|
// type: 'vector',
|
|
|
|
|
// url: 'mapbox://mapbox.mapbox-terrain-v2'
|
|
|
|
|
// })
|
|
|
|
|
// this.map.addLayer({
|
|
|
|
|
// id: 'contours',
|
|
|
|
|
// type: 'line',
|
|
|
|
|
// source: 'contours',
|
|
|
|
|
// 'source-layer': 'contour',
|
|
|
|
|
// layout: {
|
|
|
|
|
// 'line-join': 'round',
|
|
|
|
|
// 'line-cap': 'round'
|
|
|
|
|
// },
|
|
|
|
|
// paint: {
|
|
|
|
|
// 'line-color': '#ff69b4',// 设置等高线颜色
|
|
|
|
|
// 'line-width': 1 // 设置等高线宽度
|
|
|
|
|
// }
|
|
|
|
|
// })
|
|
|
|
|
// // 3D地图
|
|
|
|
|
// this.map.addSource('mapbox-dem', {
|
|
|
|
|
// type: 'raster-dem',
|
|
|
|
|
// url: 'mapbox://mapbox.mapbox-terrain-dem-v1',
|
|
|
|
|
// tileSize: 512,
|
|
|
|
|
// maxzoom: 14
|
|
|
|
|
// })
|
|
|
|
|
// this.map.setTerrain({ source: 'mapbox-dem', exaggeration: 1.5 })
|
|
|
|
|
|
|
|
|
|
// 外部组件回调 触发地图加载完成事件(on 'load') 传到外部组件调用 以确保执行时地图已经加载完成
|
|
|
|
|
this.$emit('map-ready')
|
2023-11-07 13:28:53 +08:00
|
|
|
|
|
2024-10-11 12:42:56 +08:00
|
|
|
|
// 判断地图开启了点飞功能
|
|
|
|
|
if (this.enableGuided) {
|
2025-06-22 15:05:06 +08:00
|
|
|
|
// 长按事件 传longPress到组件外部调用
|
2024-10-11 12:42:56 +08:00
|
|
|
|
let pressTimer = null
|
|
|
|
|
let isLongPress = false // 标记是否为长按
|
|
|
|
|
let startPoint = null // 记录按下时的位置
|
|
|
|
|
// pc操作
|
|
|
|
|
this.map.on('mousedown', (event) => { // 按下鼠标左键
|
|
|
|
|
startPoint = { x: event.point.x, y: event.point.y } // 记录起始位置
|
|
|
|
|
isLongPress = false // 重置标记
|
|
|
|
|
pressTimer = setTimeout(() => {
|
|
|
|
|
isLongPress = true // 设置为长按状态
|
|
|
|
|
}, 200) // 设置长按延迟时间
|
|
|
|
|
})
|
|
|
|
|
this.map.on('mouseup', (event) => { // 提起鼠标左键
|
|
|
|
|
clearTimeout(pressTimer) // 清除定时器
|
|
|
|
|
const currentPoint = { x: event.point.x, y: event.point.y }
|
|
|
|
|
const dx = currentPoint.x - startPoint.x
|
|
|
|
|
const dy = currentPoint.y - startPoint.y
|
|
|
|
|
if (Math.sqrt(dx * dx + dy * dy) <= 5 && isLongPress) { // 没有发生拖拽地图
|
|
|
|
|
const lonLat = {
|
2025-06-20 15:03:24 +08:00
|
|
|
|
lon: Number(this.map.unproject(currentPoint).lng.toFixed(7)),
|
|
|
|
|
lat: Number(this.map.unproject(currentPoint).lat.toFixed(7))
|
2024-10-11 12:42:56 +08:00
|
|
|
|
}
|
|
|
|
|
this.$emit('longPress', lonLat) // 将经纬度信息传递到组件外部
|
2024-10-11 14:38:46 +08:00
|
|
|
|
this.makeGuidedMarker(lonLat) // 创建标记
|
2024-10-11 12:42:56 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
// 触摸屏操作
|
|
|
|
|
this.map.on('touchstart', (event) => { // 按下鼠标左键
|
|
|
|
|
clearTimeout(pressTimer) // 清除定时器
|
|
|
|
|
startPoint = { x: event.point.x, y: event.point.y } // 记录起始位置
|
|
|
|
|
isLongPress = false // 重置标记
|
|
|
|
|
pressTimer = setTimeout(() => {
|
|
|
|
|
isLongPress = true // 设置为长按状态
|
|
|
|
|
}, 200) // 设置长按延迟时间
|
|
|
|
|
})
|
|
|
|
|
this.map.on('touchend', (event) => { // 提起鼠标左键
|
|
|
|
|
const currentPoint = { x: event.point.x, y: event.point.y }
|
|
|
|
|
const dx = currentPoint.x - startPoint.x
|
|
|
|
|
const dy = currentPoint.y - startPoint.y
|
|
|
|
|
if (Math.sqrt(dx * dx + dy * dy) <= 5 && isLongPress) { // 没有发生拖拽地图
|
|
|
|
|
const lonLat = {
|
|
|
|
|
lon: this.map.unproject(currentPoint).lng,
|
|
|
|
|
lat: this.map.unproject(currentPoint).lat
|
|
|
|
|
}
|
|
|
|
|
this.$emit('longPress', lonLat) // 将经纬度信息传递到组件外部
|
2024-10-11 14:38:46 +08:00
|
|
|
|
this.makeGuidedMarker(lonLat) // 创建标记
|
2024-10-11 12:42:56 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
2023-11-07 13:28:53 +08:00
|
|
|
|
}
|
2023-09-20 21:33:11 +08:00
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
/**
|
2024-07-25 05:13:30 +08:00
|
|
|
|
* @description: 地图初始化
|
|
|
|
|
*/
|
2023-09-20 21:33:11 +08:00
|
|
|
|
async init () {
|
|
|
|
|
// token
|
2024-10-09 12:14:52 +08:00
|
|
|
|
mapboxgl.accessToken = this.token
|
2023-09-20 21:33:11 +08:00
|
|
|
|
// 实例化map
|
|
|
|
|
this.map = new mapboxgl.Map({
|
|
|
|
|
container: 'map',
|
2025-06-22 06:06:18 +08:00
|
|
|
|
style: this.mapStyleList[0],
|
2024-09-19 13:09:43 +08:00
|
|
|
|
center: this.defaultLonlat,
|
2023-09-20 21:33:11 +08:00
|
|
|
|
zoom: this.defaultZoom,
|
2023-10-18 15:58:44 +08:00
|
|
|
|
pitch: 0,
|
2023-09-20 21:33:11 +08:00
|
|
|
|
bearing: 0,
|
|
|
|
|
projection: 'globe'
|
|
|
|
|
})
|
2024-07-25 05:13:30 +08:00
|
|
|
|
/* 地图控件 */
|
|
|
|
|
// 移动旋转指南针
|
2024-08-08 14:40:30 +08:00
|
|
|
|
if (this.enblueTranslate) {
|
|
|
|
|
this.map.addControl(new mapboxgl.NavigationControl(), 'top-right')
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-25 05:13:30 +08:00
|
|
|
|
// 地图比例
|
2024-08-08 14:40:30 +08:00
|
|
|
|
if (this.enblueScale) {
|
|
|
|
|
this.map.addControl(new mapboxgl.ScaleControl(), 'bottom-right')
|
|
|
|
|
}
|
2024-07-25 17:21:18 +08:00
|
|
|
|
// 全屏 并再之后 刷新地图
|
2024-08-08 14:40:30 +08:00
|
|
|
|
if (this.enableZoom) {
|
|
|
|
|
this.map.addControl(new CustomFullscreenControl(this.handleResize), 'top-right')
|
|
|
|
|
}
|
2024-07-25 17:21:18 +08:00
|
|
|
|
|
2024-07-25 19:59:01 +08:00
|
|
|
|
// 地图样式选择控件
|
2024-07-25 21:33:52 +08:00
|
|
|
|
// 自定义地图样式列表
|
2024-08-08 14:40:30 +08:00
|
|
|
|
if (this.enableSwitch) {
|
2025-06-22 06:06:18 +08:00
|
|
|
|
const styleSwitcherControl = new MapboxStyleSwitcherControl(this.mapStyleList, 'iconfont icon-duozhang f-s-20', this.currentStyleIndex)
|
2024-08-08 14:40:30 +08:00
|
|
|
|
this.map.addControl(styleSwitcherControl, 'top-right')
|
|
|
|
|
}
|
2024-07-25 19:59:01 +08:00
|
|
|
|
|
2024-07-25 17:21:18 +08:00
|
|
|
|
// 飞机跟随
|
2024-08-08 14:40:30 +08:00
|
|
|
|
if (this.enableFollow) {
|
|
|
|
|
this.map.addControl(new FollowControl({
|
|
|
|
|
defaultIconClass: 'iconfont icon-weidingwei f-s-20', // 默认图标
|
|
|
|
|
activeIconClass: 'iconfont icon-dingwei f-s-20 brandFontColor', // 激活时图标
|
|
|
|
|
onClick: (isActive) => {
|
|
|
|
|
this.isflow = isActive // 根据控件的状态更新 isflow
|
|
|
|
|
if (this.isflow) {
|
|
|
|
|
this.goto(this.planes[0].getLngLat()) // 点击按钮首先直接 跳转到当前飞机对象最后的经纬度
|
|
|
|
|
}
|
2024-07-25 17:21:18 +08:00
|
|
|
|
}
|
2024-08-08 14:40:30 +08:00
|
|
|
|
}), 'top-right')
|
|
|
|
|
}
|
2025-06-03 03:50:56 +08:00
|
|
|
|
|
2025-06-16 16:41:03 +08:00
|
|
|
|
// 保存数据到文件 控件 用于:1.保存历史飞行航线轨迹
|
|
|
|
|
if (this.enableSaveToFile && typeof this.getData === 'function') {
|
|
|
|
|
this.map.addControl(new SaveToFileControl({
|
|
|
|
|
Data: this.getData,
|
|
|
|
|
filename: `export_${Date.now()}.json`,
|
|
|
|
|
title: '保存当前数据',
|
|
|
|
|
icon: '💾'
|
|
|
|
|
}), 'top-left')
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-17 01:56:19 +08:00
|
|
|
|
// 禁飞区 限飞区 设置组件
|
2025-06-03 03:50:56 +08:00
|
|
|
|
if (this.enableNofly) {
|
|
|
|
|
this.map.addControl(new NoFlyControl({
|
2025-06-18 19:58:16 +08:00
|
|
|
|
defaultPolygons: this.$store.state.noflyData[0], // 禁飞
|
|
|
|
|
shopId: this.$store.state.user.shop_id,
|
|
|
|
|
onSave: (data) => {
|
|
|
|
|
console.log('保存成功:', data)
|
|
|
|
|
}
|
|
|
|
|
}), 'top-left')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 禁飞区 限飞区 设置组件
|
|
|
|
|
if (this.enableRestrictfly) {
|
|
|
|
|
this.map.addControl(new RestrictflyControl({
|
|
|
|
|
defaultPolygons: this.$store.state.noflyData[1], // 限飞区
|
|
|
|
|
defaultHeights: this.$store.state.noflyData[2], // 限飞区对应高度
|
|
|
|
|
shopId: this.$store.state.user.shop_id,
|
|
|
|
|
onSave: (data) => {
|
|
|
|
|
console.log('保存成功:', data)
|
2025-06-03 03:50:56 +08:00
|
|
|
|
}
|
|
|
|
|
}), 'top-left')
|
|
|
|
|
}
|
2025-06-17 23:17:46 +08:00
|
|
|
|
|
|
|
|
|
// 显示禁飞区 限飞区控件
|
2025-06-18 03:59:14 +08:00
|
|
|
|
if (this.enableShowNofly) {
|
2025-06-17 23:17:46 +08:00
|
|
|
|
const noflyPolygons = this.$store.state.noflyData[0] || []
|
|
|
|
|
const restrictflyPolygons = this.$store.state.noflyData[1] || []
|
2025-06-18 19:58:16 +08:00
|
|
|
|
const restrictflyHeights = this.$store.state.noflyData[2] || []
|
2025-06-17 23:17:46 +08:00
|
|
|
|
|
|
|
|
|
this.map.addControl(new PolygonToggleControl({
|
2025-06-18 03:59:14 +08:00
|
|
|
|
defaultIconClass: 'iconfont icon-jinfeiqu_weidianji f-s-20 seatFontColor',
|
|
|
|
|
activeIconClass: 'iconfont icon-jinfeiqu f-s-20 brandFontColor',
|
|
|
|
|
|
|
|
|
|
onToggle: (isActive) => {
|
|
|
|
|
if (isActive) {
|
2025-06-18 19:58:16 +08:00
|
|
|
|
// 绘制禁飞区
|
2025-06-18 03:59:14 +08:00
|
|
|
|
noflyPolygons.forEach((coords, i) => {
|
|
|
|
|
this.drawPolygonWithLabel(coords, `nofly-polygon-${i}`, '#F33', '禁飞区')
|
|
|
|
|
})
|
2025-06-18 19:58:16 +08:00
|
|
|
|
// 绘制限飞区,添加高度标签
|
2025-06-18 03:59:14 +08:00
|
|
|
|
restrictflyPolygons.forEach((coords, i) => {
|
2025-06-18 19:58:16 +08:00
|
|
|
|
const height = restrictflyHeights[i] || 0
|
|
|
|
|
const label = `限飞高度${height}米`
|
|
|
|
|
this.drawPolygonWithLabel(coords, `restrict-polygon-${i}`, '#F83', label)
|
2025-06-18 03:59:14 +08:00
|
|
|
|
})
|
|
|
|
|
} else {
|
2025-06-18 19:58:16 +08:00
|
|
|
|
// 清除图层
|
2025-06-18 03:59:14 +08:00
|
|
|
|
const layerIds = []
|
|
|
|
|
const sourceIds = []
|
|
|
|
|
|
|
|
|
|
noflyPolygons.forEach((_, i) => {
|
|
|
|
|
layerIds.push(`nofly-polygon-${i}`, `nofly-polygon-${i}-outline`, `nofly-polygon-${i}-label-layer`)
|
|
|
|
|
sourceIds.push(`nofly-polygon-${i}`, `nofly-polygon-${i}-label`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
restrictflyPolygons.forEach((_, i) => {
|
|
|
|
|
layerIds.push(`restrict-polygon-${i}`, `restrict-polygon-${i}-outline`, `restrict-polygon-${i}-label-layer`)
|
|
|
|
|
sourceIds.push(`restrict-polygon-${i}`, `restrict-polygon-${i}-label`)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
this.clearMapElements(layerIds, sourceIds)
|
|
|
|
|
}
|
2025-06-17 23:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
}), 'top-right')
|
|
|
|
|
}
|
2023-09-20 21:33:11 +08:00
|
|
|
|
},
|
2025-06-16 15:22:57 +08:00
|
|
|
|
/**
|
|
|
|
|
* @description 通用地图图层和数据源清除方法
|
|
|
|
|
* @param {Array} layerIds - 要清除的图层ID数组
|
|
|
|
|
* @param {Array} sourceIds - 要清除的数据源ID数组
|
|
|
|
|
*/
|
|
|
|
|
clearMapElements (layerIds = [], sourceIds = []) {
|
|
|
|
|
// 先移除图层(必须先删图层,再删数据源)
|
|
|
|
|
layerIds.forEach(id => {
|
|
|
|
|
if (this.map.getLayer(id)) {
|
|
|
|
|
this.map.removeLayer(id)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 再移除数据源
|
|
|
|
|
sourceIds.forEach(id => {
|
|
|
|
|
if (this.map.getSource(id)) {
|
|
|
|
|
this.map.removeSource(id)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
2023-09-20 21:33:11 +08:00
|
|
|
|
/**
|
|
|
|
|
* @description: 清除地图上的航线
|
|
|
|
|
*/
|
|
|
|
|
clearRoute () {
|
|
|
|
|
// 判断地图上是否已经存在 航线信息 存在先将其清除
|
|
|
|
|
if (this.map.getSource('way_route')) {
|
|
|
|
|
this.map.removeLayer('takeoff_layer')
|
|
|
|
|
this.map.removeSource('takeoff_route')
|
|
|
|
|
this.map.removeLayer('way_route_layer')
|
|
|
|
|
this.map.removeLayer('way_route_layer1')
|
|
|
|
|
this.map.removeSource('way_route')
|
|
|
|
|
this.map.removeLayer('home_point_layer')
|
|
|
|
|
this.map.removeSource('home_point')
|
|
|
|
|
this.map.removeLayer('takeoff_point_layer')
|
|
|
|
|
this.map.removeSource('takeoff_point')
|
2024-09-19 13:09:43 +08:00
|
|
|
|
for (let i = 2; i < this.lonLats.length - 1; i++) {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
this.map.removeLayer('way_point_layer' + i)
|
|
|
|
|
this.map.removeSource('way_point' + i)
|
|
|
|
|
}
|
2024-09-19 13:09:43 +08:00
|
|
|
|
this.lonLats = []
|
|
|
|
|
this.wayLonLats = []
|
|
|
|
|
this.takeoffLonLats = []
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
/**
|
2024-07-25 05:13:30 +08:00
|
|
|
|
* @description: 绘画航线
|
|
|
|
|
* @param {*} routeObj 航点文件对象
|
|
|
|
|
*/
|
2023-09-20 21:33:11 +08:00
|
|
|
|
makeRoute (routeObj) {
|
|
|
|
|
// 初始化清空航线
|
|
|
|
|
this.clearRoute()
|
|
|
|
|
// 获取所有航点 及 非起点所有航点
|
2024-07-10 22:24:08 +08:00
|
|
|
|
routeObj.questAss.tasks.forEach((element, index) => {
|
2024-09-19 13:09:43 +08:00
|
|
|
|
const lonLat = [element.y, element.x, 100]
|
|
|
|
|
this.lonLats.push(lonLat)
|
2024-07-10 22:24:08 +08:00
|
|
|
|
if (index > 1 && index < routeObj.questAss.taskcount - 1) {
|
2024-09-19 13:09:43 +08:00
|
|
|
|
this.wayLonLats.push(lonLat)
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
// 跳转home点位置
|
2024-09-19 13:09:43 +08:00
|
|
|
|
this.goto(this.lonLats[0])
|
2023-09-20 21:33:11 +08:00
|
|
|
|
// 起飞 降落虚线
|
2024-09-19 13:09:43 +08:00
|
|
|
|
if (routeObj.questAss.tasks[this.lonLats.length - 1].command === 20) {
|
|
|
|
|
this.takeoffLonLats.push(this.lonLats[2])
|
|
|
|
|
this.takeoffLonLats.push(this.lonLats[1])
|
|
|
|
|
this.takeoffLonLats.push(this.lonLats[this.lonLats.length - 2])
|
2024-07-25 05:13:30 +08:00
|
|
|
|
} else if (
|
2024-09-19 13:09:43 +08:00
|
|
|
|
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
|
2024-07-25 05:13:30 +08:00
|
|
|
|
) {
|
2024-09-19 13:09:43 +08:00
|
|
|
|
this.takeoffLonLats.push(this.lonLats[2])
|
|
|
|
|
this.takeoffLonLats.push(this.lonLats[1])
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 起飞降落虚线
|
|
|
|
|
this.map.addSource('takeoff_route', {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: {
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
properties: {},
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'LineString',
|
2024-09-19 13:09:43 +08:00
|
|
|
|
coordinates: this.takeoffLonLats
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: 'takeoff_layer',
|
|
|
|
|
type: 'line',
|
|
|
|
|
source: 'takeoff_route',
|
|
|
|
|
layout: {
|
|
|
|
|
'line-join': 'round', // 线连接处的样式
|
2024-07-25 05:13:30 +08:00
|
|
|
|
'line-cap': 'round' // 线结束处的样式
|
2023-09-20 21:33:11 +08:00
|
|
|
|
},
|
|
|
|
|
paint: {
|
|
|
|
|
'line-color': '#fff', // 线的颜色
|
|
|
|
|
'line-width': 2, // 线的宽度
|
|
|
|
|
'line-opacity': 1.0, // 线的透明度
|
2024-07-25 05:13:30 +08:00
|
|
|
|
'line-dasharray': [3, 2] // 虚线
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
// 航线 叠加描边线
|
|
|
|
|
this.map.addSource('way_route', {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: {
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
properties: {},
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'LineString',
|
2024-09-19 13:09:43 +08:00
|
|
|
|
coordinates: this.wayLonLats
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: 'way_route_layer1',
|
|
|
|
|
type: 'line',
|
|
|
|
|
source: 'way_route',
|
|
|
|
|
layout: {
|
|
|
|
|
'line-join': 'round', // 线连接处的样式
|
2024-07-25 05:13:30 +08:00
|
|
|
|
'line-cap': 'round' // 线结束处的样式
|
2023-09-20 21:33:11 +08:00
|
|
|
|
},
|
|
|
|
|
paint: {
|
|
|
|
|
'line-color': '#fff', // 线的颜色
|
|
|
|
|
'line-width': 4, // 线的宽度
|
2024-07-25 05:13:30 +08:00
|
|
|
|
'line-opacity': 1.0 // 线的透明度
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: 'way_route_layer',
|
|
|
|
|
type: 'line',
|
|
|
|
|
source: 'way_route',
|
|
|
|
|
layout: {
|
|
|
|
|
'line-join': 'round', // 线连接处的样式
|
2024-07-25 05:13:30 +08:00
|
|
|
|
'line-cap': 'round' // 线结束处的样式
|
2023-09-20 21:33:11 +08:00
|
|
|
|
},
|
|
|
|
|
paint: {
|
|
|
|
|
'line-color': '#f00', // 线的颜色
|
|
|
|
|
'line-width': 2, // 线的宽度
|
2024-07-25 05:13:30 +08:00
|
|
|
|
'line-opacity': 1.0 // 线的透明度
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
// 航点 图标 PS:home点 起飞点 航点 送餐点等
|
2024-09-19 13:09:43 +08:00
|
|
|
|
this.lonLats.forEach((item, index) => {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
// home点
|
2024-07-25 05:13:30 +08:00
|
|
|
|
if (index === 0) {
|
|
|
|
|
// 第一点home点
|
2023-09-20 21:33:11 +08:00
|
|
|
|
this.map.addSource('home_point', {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: {
|
|
|
|
|
type: 'FeatureCollection',
|
2024-07-25 05:13:30 +08:00
|
|
|
|
features: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'Point',
|
|
|
|
|
coordinates: item
|
|
|
|
|
},
|
|
|
|
|
properties: {
|
|
|
|
|
'marker-symbol': 'homePoint' // home点图标
|
|
|
|
|
}
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
2024-07-25 05:13:30 +08:00
|
|
|
|
]
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: 'home_point_layer',
|
|
|
|
|
type: 'symbol',
|
|
|
|
|
source: 'home_point',
|
|
|
|
|
layout: {
|
|
|
|
|
'icon-image': '{marker-symbol}',
|
|
|
|
|
'icon-size': 1.5, // 图标的大小
|
|
|
|
|
'icon-allow-overlap': true
|
|
|
|
|
}
|
|
|
|
|
})
|
2024-07-25 05:13:30 +08:00
|
|
|
|
} else if (index === 1) {
|
|
|
|
|
// 起飞点
|
2023-09-20 21:33:11 +08:00
|
|
|
|
let takeoffPoint
|
|
|
|
|
// 起点图标设定 根据最后一个航点为RETURN_TO_LAUNCH 20 返回起点降落 或者 最后一个航点为LAND 21 指定点降落
|
2024-09-19 13:09:43 +08:00
|
|
|
|
if (routeObj.questAss.tasks[this.lonLats.length - 1].command === 20) {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
takeoffPoint = 'takeoffLandPoint'
|
2024-07-25 05:13:30 +08:00
|
|
|
|
} else if (
|
2024-09-19 13:09:43 +08:00
|
|
|
|
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
|
2024-07-25 05:13:30 +08:00
|
|
|
|
) {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
takeoffPoint = 'takeoffPoint'
|
|
|
|
|
}
|
|
|
|
|
this.map.addSource('takeoff_point', {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: {
|
|
|
|
|
type: 'FeatureCollection',
|
2024-07-25 05:13:30 +08:00
|
|
|
|
features: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'Point',
|
|
|
|
|
coordinates: item
|
|
|
|
|
},
|
|
|
|
|
properties: {
|
|
|
|
|
'marker-symbol': takeoffPoint // 起飞点图标
|
|
|
|
|
}
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
2024-07-25 05:13:30 +08:00
|
|
|
|
]
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: 'takeoff_point_layer',
|
|
|
|
|
type: 'symbol',
|
|
|
|
|
source: 'takeoff_point',
|
|
|
|
|
layout: {
|
|
|
|
|
'icon-image': '{marker-symbol}',
|
|
|
|
|
'icon-size': 1.5, // 图标的大小
|
|
|
|
|
'icon-allow-overlap': true
|
|
|
|
|
}
|
|
|
|
|
})
|
2024-07-25 05:13:30 +08:00
|
|
|
|
} else {
|
|
|
|
|
// waypoint点
|
2024-09-19 13:09:43 +08:00
|
|
|
|
if (index !== this.lonLats.length - 1) {
|
2024-07-25 05:13:30 +08:00
|
|
|
|
// 最后一个点不显示 要不和最后一个点结合 要不和起点结合
|
2023-09-20 21:33:11 +08:00
|
|
|
|
let wayPoint = 'wayPoint'
|
2024-09-19 13:09:43 +08:00
|
|
|
|
if (index === this.lonLats.length - 2) {
|
2024-07-25 05:13:30 +08:00
|
|
|
|
// 降落点 如果是LAND 21 和最后一个waypoint点结合
|
|
|
|
|
if (
|
2024-09-19 13:09:43 +08:00
|
|
|
|
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
|
2024-07-25 05:13:30 +08:00
|
|
|
|
) {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
wayPoint = 'wayLandPoint'
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-25 05:13:30 +08:00
|
|
|
|
if (routeObj.questAss.tasks[index].command === 94) {
|
|
|
|
|
// command字段为94既然 投放放勾航点 放勾图标
|
2023-09-20 21:33:11 +08:00
|
|
|
|
wayPoint = 'hookPoint'
|
|
|
|
|
}
|
|
|
|
|
this.map.addSource('way_point' + index, {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: {
|
|
|
|
|
type: 'FeatureCollection',
|
2024-07-25 05:13:30 +08:00
|
|
|
|
features: [
|
|
|
|
|
{
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'Point',
|
|
|
|
|
coordinates: item
|
|
|
|
|
},
|
|
|
|
|
properties: {
|
|
|
|
|
'marker-symbol': wayPoint
|
|
|
|
|
}
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
2024-07-25 05:13:30 +08:00
|
|
|
|
]
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: 'way_point_layer' + index,
|
|
|
|
|
type: 'symbol',
|
|
|
|
|
source: 'way_point' + index,
|
|
|
|
|
layout: {
|
|
|
|
|
'icon-image': '{marker-symbol}',
|
|
|
|
|
'icon-size': 1.5, // 图标的大小
|
|
|
|
|
'icon-allow-overlap': true
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
2024-10-11 12:42:56 +08:00
|
|
|
|
/**
|
|
|
|
|
* @abstract 创建指点飞行标记
|
|
|
|
|
* @param {Array} lonLat - 经纬度数组,格式为 [longitude, latitude]
|
|
|
|
|
*/
|
2024-10-11 14:38:46 +08:00
|
|
|
|
makeGuidedMarker (lonLat) {
|
2024-10-11 12:42:56 +08:00
|
|
|
|
// 删除之前的所有标记
|
2024-10-11 14:38:46 +08:00
|
|
|
|
this.delGuidedMarker()
|
|
|
|
|
|
2024-10-11 12:42:56 +08:00
|
|
|
|
// 创建一个标记对象
|
|
|
|
|
this.guidedMarker = new mapboxgl.Marker({
|
|
|
|
|
draggable: false// 关闭拖拽
|
|
|
|
|
})
|
|
|
|
|
.setLngLat(lonLat)
|
2024-10-12 11:36:32 +08:00
|
|
|
|
.setPopup(
|
|
|
|
|
new mapboxgl.Popup({ offset: 32 }).setHTML(
|
|
|
|
|
'<h3>指点</h3>'
|
|
|
|
|
)
|
|
|
|
|
) // 添加弹出窗口
|
2024-10-11 14:38:46 +08:00
|
|
|
|
.addTo(this.map)
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* @abstract 删除指点飞行标记
|
|
|
|
|
*/
|
|
|
|
|
delGuidedMarker () {
|
|
|
|
|
if (this.guidedMarker) {
|
|
|
|
|
this.guidedMarker.remove()
|
|
|
|
|
this.guidedMarker = null // 清除当前标记
|
|
|
|
|
}
|
2024-10-11 12:42:56 +08:00
|
|
|
|
},
|
2023-09-20 21:33:11 +08:00
|
|
|
|
/**
|
2025-06-22 15:05:06 +08:00
|
|
|
|
* @description 清除历史轨迹所有轨迹线和轨迹点的图层与数据源
|
|
|
|
|
* @param {Array<string>} lineLayerIds - 轨迹线图层ID数组
|
|
|
|
|
* @param {Array<string>} pointLayerIds - 轨迹点图层ID数组
|
|
|
|
|
*/
|
|
|
|
|
clearHistoryPaths (lineLayerIds = [], pointLayerIds = []) {
|
|
|
|
|
if (!this.map) return
|
|
|
|
|
|
|
|
|
|
// 清除轨迹线图层及对应数据源
|
|
|
|
|
lineLayerIds.forEach(id => {
|
|
|
|
|
if (this.map.getLayer(id)) {
|
|
|
|
|
this.map.removeLayer(id)
|
|
|
|
|
}
|
|
|
|
|
if (this.map.getSource(id)) {
|
|
|
|
|
this.map.removeSource(id)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 清除轨迹点图层及对应数据源
|
|
|
|
|
pointLayerIds.forEach(id => {
|
|
|
|
|
if (this.map.getLayer(id)) {
|
|
|
|
|
this.map.removeLayer(id)
|
|
|
|
|
}
|
|
|
|
|
if (this.map.getSource(id)) {
|
|
|
|
|
this.map.removeSource(id)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @description 根据索引绘制一条历史轨迹的轨迹线和起点圆点
|
|
|
|
|
* @param {Array<Array<number>>} pathArray - GPS坐标数组,格式为 [[lon, lat, ...], ...]
|
|
|
|
|
* @param {number} index - 用于生成图层ID的索引,确保每条轨迹ID唯一
|
|
|
|
|
*/
|
|
|
|
|
drawHistoryPathByIndex (pathArray, index) {
|
|
|
|
|
if (!this.map) return
|
|
|
|
|
if (!pathArray || pathArray.length === 0) return
|
|
|
|
|
|
|
|
|
|
const zoomThreshold = 12 // 缩放等级阈值,决定显示轨迹线还是起点圆点
|
|
|
|
|
const pointRadius = 6 // 起点圆点半径
|
|
|
|
|
|
|
|
|
|
// 轨迹线GeoJSON对象
|
|
|
|
|
const lineGeojson = {
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'LineString',
|
|
|
|
|
coordinates: pathArray
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 起点圆点GeoJSON对象,取轨迹的第一个坐标点
|
|
|
|
|
const pointGeojson = {
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'Point',
|
|
|
|
|
coordinates: pathArray[0]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 图层ID,保证唯一
|
|
|
|
|
const lineLayerId = `path${index}`
|
|
|
|
|
const pointLayerId = `path${index}-point`
|
|
|
|
|
|
|
|
|
|
// 绘制轨迹线
|
|
|
|
|
const drawLine = () => {
|
|
|
|
|
if (this.map.getSource(lineLayerId)) return
|
|
|
|
|
this.map.addSource(lineLayerId, {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: lineGeojson
|
|
|
|
|
})
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: lineLayerId,
|
|
|
|
|
type: 'line',
|
|
|
|
|
source: lineLayerId,
|
|
|
|
|
layout: {
|
|
|
|
|
'line-join': 'round',
|
|
|
|
|
'line-cap': 'round'
|
|
|
|
|
},
|
|
|
|
|
paint: {
|
|
|
|
|
'line-color': '#3388ff',
|
|
|
|
|
'line-width': 3
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 绘制起点圆点
|
|
|
|
|
const drawPoint = () => {
|
|
|
|
|
if (this.map.getSource(pointLayerId)) return
|
|
|
|
|
this.map.addSource(pointLayerId, {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: pointGeojson
|
|
|
|
|
})
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: pointLayerId,
|
|
|
|
|
type: 'circle',
|
|
|
|
|
source: pointLayerId,
|
|
|
|
|
paint: {
|
|
|
|
|
'circle-radius': pointRadius,
|
|
|
|
|
'circle-color': '#3388ff',
|
|
|
|
|
'circle-stroke-color': '#fff',
|
|
|
|
|
'circle-stroke-width': 2
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 根据缩放等级切换显示轨迹线还是起点圆点
|
|
|
|
|
const updateDisplay = () => {
|
|
|
|
|
const zoom = this.map.getZoom()
|
|
|
|
|
|
|
|
|
|
// 起点圆点一直显示,先确保点图层存在
|
|
|
|
|
if (!this.map.getSource(pointLayerId)) {
|
|
|
|
|
drawPoint()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 轨迹线根据缩放决定显示或隐藏
|
|
|
|
|
if (zoom >= zoomThreshold) {
|
|
|
|
|
// 放大,显示轨迹线
|
|
|
|
|
if (!this.map.getSource(lineLayerId)) {
|
|
|
|
|
drawLine()
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 缩小时隐藏轨迹线
|
|
|
|
|
if (this.map.getLayer(lineLayerId)) {
|
|
|
|
|
this.map.removeLayer(lineLayerId)
|
|
|
|
|
}
|
|
|
|
|
if (this.map.getSource(lineLayerId)) {
|
|
|
|
|
this.map.removeSource(lineLayerId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 首次渲染轨迹显示
|
|
|
|
|
updateDisplay()
|
|
|
|
|
|
|
|
|
|
// 监听缩放事件,动态切换显示
|
|
|
|
|
this.map.on('zoom', () => {
|
|
|
|
|
updateDisplay()
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* @description: 创建飞机轨迹 ps:原理删除之前的轨迹 重新绘制 用于实时轨迹
|
2023-09-20 21:33:11 +08:00
|
|
|
|
* @param {arr} coordinatesArray 飞机经纬高度数组
|
|
|
|
|
*/
|
|
|
|
|
createPathWithArray (coordinatesArray) {
|
|
|
|
|
// 创建一个包含纬度、经度和高度信息的 GeoJSON LineString
|
|
|
|
|
const geojson = {
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
properties: {},
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'LineString',
|
2024-07-25 05:13:30 +08:00
|
|
|
|
coordinates: coordinatesArray.map((coord) => [
|
|
|
|
|
coord[0],
|
|
|
|
|
coord[1],
|
|
|
|
|
coord[2]
|
|
|
|
|
])
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 不是第一次 则改变路径图层里面得数据
|
|
|
|
|
if (this.map.getLayer('path')) {
|
|
|
|
|
this.map.getSource('path').setData(geojson)
|
2024-07-25 05:13:30 +08:00
|
|
|
|
} else {
|
|
|
|
|
// 第一次在地图里添加路径图层
|
2023-09-20 21:33:11 +08:00
|
|
|
|
// 如果坐标数组不为空,创建新路径
|
|
|
|
|
if (coordinatesArray.length > 0) {
|
|
|
|
|
// 添加3D图层
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: 'path',
|
|
|
|
|
type: 'line',
|
|
|
|
|
source: {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: geojson
|
|
|
|
|
},
|
|
|
|
|
layout: {
|
|
|
|
|
'line-cap': 'round',
|
|
|
|
|
'line-join': 'round'
|
|
|
|
|
},
|
|
|
|
|
paint: {
|
|
|
|
|
'line-color': 'purple',
|
2024-07-25 01:05:28 +08:00
|
|
|
|
'line-width': 2
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-06-17 23:17:46 +08:00
|
|
|
|
/**
|
2025-06-18 03:59:14 +08:00
|
|
|
|
* @description:在 Mapbox 地图上绘制一个带轮廓线的多边形区域,并可选添加标签文字。
|
|
|
|
|
*
|
|
|
|
|
* @param {Array<Array<number>>} coords - 多边形的经纬度坐标数组,格式为 [[lng, lat], [lng, lat], ...]
|
|
|
|
|
* @param {string} id - 图层和数据源的唯一标识符前缀,用于创建 source 和 layer 的 ID
|
|
|
|
|
* @param {string} fillColor - 多边形的填充颜色(如 '#F00' 表示红色)
|
|
|
|
|
* @param {string} labelText - 要显示的标签文字(传空则不显示)
|
2025-06-17 23:17:46 +08:00
|
|
|
|
*/
|
2025-06-18 03:59:14 +08:00
|
|
|
|
drawPolygonWithLabel (coords, id, fillColor, labelText) {
|
|
|
|
|
// 添加 GeoJSON 数据源(多边形)
|
|
|
|
|
this.map.addSource(id, {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: {
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'Polygon',
|
|
|
|
|
coordinates: [coords]
|
|
|
|
|
}
|
2025-06-17 23:17:46 +08:00
|
|
|
|
}
|
2025-06-18 03:59:14 +08:00
|
|
|
|
})
|
2025-06-17 23:17:46 +08:00
|
|
|
|
|
2025-06-18 03:59:14 +08:00
|
|
|
|
// 添加填充图层(多边形区域)
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: id,
|
|
|
|
|
type: 'fill',
|
|
|
|
|
source: id,
|
|
|
|
|
paint: {
|
|
|
|
|
'fill-color': fillColor,
|
|
|
|
|
'fill-opacity': 0.3
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-06-17 23:17:46 +08:00
|
|
|
|
|
2025-06-18 03:59:14 +08:00
|
|
|
|
// 添加轮廓图层(边框线)
|
|
|
|
|
this.map.addLayer({
|
|
|
|
|
id: `${id}-outline`,
|
|
|
|
|
type: 'line',
|
|
|
|
|
source: id,
|
|
|
|
|
paint: {
|
|
|
|
|
'line-color': fillColor,
|
|
|
|
|
'line-width': 2
|
2025-06-17 23:17:46 +08:00
|
|
|
|
}
|
2025-06-18 03:59:14 +08:00
|
|
|
|
})
|
2025-06-17 23:17:46 +08:00
|
|
|
|
|
2025-06-18 03:59:14 +08:00
|
|
|
|
// 如果传入了标签文字,则添加文字图层
|
|
|
|
|
if (labelText) {
|
|
|
|
|
// 简单方式计算中心点(可替换为更复杂的质心算法)
|
|
|
|
|
const center = coords.reduce((acc, cur) => {
|
|
|
|
|
acc[0] += cur[0]
|
|
|
|
|
acc[1] += cur[1]
|
|
|
|
|
return acc
|
|
|
|
|
}, [0, 0]).map(v => v / coords.length)
|
2025-06-17 23:17:46 +08:00
|
|
|
|
|
2025-06-18 03:59:14 +08:00
|
|
|
|
// 添加文字专用数据源
|
|
|
|
|
this.map.addSource(`${id}-label`, {
|
|
|
|
|
type: 'geojson',
|
|
|
|
|
data: {
|
|
|
|
|
type: 'Feature',
|
|
|
|
|
geometry: {
|
|
|
|
|
type: 'Point',
|
|
|
|
|
coordinates: center
|
|
|
|
|
},
|
|
|
|
|
properties: {
|
|
|
|
|
label: labelText
|
|
|
|
|
}
|
2025-06-17 23:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2025-06-18 03:59:14 +08:00
|
|
|
|
// 添加文字图层
|
2025-06-17 23:17:46 +08:00
|
|
|
|
this.map.addLayer({
|
2025-06-18 03:59:14 +08:00
|
|
|
|
id: `${id}-label-layer`,
|
|
|
|
|
type: 'symbol',
|
|
|
|
|
source: `${id}-label`,
|
|
|
|
|
layout: {
|
|
|
|
|
'text-field': ['get', 'label'],
|
|
|
|
|
'text-size': 14,
|
|
|
|
|
'text-font': ['Open Sans Bold', 'Arial Unicode MS Bold'],
|
|
|
|
|
'text-offset': [0, 0.6],
|
|
|
|
|
'text-anchor': 'top'
|
|
|
|
|
},
|
2025-06-17 23:17:46 +08:00
|
|
|
|
paint: {
|
2025-06-18 03:59:14 +08:00
|
|
|
|
'text-color': fillColor,
|
|
|
|
|
'text-halo-color': '#fff',
|
|
|
|
|
'text-halo-width': 1.5
|
2025-06-17 23:17:46 +08:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
},
|
2023-09-20 21:33:11 +08:00
|
|
|
|
/**
|
2024-07-25 05:13:30 +08:00
|
|
|
|
* @description: 创建一个飞机
|
|
|
|
|
* @param {*} plane 飞机对象
|
|
|
|
|
*/
|
2023-09-20 21:33:11 +08:00
|
|
|
|
makePlane (plane, index = 0) {
|
|
|
|
|
const customIcon = document.createElement('div')
|
|
|
|
|
customIcon.className = 'custom-marker' // 添加自定义样式类名
|
|
|
|
|
customIcon.style.backgroundImage = `url(${planeIcon})` // 使用引入的 SVG 图标
|
|
|
|
|
customIcon.style.width = '64px' // 图标宽度
|
|
|
|
|
customIcon.style.height = '64px' // 图标高度
|
|
|
|
|
// 创建一个marker对象
|
|
|
|
|
this.planes[index] = new mapboxgl.Marker(customIcon)
|
2024-09-19 13:09:43 +08:00
|
|
|
|
.setLngLat([plane.lon, plane.lat])
|
2024-07-25 05:13:30 +08:00
|
|
|
|
.setPopup(
|
|
|
|
|
new mapboxgl.Popup({ offset: 25 }).setHTML(
|
|
|
|
|
'<h3>' + plane.name + '</h3><hr><p>macID:' + plane.macadd + '</p>'
|
|
|
|
|
)
|
|
|
|
|
) // 添加弹出窗口
|
2023-09-20 21:33:11 +08:00
|
|
|
|
.addTo(this.map)
|
|
|
|
|
},
|
|
|
|
|
/**
|
2024-07-25 05:13:30 +08:00
|
|
|
|
* @description: 移除页面上的所有飞机
|
|
|
|
|
*/
|
2023-09-20 21:33:11 +08:00
|
|
|
|
removePlanes () {
|
2024-07-25 05:13:30 +08:00
|
|
|
|
this.planes.forEach((plane) => {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
plane.remove()
|
|
|
|
|
})
|
|
|
|
|
this.planes = []
|
|
|
|
|
},
|
2025-06-20 15:03:24 +08:00
|
|
|
|
/**
|
|
|
|
|
* @description: 创建 ADSB 飞机图标并添加到地
|
|
|
|
|
* @param {Array} adsbList - ADSB 飞机数据列表
|
|
|
|
|
*/
|
|
|
|
|
makeADSBPlanes (adsbList = []) {
|
|
|
|
|
// 清除旧图标
|
|
|
|
|
if (this.ADSBMarkers && this.ADSBMarkers.length) {
|
|
|
|
|
this.ADSBMarkers.forEach(marker => marker.remove())
|
|
|
|
|
}
|
|
|
|
|
this.ADSBMarkers = []
|
|
|
|
|
|
|
|
|
|
adsbList.forEach((plane) => {
|
|
|
|
|
// 创建图标容器
|
|
|
|
|
const iconWrapper = document.createElement('div')
|
|
|
|
|
iconWrapper.className = 'adsb-wrapper'
|
|
|
|
|
iconWrapper.style.width = '36px'
|
|
|
|
|
iconWrapper.style.height = '36px'
|
|
|
|
|
|
|
|
|
|
// 创建真正的飞机图标元素(子元素旋转!父级无法控制transiton)
|
|
|
|
|
const planeIcon = document.createElement('div')
|
|
|
|
|
planeIcon.className = 'adsb-icon'
|
|
|
|
|
planeIcon.style.backgroundImage = `url(${civilIcon})`
|
|
|
|
|
planeIcon.style.width = '100%'
|
|
|
|
|
planeIcon.style.height = '100%'
|
|
|
|
|
planeIcon.style.backgroundSize = 'contain'
|
|
|
|
|
planeIcon.style.backgroundRepeat = 'no-repeat'
|
|
|
|
|
planeIcon.style.transform = `rotate(${plane.heading}deg)`
|
|
|
|
|
planeIcon.style.transition = 'transform 0.3s linear'
|
|
|
|
|
|
|
|
|
|
// 加入子元素
|
|
|
|
|
iconWrapper.appendChild(planeIcon)
|
|
|
|
|
|
|
|
|
|
const popupContent = `
|
|
|
|
|
<h3>ADSB 飞机</h3>
|
|
|
|
|
<hr>
|
|
|
|
|
<p>ICAO: ${plane.icao}</p>
|
|
|
|
|
<p>高度: ${plane.alt} m</p>
|
|
|
|
|
<p>航向: ${plane.heading}°</p>
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
const marker = new mapboxgl.Marker(iconWrapper)
|
|
|
|
|
.setLngLat([plane.lon, plane.lat])
|
|
|
|
|
.setPopup(new mapboxgl.Popup({ offset: 25 }).setHTML(popupContent))
|
|
|
|
|
.addTo(this.map)
|
|
|
|
|
|
|
|
|
|
this.ADSBMarkers.push(marker)
|
|
|
|
|
})
|
|
|
|
|
},
|
2023-09-20 21:33:11 +08:00
|
|
|
|
/**
|
|
|
|
|
* @description: 实时更新经纬度
|
2024-09-19 13:09:43 +08:00
|
|
|
|
* @param {obj} lonLat lon经度 lat纬度
|
2023-09-20 21:33:11 +08:00
|
|
|
|
* @param {*} index 飞机序号
|
2024-07-25 05:13:30 +08:00
|
|
|
|
* @param {*} pathArr 飞机轨迹 痕迹坐标数组
|
2023-09-20 21:33:11 +08:00
|
|
|
|
*/
|
2024-09-19 13:09:43 +08:00
|
|
|
|
setPlaneLonLat (lonLat, index, pathArr) {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
// 设置新的经纬度
|
|
|
|
|
const plane = this.planes[index]
|
|
|
|
|
if (plane != null) {
|
2024-09-19 13:09:43 +08:00
|
|
|
|
plane.setLngLat([lonLat.lon, lonLat.lat])
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
// 创建轨迹
|
2024-07-25 05:13:30 +08:00
|
|
|
|
this.createPathWithArray(pathArr) // 创建轨迹
|
2023-09-20 21:33:11 +08:00
|
|
|
|
// 镜头跟随飞机
|
2024-07-25 05:13:30 +08:00
|
|
|
|
if (this.isflow) {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
this.map.flyTo({
|
2024-09-19 13:09:43 +08:00
|
|
|
|
center: lonLat,
|
2023-09-20 21:33:11 +08:00
|
|
|
|
speed: 2,
|
|
|
|
|
curve: 1,
|
|
|
|
|
easing (t) {
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
/**
|
2024-07-25 05:13:30 +08:00
|
|
|
|
* @description: 镜头跳转
|
2024-09-19 13:09:43 +08:00
|
|
|
|
* @param {obj} lonLat {lon:lon,lat:lat} 经纬度
|
2025-06-22 15:05:06 +08:00
|
|
|
|
* @param {Number} zoom 地图放大率
|
2024-07-25 05:13:30 +08:00
|
|
|
|
*/
|
2025-06-16 15:22:57 +08:00
|
|
|
|
goto (lonLat, zoom = 18) {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
this.map.flyTo({
|
2024-09-19 13:09:43 +08:00
|
|
|
|
center: lonLat,
|
2025-06-16 15:22:57 +08:00
|
|
|
|
zoom: zoom,
|
2023-09-20 21:33:11 +08:00
|
|
|
|
speed: 2,
|
|
|
|
|
curve: 1,
|
|
|
|
|
easing (t) {
|
|
|
|
|
return t
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
/**
|
2024-07-25 05:13:30 +08:00
|
|
|
|
* @description: 屏幕横移 纵移
|
|
|
|
|
* @param {*} x 正数向左移动 负数向右移动
|
|
|
|
|
* @param {*} y 正数向上移动 负数向下移动
|
|
|
|
|
*/
|
2024-07-23 18:49:49 +08:00
|
|
|
|
mapXOffset (x, y) {
|
|
|
|
|
this.map.panBy([x, y], {
|
2023-09-20 21:33:11 +08:00
|
|
|
|
duration: 333 // 过渡持续时间,以毫秒为单位
|
|
|
|
|
})
|
2024-07-25 01:19:15 +08:00
|
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* @description:重置地图大小
|
|
|
|
|
*/
|
|
|
|
|
handleResize () {
|
|
|
|
|
if (this.map) {
|
2024-07-25 14:40:38 +08:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
this.map.resize()
|
2024-08-09 13:00:02 +08:00
|
|
|
|
}, 200)
|
2024-07-25 01:19:15 +08:00
|
|
|
|
}
|
2023-09-20 21:33:11 +08:00
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
beforeDestroy () {
|
|
|
|
|
if (this.map) {
|
2025-06-17 01:56:19 +08:00
|
|
|
|
this.$store.commit('app/setDefaultLonLat', this.map.getCenter())
|
|
|
|
|
this.$store.commit('app/setDefaultZoom', this.map.getZoom())
|
2024-07-25 05:13:30 +08:00
|
|
|
|
}
|
2024-07-25 13:15:08 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-20 21:33:11 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
#map {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.custom-marker {
|
|
|
|
|
background-size: cover;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
2025-06-03 03:50:56 +08:00
|
|
|
|
|
2025-06-20 15:03:24 +08:00
|
|
|
|
.adsb-wrapper {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.adsb-icon {
|
|
|
|
|
will-change: transform;
|
|
|
|
|
}
|
2023-09-20 21:33:11 +08:00
|
|
|
|
</style>
|