food/src/components/MapBox.vue
air 8d06b53183 【类 型】:feat
【原  因】:1飞行数据删除功能 2导航栏取消添加 对拼等子页面显示 放到列表页显示  3.通过选飞机 直接到对拼页面
 【过  程】:
【影  响】:
2025-06-23 14:07:30 +08:00

1051 lines
33 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">
<slot name="content"></slot>
</div>
</template>
<script>
import mapboxgl from 'mapbox-gl'
import { MapboxStyleSwitcherControl, FollowControl, CustomFullscreenControl, NoFlyControl, RestrictflyControl, SaveToFileControl, PolygonToggleControl } from '@/utils/mapboxgl_plugs'
import planeIcon from '@/assets/svg/plane.svg'
// import unlineIcon from '@/assets/svg/plane_unline.svg'
import civilIcon from '@/assets/svg/civil.svg'
export default {
name: 'MapBox',
data () {
return {
token: 'pk.eyJ1Ijoic3pkb3QiLCJhIjoiY2xhN2pkMWFoMHJ4eTN3cXp6bmlzaHZ0NCJ9.3hH-EAUr0wQCaLvIM2lBMQ',
map: null,
ADSBMarkers: [], // ADSB民航飞机对象
planes: [], // 飞机对象
lonLats: [], // 航线 所有航点
wayLonLats: [], // 航线 不包括起点航点
takeoffLonLats: [], // 航线 第一个航点 起点 最后一个航点
isflow: false, // 飞机经纬度变化时是否跟随飞机
currentStyleIndex: 0, // 当前选中的地图样式索引
guidedMarker: null // 指点飞行地图标记
}
},
props: {
enableGuided: { // 是否开启 飞机点飞功能
type: Boolean,
default: false
},
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
},
enableSaveToFile: { // 开启 保存数据到文件 组件
type: Boolean,
default: false
},
getData: { // 从父级 获取数据
type: Function,
default: null
},
enableNofly: { // 禁飞区 设置 组件
type: Boolean,
default: false
},
enableRestrictfly: { // 限飞区 设置 组件
type: Boolean,
default: false
},
enableShowNofly: { // 显示 禁飞区 限飞区 组件
type: Boolean,
default: false
}
},
computed: {
defaultLonlat () {
return this.$store.getters['app/getDefaultLonLat']
},
defaultZoom () {
return this.$store.getters['app/getDefaultZoom']
},
mapStyleList () {
return this.$store.state.mapStyleList
}
},
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
})
// // 添加等高线图层
// 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')
// 判断地图开启了点飞功能
if (this.enableGuided) {
// 长按事件 传longPress到组件外部调用
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 = {
lon: Number(this.map.unproject(currentPoint).lng.toFixed(7)),
lat: Number(this.map.unproject(currentPoint).lat.toFixed(7))
}
this.$emit('longPress', lonLat) // 将经纬度信息传递到组件外部
this.makeGuidedMarker(lonLat) // 创建标记
}
})
// 触摸屏操作
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) // 将经纬度信息传递到组件外部
this.makeGuidedMarker(lonLat) // 创建标记
}
})
}
})
},
methods: {
/**
* @description: 地图初始化
*/
async init () {
// token
mapboxgl.accessToken = this.token
// 实例化map
this.map = new mapboxgl.Map({
container: 'map',
style: this.mapStyleList[0],
center: this.defaultLonlat,
zoom: this.defaultZoom,
pitch: 0,
bearing: 0,
projection: 'globe'
})
/* 地图控件 */
// 移动旋转指南针
if (this.enblueTranslate) {
this.map.addControl(new mapboxgl.NavigationControl(), 'top-right')
}
// 地图比例
if (this.enblueScale) {
this.map.addControl(new mapboxgl.ScaleControl(), 'bottom-right')
}
// 全屏 并再之后 刷新地图
if (this.enableZoom) {
this.map.addControl(new CustomFullscreenControl(this.handleResize), 'top-right')
}
// 地图样式选择控件
// 自定义地图样式列表
if (this.enableSwitch) {
const styleSwitcherControl = new MapboxStyleSwitcherControl(this.mapStyleList, 'iconfont icon-duozhang f-s-20', this.currentStyleIndex)
this.map.addControl(styleSwitcherControl, 'top-right')
}
// 飞机跟随
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()) // 点击按钮首先直接 跳转到当前飞机对象最后的经纬度
}
}
}), 'top-right')
}
// 保存数据到文件 控件 用于:1.保存历史飞行航线轨迹
if (this.enableSaveToFile && typeof this.getData === 'function') {
this.map.addControl(new SaveToFileControl({
Data: this.getData,
filename: `飞行数据_${Date.now()}.json`,
title: '保存当前数据',
icon: '💾'
}), 'top-left')
}
// 禁飞区 限飞区 设置组件
if (this.enableNofly) {
this.map.addControl(new NoFlyControl({
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)
}
}), 'top-left')
}
// 显示禁飞区 限飞区控件
if (this.enableShowNofly) {
const noflyPolygons = this.$store.state.noflyData[0] || []
const restrictflyPolygons = this.$store.state.noflyData[1] || []
const restrictflyHeights = this.$store.state.noflyData[2] || []
this.map.addControl(new PolygonToggleControl({
defaultIconClass: 'iconfont icon-jinfeiqu_weidianji f-s-20 seatFontColor',
activeIconClass: 'iconfont icon-jinfeiqu f-s-20 brandFontColor',
onToggle: (isActive) => {
if (isActive) {
// 绘制禁飞区
noflyPolygons.forEach((coords, i) => {
this.drawPolygonWithLabel(coords, `nofly-polygon-${i}`, '#F33', '禁飞区')
})
// 绘制限飞区,添加高度标签
restrictflyPolygons.forEach((coords, i) => {
const height = restrictflyHeights[i] || 0
const label = `限飞高度${height}`
this.drawPolygonWithLabel(coords, `restrict-polygon-${i}`, '#F83', label)
})
} else {
// 清除图层
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)
}
}
}), 'top-right')
}
},
/**
* @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)
}
})
},
/**
* @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')
for (let i = 2; i < this.lonLats.length - 1; i++) {
this.map.removeLayer('way_point_layer' + i)
this.map.removeSource('way_point' + i)
}
this.lonLats = []
this.wayLonLats = []
this.takeoffLonLats = []
}
},
/**
* @description: 绘画航线
* @param {*} routeObj 航点文件对象
*/
makeRoute (routeObj) {
// 初始化清空航线
this.clearRoute()
// 获取所有航点 及 非起点所有航点
routeObj.questAss.tasks.forEach((element, index) => {
const lonLat = [element.y, element.x, 100]
this.lonLats.push(lonLat)
if (index > 1 && index < routeObj.questAss.taskcount - 1) {
this.wayLonLats.push(lonLat)
}
})
// 跳转home点位置
this.goto(this.lonLats[0])
// 起飞 降落虚线
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])
} else if (
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
) {
this.takeoffLonLats.push(this.lonLats[2])
this.takeoffLonLats.push(this.lonLats[1])
}
// 起飞降落虚线
this.map.addSource('takeoff_route', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: this.takeoffLonLats
}
}
})
this.map.addLayer({
id: 'takeoff_layer',
type: 'line',
source: 'takeoff_route',
layout: {
'line-join': 'round', // 线连接处的样式
'line-cap': 'round' // 线结束处的样式
},
paint: {
'line-color': '#fff', // 线的颜色
'line-width': 2, // 线的宽度
'line-opacity': 1.0, // 线的透明度
'line-dasharray': [3, 2] // 虚线
}
})
// 航线 叠加描边线
this.map.addSource('way_route', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: this.wayLonLats
}
}
})
this.map.addLayer({
id: 'way_route_layer1',
type: 'line',
source: 'way_route',
layout: {
'line-join': 'round', // 线连接处的样式
'line-cap': 'round' // 线结束处的样式
},
paint: {
'line-color': '#fff', // 线的颜色
'line-width': 4, // 线的宽度
'line-opacity': 1.0 // 线的透明度
}
})
this.map.addLayer({
id: 'way_route_layer',
type: 'line',
source: 'way_route',
layout: {
'line-join': 'round', // 线连接处的样式
'line-cap': 'round' // 线结束处的样式
},
paint: {
'line-color': '#f00', // 线的颜色
'line-width': 2, // 线的宽度
'line-opacity': 1.0 // 线的透明度
}
})
// 航点 图标 PS:home点 起飞点 航点 送餐点等
this.lonLats.forEach((item, index) => {
// home点
if (index === 0) {
// 第一点home点
this.map.addSource('home_point', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: item
},
properties: {
'marker-symbol': 'homePoint' // home点图标
}
}
]
}
})
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
}
})
} else if (index === 1) {
// 起飞点
let takeoffPoint
// 起点图标设定 根据最后一个航点为RETURN_TO_LAUNCH 20 返回起点降落 或者 最后一个航点为LAND 21 指定点降落
if (routeObj.questAss.tasks[this.lonLats.length - 1].command === 20) {
takeoffPoint = 'takeoffLandPoint'
} else if (
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
) {
takeoffPoint = 'takeoffPoint'
}
this.map.addSource('takeoff_point', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: item
},
properties: {
'marker-symbol': takeoffPoint // 起飞点图标
}
}
]
}
})
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
}
})
} else {
// waypoint点
if (index !== this.lonLats.length - 1) {
// 最后一个点不显示 要不和最后一个点结合 要不和起点结合
let wayPoint = 'wayPoint'
if (index === this.lonLats.length - 2) {
// 降落点 如果是LAND 21 和最后一个waypoint点结合
if (
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
) {
wayPoint = 'wayLandPoint'
}
}
if (routeObj.questAss.tasks[index].command === 94) {
// command字段为94既然 投放放勾航点 放勾图标
wayPoint = 'hookPoint'
}
this.map.addSource('way_point' + index, {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: item
},
properties: {
'marker-symbol': wayPoint
}
}
]
}
})
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
}
})
}
}
})
},
/**
* @abstract 创建指点飞行标记
* @param {Array} lonLat - 经纬度数组,格式为 [longitude, latitude]
*/
makeGuidedMarker (lonLat) {
// 删除之前的所有标记
this.delGuidedMarker()
// 创建一个标记对象
this.guidedMarker = new mapboxgl.Marker({
draggable: false// 关闭拖拽
})
.setLngLat(lonLat)
.setPopup(
new mapboxgl.Popup({ offset: 32 }).setHTML(
'<h3>指点</h3>'
)
) // 添加弹出窗口
.addTo(this.map)
},
/**
* @abstract 删除指点飞行标记
*/
delGuidedMarker () {
if (this.guidedMarker) {
this.guidedMarker.remove()
this.guidedMarker = null // 清除当前标记
}
},
/**
* @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:原理删除之前的轨迹 重新绘制 用于实时轨迹
* @param {arr} coordinatesArray 飞机经纬高度数组
*/
createPathWithArray (coordinatesArray) {
// 创建一个包含纬度、经度和高度信息的 GeoJSON LineString
const geojson = {
type: 'Feature',
properties: {},
geometry: {
type: 'LineString',
coordinates: coordinatesArray.map((coord) => [
coord[0],
coord[1],
coord[2]
])
}
}
// 不是第一次 则改变路径图层里面得数据
if (this.map.getLayer('path')) {
this.map.getSource('path').setData(geojson)
} else {
// 第一次在地图里添加路径图层
// 如果坐标数组不为空,创建新路径
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',
'line-width': 2
}
})
}
}
},
/**
* @description:在 Mapbox 地图上绘制一个带轮廓线的多边形区域,并可选添加标签文字。
*
* @param {Array<Array<number>>} coords - 多边形的经纬度坐标数组,格式为 [[lng, lat], [lng, lat], ...]
* @param {string} id - 图层和数据源的唯一标识符前缀,用于创建 source 和 layer 的 ID
* @param {string} fillColor - 多边形的填充颜色(如 '#F00' 表示红色)
* @param {string} labelText - 要显示的标签文字(传空则不显示)
*/
drawPolygonWithLabel (coords, id, fillColor, labelText) {
// 添加 GeoJSON 数据源(多边形)
this.map.addSource(id, {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Polygon',
coordinates: [coords]
}
}
})
// 添加填充图层(多边形区域)
this.map.addLayer({
id: id,
type: 'fill',
source: id,
paint: {
'fill-color': fillColor,
'fill-opacity': 0.3
}
})
// 添加轮廓图层(边框线)
this.map.addLayer({
id: `${id}-outline`,
type: 'line',
source: id,
paint: {
'line-color': fillColor,
'line-width': 2
}
})
// 如果传入了标签文字,则添加文字图层
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)
// 添加文字专用数据源
this.map.addSource(`${id}-label`, {
type: 'geojson',
data: {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: center
},
properties: {
label: labelText
}
}
})
// 添加文字图层
this.map.addLayer({
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'
},
paint: {
'text-color': fillColor,
'text-halo-color': '#fff',
'text-halo-width': 1.5
}
})
}
},
/**
* @description: 创建一个飞机
* @param {*} plane 飞机对象
*/
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)
.setLngLat([plane.lon, plane.lat])
.setPopup(
new mapboxgl.Popup({ offset: 25 }).setHTML(
'<h3>' + plane.name + '</h3><hr><p>macID:' + plane.macadd + '</p>'
)
) // 添加弹出窗口
.addTo(this.map)
},
/**
* @description: 移除页面上的所有飞机
*/
removePlanes () {
this.planes.forEach((plane) => {
plane.remove()
})
this.planes = []
},
/**
* @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)
})
},
/**
* @description: 实时更新经纬度
* @param {obj} lonLat lon经度 lat纬度
* @param {*} index 飞机序号
* @param {*} pathArr 飞机轨迹 痕迹坐标数组
*/
setPlaneLonLat (lonLat, index, pathArr) {
// 设置新的经纬度
const plane = this.planes[index]
if (plane != null) {
plane.setLngLat([lonLat.lon, lonLat.lat])
}
// 创建轨迹
this.createPathWithArray(pathArr) // 创建轨迹
// 镜头跟随飞机
if (this.isflow) {
this.map.flyTo({
center: lonLat,
speed: 2,
curve: 1,
easing (t) {
return t
}
})
}
},
/**
* @description: 镜头跳转
* @param {obj} lonLat {lon:lon,lat:lat} 经纬度
* @param {Number} zoom 地图放大率
*/
goto (lonLat, zoom = 12) {
this.map.flyTo({
center: lonLat,
zoom: zoom,
speed: 2,
curve: 1,
easing (t) {
return t
}
})
},
/**
* @description: 屏幕横移 纵移
* @param {*} x 正数向左移动 负数向右移动
* @param {*} y 正数向上移动 负数向下移动
*/
mapXOffset (x, y) {
this.map.panBy([x, y], {
duration: 333 // 过渡持续时间,以毫秒为单位
})
},
/**
* @description:重置地图大小
*/
handleResize () {
if (this.map) {
setTimeout(() => {
this.map.resize()
}, 200)
}
}
},
beforeDestroy () {
if (this.map) {
this.$store.commit('app/setDefaultLonLat', this.map.getCenter())
this.$store.commit('app/setDefaultZoom', this.map.getZoom())
}
}
}
</script>
<style lang="scss" scoped>
#map {
width: 100%;
height: 100%;
overflow: hidden;
}
.custom-marker {
background-size: cover;
cursor: pointer;
}
.adsb-wrapper {
display: flex;
justify-content: center;
align-items: center;
}
.adsb-icon {
will-change: transform;
}
</style>