前言
最近实现了一个业务需求:根据船讯网给的数据,绘制船舶的行进轨迹。 进行轨迹的绘制,百度地图(vue-baidu-map)和高德地图(vue-amap)都提供了可用的API。起初选择了百度地图,但是无法很好的解决跨180°经线衔接的问题,进行分段绘制后,轨迹仍然不能够衔接起来,所以最后选择了高德地图进行了实现。
实现思想
实现中需要解决的两个关键问题是:
- 轨迹跨180°经线衔接问题;
- 海量点进行描点处理严重卡顿问题
针对这两个问题的解决方案:
- 跨180°经线问题:分段绘制。从180°经线处插入分割点,然后根据分割点,分东西半球进行绘制(注意:百度地图使用此方案后,东西半球两段轨迹线不能连接在一起,[-180°, 0°]和[0°, 180°]这两个范围的轨迹线在视图中不能共存),代码如下:
const list = [ .... ] // 轨迹点坐标
const pois = [] // 存储坐标点(包含分割点)
const splitIndex = [0] // 标记分隔位置
list.map((item, index) => {
// 将坐标转换为高德坐标
const curLngLat = new AMap.LngLat(item.lon, item.lat)
pois.push(curLngLat)
// 跨过180°经线插入分割点
if (index + 1 < list.length) {
if (item.lon > 0 && list[index + 1].lon < 0 && item.lon > 170) {
pois.push(new AMap.LngLat(180, item.lat))
pois.push(new AMap.LngLat(-180, item.lat))
splitIndex.push(index + 2)
}
if (item.lon < 0 && list[index + 1].lon > 0 && list[index + 1].lon > 170) {
pois.push(new AMap.LngLat(-180, item.lat))
pois.push(new AMap.LngLat(180, item.lat))
splitIndex.push(index + 2)
}
}
})
// 分段绘制轨迹
splitIndex.forEach((item, index) => {
const polyline = new AMap.Polyline({
path: pois.slice(item, splitIndex[index + 1]),
showDir: true,
strokeWeight: 8, // 折线的宽度,以像素为单位
strokeOpacity: 0.8, // 折线的透明度,取值范围0 - 1
strokeColor: '#FB991C', // 折线颜色
lineJoin: 'round'
})
map.add(polyline)
})
- 卡顿问题:海量点聚合。高德API中提供了海量点聚合的处理方式,有效避免了加载大于3000个点时出现卡顿情况,并且能够根据地图级别来显示轨迹上的点。
const markerPoints = [] // 轨迹上的点
list.map((item, index) => {
// 将坐标转换为高德坐标
const curLngLat = new AMap.LngLat(item.lon, item.lat)
// 创建marker
const marker = new AMap.Marker({
position: curLngLat,
icon: require('@/assets/images/ship.png'),
info: item.timestampStr,
size: new AMap.Size(64, 72),
zIndex: 500
})
markerPoints.push(marker)
// 显示节点信息
_this.handleInfoShow(marker, item.timestampStr)
})
// 定义轨迹上的点的样式
const _renderClusterMarker = context => {
// 显示聚合点中第一个点的位置
context.marker.setContent('<img src="' + require('@/assets/images/ship.png') + '" width="64" height="64">')
// 显示节点信息
_this.handleInfoShow(context.marker, context.markers[0].De.info)
}
// 聚合点,绘制轨迹上的点
AMap.plugin(['AMap.MarkerClusterer'], () => {
new AMap.MarkerClusterer(map, markerPoints, {
gridSize: 80,
averageCenter: false, // 显示聚合点中第一个点的位置
renderClusterMarker: _renderClusterMarker // 自定义聚合点的样式
})
})
具体实现
- 安装依赖
npm install vue-amap --save
- 申请API key,在项目中注册
AMapLoader.load({
key: '你申请的key',
plugins: ['AMap.Scale', 'AMap.OverView', 'AMap.ToolBar', 'AMap.PolyEditor'] // 需要使用的的插件列表
}).then(AMap => {
this.initMap(AMap) // 绘制轨迹
})
- 完整代码
<template>
<div>
<div id="map-container" />
</div>
</template>
<script>
import AMapLoader from '@amap/amap-jsapi-loader'
export default {
name: 'Map',
props: {
mapInfo: { type: Object, default: null }
},
data() {
return {
AMap: null,
center: { lon: 160.2222222, lat: 20.222222 },
zoom: 3,
pointList: [
// { lon: 160.2222222, lat: 21.222222 },
// { lon: 162.2222222, lat: 22.222222 },
// { lon: 165.2222222, lat: 23.222222 },
// { lon: 168.2222222, lat: 24.222222 },
// { lon: 170.2222222, lat: 23.332222 },
// { lon: 175.2222222, lat: 22.222222 },
// { lon: 177.2222222, lat: 22.002222 },
// { lon: 178.2222222, lat: 21.222222 },
// { lon: -179.2222222, lat: 20.222222 },
// { lon: -178.2222222, lat: 19.222222 },
// { lon: -170.2222222, lat: 18.222222 },
// { lon: -165.2222222, lat: 19.222222 },
// { lon: -160.2222222, lat: 20.222222 }
]
}
},
mounted() {
// 加载地图
AMapLoader.load({
key: 'c3a30011fff38e674d7348bc416faecb', // 申请好的Web端开发者Key,首次调用 load 时必填
plugins: [
'AMap.Scale',
'AMap.OverView',
'AMap.ToolBar',
'AMap.PolyEditor'
] // 需要使用的的插件列表
}).then(AMap => {
this.AMap = AMap
this.initMap(AMap) // 绘制轨迹
})
},
methods: {
initMap(AMap) {
const _this = this
if (_this.pointList?.length > 0) {
_this.center = _this.mapInfo.pointList[0]
}
const map = new AMap.Map('map-container', {
resizeEnable: true,
center: new AMap.LngLat(_this.center.lon, _this.center.lat), // 初始化地图中心点位置
zoom: _this.zoom // 初始化地图级别
})
map.addControl(new AMap.ToolBar()) // 开启工具条
map.addControl(new AMap.Scale()) // 开启比例尺
map.clearMap() // 清除覆盖物
// 没有数据点,不进行绘制
if (_this.pointList?.length === 0) {
return
}
const list = _this.pointList // 轨迹点
const pois = [] // 存储坐标点(包含分割点)
const splitIndex = [0] // 标记分隔位置
const markerPoints = [] // 轨迹上的点
list.map((item, index) => {
// 将坐标转换为高德坐标
const curLngLat = new AMap.LngLat(item.lon, item.lat)
pois.push(curLngLat)
// 跨过180°经线插入分割点
if (index + 1 < list.length) {
if (item.lon > 0 && list[index + 1].lon < 0 && item.lon > 170) {
pois.push(new AMap.LngLat(180, item.lat))
pois.push(new AMap.LngLat(-180, item.lat))
splitIndex.push(index + 2)
}
if (item.lon < 0 && list[index + 1].lon > 0 && list[index + 1].lon > 170) {
pois.push(new AMap.LngLat(-180, item.lat))
pois.push(new AMap.LngLat(180, item.lat))
splitIndex.push(index + 2)
}
}
// 生成marker, 放入markerPoints
// 起点和终点,不放入轨迹上的点,直接显示
if (index === 0 || index === list.length - 1) {
return
}
// 创建marker
const marker = new AMap.Marker({
position: curLngLat,
icon: require('@/assets/images/ship.png'),
info: item.timestampStr,
size: new AMap.Size(64, 72),
zIndex: 500
})
markerPoints.push(marker)
// 显示节点信息
_this.handleInfoShow(marker, item.timestampStr)
})
// 分段绘制轨迹
splitIndex.forEach((item, index) => {
const polyline = new AMap.Polyline({
path: pois.slice(item, splitIndex[index + 1]),
showDir: true,
strokeWeight: 8, // 折线的宽度,以像素为单位
strokeOpacity: 0.8, // 折线的透明度,取值范围0 - 1
strokeColor: '#FB991C', // 折线颜色
lineJoin: 'round'
})
map.add(polyline)
})
// 定义轨迹上的点的样式
const _renderClusterMarker = context => {
// 显示聚合点中第一个点的位置
context.marker.setContent('<img src="' + require('@/assets/images/ship.png') + '" width="64" height="64">')
// 显示节点信息
_this.handleInfoShow(context.marker, context.markers[0].De.info)
}
// 聚合点,绘制轨迹上的点
AMap.plugin(['AMap.MarkerClusterer'], () => {
new AMap.MarkerClusterer(map, markerPoints, {
gridSize: 80,
averageCenter: false, // 显示聚合点中第一个点的位置
renderClusterMarker: _renderClusterMarker // 自定义聚合点的样式
})
})
// 添加起点描述
const start = list[0]
const startMark = _this.createMarker(start, require('@/assets/images/start.png'), '起运港', _this.mapInfo.departurePort, start.timestampStr)
map.add(startMark)
// 添加终点描述
const end = list[list.length - 1]
const endMark = _this.createMarker(end, require('@/assets/images/ship.png'), '目的港', _this.mapInfo.arrivalPort, end.timestampStr)
map.add(endMark)
},
// 创建marker
createMarker(point, iconUrl, title, tValue, time) {
const marker = new this.AMap.Marker({
position: new this.AMap.LngLat(point.lon, point.lat),
icon: iconUrl,
label: {
direction: 'top',
content: `
<div style="padding:5px;font-size:12px;">
<div style="padding-bottom:5px">${title || ''}:${tValue || ''}</div>
<div>时 间:${time || ''}</div>
</div>
`
},
zIndex: 1000
})
return marker
},
// 信息显示与隐藏
handleInfoShow(marker, info) {
// 鼠标滑过,显示信息
marker.on('mouseover', e => {
e.target.setLabel({
direction: 'top',
content: `<div>${info || ''}</div>`
})
})
// 鼠标移出,隐藏信息
marker.on('mouseout', e => {
e.target.setLabel(null)
})
}
}
}
</script>
<style lang="scss">
#map-container {
width: 100%;
height: 370px;
}
.amap-marker-label{
border: none;
border-radius: 3px;
padding: 8px;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
background-color: #fff;
text-align: left;
font-size: 12px;
}
.amap-logo{
display: none;
opacity:0 !important;
}
.amap-copyright {
opacity:0;
}
</style>
- 实现效果