food/src/components/MapBox.vue

688 lines
21 KiB
Vue
Raw Normal View History

2023-09-20 21:33:11 +08:00
<template>
<div id="map">
<slot name="content"></slot>
</div>
</template>
<script>
import mapboxgl from 'mapbox-gl'
import { MapboxStyleSwitcherControl, FollowControl, CustomFullscreenControl } from '@/utils/mapboxgl_plugs'
2023-09-20 21:33:11 +08:00
import planeIcon from '@/assets/svg/plane.svg'
export default {
name: 'MapBox',
data () {
return {
map: null,
planes: [], // 飞机对象
lonLats: [], // 航线 所有航点
wayLonLats: [], // 航线 不包括起点航点
takeoffLonLats: [], // 航线 第一个航点 起点 最后一个航点
isflow: false, // 飞机经纬度变化时是否跟随飞机
currentStyleIndex: 0, // 当前选中的地图样式索引
// 地图样式
mapStyles: [
// mapbox官方样式 卫星地图
{
name: '谷歌卫星',
sprite: this.$store.state.settings.host + '/Public/map/sprite',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
google: {
type: 'raster',
tileSize: 256,
tiles: ['https://sb.im/google-maps/vt?lyrs=s&x={x}&y={y}&z={z}']
}
},
layers: [{ id: 'GoogleRasterLayer', type: 'raster', source: 'google' }]
2023-09-20 21:33:11 +08:00
},
{
name: '必应卫星',
sprite: this.$store.state.settings.host + '/Public/map/sprite',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
bing: {
type: 'raster',
tileSize: 256,
tiles: [
'https://ecn.t3.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=1'
],
attribution: '© Microsoft, Bing Maps'
}
},
layers: [{ id: 'BingRasterLayer', type: 'raster', source: 'bing' }]
},
{
name: 'Mapbox Streets',
sprite: 'mapbox://sprites/mapbox/streets-v11',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
mapbox: {
type: 'raster',
tileSize: 256,
tiles: ['https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=YOUR_MAPBOX_ACCESS_TOKEN']
}
},
layers: [{ id: 'MapboxStreetsLayer', type: 'raster', source: 'mapbox' }]
},
{
name: 'Google Maps',
sprite: this.$store.state.settings.host + '/Public/map/sprite',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
google: {
type: 'raster',
tileSize: 256,
tiles: ['https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}']
}
},
layers: [{ id: 'GoogleMapsLayer', type: 'raster', source: 'google' }]
},
{
name: 'OpenStreetMap',
sprite: this.$store.state.settings.host + '/Public/map/sprite',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
osm: {
type: 'raster',
tileSize: 256,
tiles: ['https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png']
}
},
layers: [{ id: 'OSMRasterLayer', type: 'raster', source: 'osm' }]
},
// 第三方 高德矢量
{
name: '高德矢量',
sprite: this.$store.state.settings.host + '/Public/map/sprite',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
gaode: {
type: 'raster',
tileSize: 256, // 瓦片大小 256 512像素
tiles: [
'http://wprd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}'
]
}
},
layers: [{ id: 'GaodeVectorLayer', type: 'raster', source: 'gaode' }]
},
// 第三方 高德卫星
{
name: '高德卫星',
sprite: this.$store.state.settings.host + '/Public/map/sprite',
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
version: 8,
sources: {
gaode: {
type: 'raster',
tileSize: 256, // 瓦片大小 256 512像素
tiles: [
'https://webst02.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}'
]
}
},
layers: [{ id: 'GaodeRasterLayer', type: 'raster', source: 'gaode' }]
}
]
2023-09-20 21:33:11 +08:00
}
},
props: {
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
}
},
2023-09-20 21:33:11 +08:00
computed: {
defaultLonlat () {
return this.$store.getters['app/getDefaultLonLat']
2023-09-20 21:33:11 +08:00
},
defaultZoom () {
return this.$store.getters['app/getDefaultZoom']
}
},
mounted () {
this.init().then(() => {
// 地图初始化之后
2023-09-20 21:33:11 +08:00
this.map.on('load', () => {
/* 更新样式,添加自定义 sprite */
2023-09-20 21:33:11 +08:00
// 星空背景 大气层
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',// 设置等高线颜色
2023-09-20 21:33:11 +08:00
// 'line-width': 1 // 设置等高线宽度
// }
// })
// 3D地图
2023-09-20 21:33:11 +08:00
// 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 })
})
// 长按事件 传longPress到组件外部调用
let pressTimer
this.map.on('mousedown', (event) => {
// pc端点击事件
pressTimer = setTimeout(() => {
const lonLat = { lon: this.map.unproject(event.point).lng, lat: this.map.unproject(event.point).lat }
// 将经纬度信息传递到组件外部
this.$emit('longPress', lonLat)
}, 1000) // 作为长按的时间阈值
})
this.map.on('touchstart', (event) => {
// 移动端点击事件
pressTimer = setTimeout(() => {
const lonLat = this.map.unproject(event.point)
// 将经纬度信息传递到组件外部
this.$emit('longPress', lonLat)
}, 1000) // 作为长按的时间阈值
})
// 松手时点击事件不够 清除定时器模拟长按事件
const clearPressTimer = () => {
clearTimeout(pressTimer)
}
this.map.on('mouseup', clearPressTimer)
this.map.on('touchend', clearPressTimer)
2023-09-20 21:33:11 +08:00
})
},
methods: {
/**
* @description: 地图初始化
*/
2023-09-20 21:33:11 +08:00
async init () {
// token
mapboxgl.accessToken =
'pk.eyJ1Ijoic3pkb3QiLCJhIjoiY2xhN2pkMWFoMHJ4eTN3cXp6bmlzaHZ0NCJ9.3hH-EAUr0wQCaLvIM2lBMQ'
2023-09-20 21:33:11 +08:00
// 实例化map
this.map = new mapboxgl.Map({
container: 'map',
style: this.mapStyles[0],
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'
})
/* 地图控件 */
// 移动旋转指南针
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.mapStyles, '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')
}
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')
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)
}
this.lonLats = []
this.wayLonLats = []
this.takeoffLonLats = []
2023-09-20 21:33:11 +08:00
}
},
/**
* @description: 绘画航线
* @param {*} routeObj 航点文件对象
*/
2023-09-20 21:33:11 +08:00
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)
2023-09-20 21:33:11 +08:00
}
})
// 跳转home点位置
this.goto(this.lonLats[0])
2023-09-20 21:33:11 +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])
} else if (
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
) {
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',
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', // 线连接处的样式
'line-cap': 'round' // 线结束处的样式
2023-09-20 21:33:11 +08:00
},
paint: {
'line-color': '#fff', // 线的颜色
'line-width': 2, // 线的宽度
'line-opacity': 1.0, // 线的透明度
'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',
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', // 线连接处的样式
'line-cap': 'round' // 线结束处的样式
2023-09-20 21:33:11 +08:00
},
paint: {
'line-color': '#fff', // 线的颜色
'line-width': 4, // 线的宽度
'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', // 线连接处的样式
'line-cap': 'round' // 线结束处的样式
2023-09-20 21:33:11 +08:00
},
paint: {
'line-color': '#f00', // 线的颜色
'line-width': 2, // 线的宽度
'line-opacity': 1.0 // 线的透明度
2023-09-20 21:33:11 +08:00
}
})
// 航点 图标 PS:home点 起飞点 航点 送餐点等
this.lonLats.forEach((item, index) => {
2023-09-20 21:33:11 +08:00
// home点
if (index === 0) {
// 第一点home点
2023-09-20 21:33:11 +08:00
this.map.addSource('home_point', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: item
},
properties: {
'marker-symbol': 'homePoint' // home点图标
}
2023-09-20 21:33:11 +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
}
})
} else if (index === 1) {
// 起飞点
2023-09-20 21:33:11 +08:00
let takeoffPoint
// 起点图标设定 根据最后一个航点为RETURN_TO_LAUNCH 20 返回起点降落 或者 最后一个航点为LAND 21 指定点降落
if (routeObj.questAss.tasks[this.lonLats.length - 1].command === 20) {
2023-09-20 21:33:11 +08:00
takeoffPoint = 'takeoffLandPoint'
} else if (
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
) {
2023-09-20 21:33:11 +08:00
takeoffPoint = 'takeoffPoint'
}
this.map.addSource('takeoff_point', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: item
},
properties: {
'marker-symbol': takeoffPoint // 起飞点图标
}
2023-09-20 21:33:11 +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
}
})
} else {
// waypoint点
if (index !== this.lonLats.length - 1) {
// 最后一个点不显示 要不和最后一个点结合 要不和起点结合
2023-09-20 21:33:11 +08:00
let wayPoint = 'wayPoint'
if (index === this.lonLats.length - 2) {
// 降落点 如果是LAND 21 和最后一个waypoint点结合
if (
routeObj.questAss.tasks[this.lonLats.length - 1].command === 21
) {
2023-09-20 21:33:11 +08:00
wayPoint = 'wayLandPoint'
}
}
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',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: item
},
properties: {
'marker-symbol': wayPoint
}
2023-09-20 21:33:11 +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
}
})
}
}
})
},
/**
* @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]
])
2023-09-20 21:33:11 +08:00
}
}
// 不是第一次 则改变路径图层里面得数据
if (this.map.getLayer('path')) {
this.map.getSource('path').setData(geojson)
} 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',
'line-width': 2
2023-09-20 21:33:11 +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)
.setLngLat([plane.lon, plane.lat])
.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)
},
/**
* @description: 移除页面上的所有飞机
*/
2023-09-20 21:33:11 +08:00
removePlanes () {
this.planes.forEach((plane) => {
2023-09-20 21:33:11 +08:00
plane.remove()
})
this.planes = []
},
/**
* @description: 实时更新经纬度
* @param {obj} lonLat lon经度 lat纬度
2023-09-20 21:33:11 +08:00
* @param {*} index 飞机序号
* @param {*} pathArr 飞机轨迹 痕迹坐标数组
2023-09-20 21:33:11 +08:00
*/
setPlaneLonLat (lonLat, index, pathArr) {
2023-09-20 21:33:11 +08:00
// 设置新的经纬度
const plane = this.planes[index]
if (plane != null) {
plane.setLngLat([lonLat.lon, lonLat.lat])
2023-09-20 21:33:11 +08:00
}
// 创建轨迹
this.createPathWithArray(pathArr) // 创建轨迹
2023-09-20 21:33:11 +08:00
// 镜头跟随飞机
if (this.isflow) {
2023-09-20 21:33:11 +08:00
this.map.flyTo({
center: lonLat,
2023-09-20 21:33:11 +08:00
speed: 2,
curve: 1,
easing (t) {
return t
}
})
}
},
/**
* @description: 镜头跳转
* @param {obj} lonLat {lon:lon,lat:lat} 经纬度
*/
goto (lonLat) {
2023-09-20 21:33:11 +08:00
this.map.flyTo({
center: lonLat,
2023-09-20 21:33:11 +08:00
speed: 2,
curve: 1,
easing (t) {
return t
}
})
},
/**
* @description: 屏幕横移 纵移
* @param {*} x 正数向左移动 负数向右移动
* @param {*} y 正数向上移动 负数向下移动
*/
mapXOffset (x, y) {
this.map.panBy([x, y], {
2023-09-20 21:33:11 +08:00
duration: 333 // 过渡持续时间,以毫秒为单位
})
},
/**
* @description:重置地图大小
*/
handleResize () {
if (this.map) {
setTimeout(() => {
this.map.resize()
}, 200)
}
2023-09-20 21:33:11 +08:00
}
},
beforeDestroy () {
if (this.map) {
this.$store.commit('app/setDefaultLonLat', this.map.getCenter()) // 记录当前经纬度 缺省
this.$store.commit('app/setDefaultZoom', this.map.getZoom()) // 记录当前经纬度 缺省
this.map.remove() // 清除地图资源
}
},
watch: {
}
}
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;
}
</style>