共计 11434 个字符,预计需要花费 29 分钟才能阅读完成。
需求背景
1、查看某个地区所有业务员的坐标
2、点击业务员坐标可展示具体信息、当前具体地址
3、点击某个业务员名字,自动关联到地图上的位置
4、查看业务员当天的行程轨迹
5、动态切换地图上坐标的自定义图标
6、支持展示所有业务员在可视区内(自适应缩放地图比例,展示所有业务员位置)
之所以没有选择用 vue-baidu-map,是因为有 bug。。。
如果你之前没有接触过地图方面的开发,建议先去官网熟悉一下 api。
百度地图 API(javascript API)
具体的参考类
大概会用到以下几个 api:
- 账号和秘钥获取
- 初始化地图
- 自定义地图样式
- 添加控件
- 添加标注
- 添加信息窗口
- 地图事件处理
- 出行路线规划
- 坐标系之间的转换
先看一下最终的效果图
显示轨迹的模块
项目是 vue 项目 我贴一下 map.vue 的文件代码
html 代码????
只需要一个显示地图的 div
<template>
<div class="main">
<div id="container" class="bm-view">
</div>
<div class="btn__group">
<button @click="changeType('all')" :class="{'active': item ==='all'}"> 全部 </button>
<button @click="changeType('path')" :class="{'active': item ==='path'}"
v-show="currentItem==='car'"> 轨迹 </button>
</div>
</div>
</template>
js 部分????
<script>
import mapStyle from '@/assets/mapStyleJson.json'
import {drawTrack} from '@/libs/util.js'
export default {
name: 'map-module',
props: {
currentItem: {
type: String,
default: ''
}
},
data() {
return {stewardCoordList: [],
zoom: 10,
// 地图样式
mapStyle: {styleJson: mapStyle},
currentCity: '',
myMap: null,
item: '',
mapShow: true,
infoWindow: null,
trackList: []}
},
watch: {
list: {handler(newVal, oldVal) {if (!newVal.length) {this.myMap.clearOverlays()
return
}
this.stewardCoordList = newVal
// this.currentItem === 'car' && (this.item = 'all')
let translateList = []
this.stewardCoordList.forEach(e => {
// 数组中加 icon 的 url
e.url = this.currentItem === 'car' ? (!e.currentOrder.length ? ('/static/img/normal.png') : ('/static/img/warning.png')) : (e.currentOrder.length ?
('/static/img/servicer-busy.svg') : ('/static/img/servicer-free.svg'))
// 需要转换的坐标数组
translateList.push(new BMap.Point(e.geo.lng, e.geo.lat))
})
let convertor = new BMap.Convertor();
let myGeo = new BMap.Geocoder();
let that = this;
// 百度地图经纬度解析成具体地址
let geocodeSearch = (pt, item) => {myGeo.getLocation(pt, function (rs) {if (rs !== null) item.address = rs.address
});
}
let pageSize = 10,
pageCount = translateList.length % 10 === 0 ? Math.floor(translateList.length / 10) : Math
.floor((translateList.length / 10) + 1);
for (let i = 0; i < pageCount; i++) {
let startIndex = pageSize * i;
let sliceArr = translateList.slice(startIndex, pageSize * (i + 1));
convertor.translate(sliceArr, 1, 5, function (data) {if (data.status === 0) {for (let i = 0; i < data.points.length; i++) {const el = data.points[i];
that.stewardCoordList[startIndex + i].geo.lng = el.lng
that.stewardCoordList[startIndex + i].geo.lat = el.lat
geocodeSearch(sliceArr[i], that.stewardCoordList[startIndex + i])
}
}
// 添加图标 marker
setTimeout(() => {that.addMarker(that.stewardCoordList)
}, 100);
})
}
},
deep: true
},
currentItem(val) {this.item = val === 'car' ? 'all' : ''}
},
computed: {list() {return this.$store.getters.getter_stewardCoordList},
geo() {
return this.stewardCoordList.map(e => {return new BMap.Point(e.geo.lng, e.geo.lat)
})
},
},
mounted() {this.initMap()
// 接收其他组件传过来的业务员坐标(是一个数组)这里实现的功能是:获取业务员的轨迹、自动移动地图当前可视区,定位到小哥的当前位置
// 兄弟组件的事件分发 this.$emit('getCurpos', { arr}) 注意:这里传过来的数组坐标是转成百度地图坐标之后的
this.$bus.$on('getCurPos', async val => {if (!this.item || this.item === 'all') { // 查看业务员坐标
// 这里重新渲染车和人的图标要写成异步的
await this.addMarker(this.stewardCoordList)
setTimeout(() => {this.myMap.centerAndZoom(new BMap.Point(val.arr[0], val.arr[1]), 18);
}, 300);
} else { // 查看轨迹
await this.getDataLoc(val.account)
let translateList = []
if (this.trackList && this.trackList.length) {this.trackList.forEach(e => translateList.push(new BMap.Point(e.lng, e.lat)))
}
let convertor = new BMap.Convertor();
let myGeo = new BMap.Geocoder();
let that = this;
let pageSize = 10,
pageCount = translateList.length % 10 === 0 ? Math.floor(translateList.length /
10) : Math
.floor((translateList.length / 10) + 1);
// 这里是因为百度地图每天的计算量是有限的,所以就分批处理了
for (let i = 0; i < pageCount; i++) {
let startIndex = pageSize * i;
let sliceArr = translateList.slice(startIndex, pageSize * (i + 1));
convertor.translate(sliceArr, 1, 5, function (data) {if (data.status === 0) {for (let i = 0; i < data.points.length; i++) {const el = data.points[i];
that.trackList[startIndex + i].lng = el.lng
that.trackList[startIndex + i].lat = el.lat
}
}
setTimeout(() => {drawTrack(that.myMap, that.trackList, val.name)
}, 100);
})
}
}
})
this.$nextTick(this.setMapHeight())
},
methods: {initMap() {
// 初始化地图
this.myMap = new BMap.Map('container', {enableMapClick: false});
let lng,
lat;
if (this.list.length) {lng = this.list[0].geo.lng,
lat = this.list[0].geo.lat;
// 设置地图中心点以及 zoom 值
this.myMap.centerAndZoom(new BMap.Point(lng, lat), 12);
} else{
// 设置地图默认中心点
this.myMap.centerAndZoom(new BMap.Point(116.406605, 39.921585), 10);
}
// 是否允许鼠标滚轮缩放
// this.myMap.enableScrollWheelZoom(false);
this.myMap.addControl(new BMap.NavigationControl({offset: new BMap.Size(10, 45)
}))
// this.myMap.addControl(new BMap.OverviewMapControl())
// 个性化地图样式
this.myMap.setMapStyle({styleJson: mapStyle});
},
// 获取每个业务员轨迹坐标 数据由后端接口提供
getDataLoc(account) {return api.***(account).then(res => {
const data = res.data;
if (data.resultCode === '0000') {
this.trackList = data.resultData.map(v => {
return {
lng: '' + v.longitude,
lat: '' + v.latitude
}
})
}
})
},
addMarker(arr) {
new Promise(_ => {
let that = this
// 加标注之前,先清空之前的标注。避免数据更新之后,之前的标注还在
that.myMap.clearOverlays();
if (arr != undefined && arr.length > 0 && that.myMap !== null) {
let len = arr.length,
content,
marker,
markers = []
for (let i = 0; i < len; i++) {
// 自定义 mark 图标
let myIcon = new BMap.Icon(arr[i].url, new BMap.Size(30, 26), {
// 指定定位位置
offset: new BMap.Size(10, 25),
// anchor: new BMap.Size(10, 30)
// 当需要从一幅较大的图片中截取某部分作为标注图标时,需要指定大图的偏移位置
// imageOffset: new BMap.Size(0, 0 - i * 25) // 设置图片偏移
});
let point = new BMap.Point(arr[i].geo.lng, arr[i].geo.lat);
// 创建标注对象并添加到地图
marker = new BMap.Marker(point, {icon: myIcon});
// 给图标添加事件
marker.addEventListener('click', function (e) {let item = arr[i],
html = ''
// 以下是构造信息窗口里的内容 具体的内容按照你们的需求来
const infoHtml = (!item.currentOrder.length) ? e => `
<p>${item.name} <span style="margin-left:5px;">${item.userMobile}</span><span style="color:#00bebebe;margin-left:5px;">${'空闲'}</span></p><br>
` : e => `
<p>${item.name} <span style="margin-left:5px;">${item.userMobile}</span><span style="color:#f00;margin-left:5px;">${'***'}</span></p><br>
`
const orderHtml = (item.currentOrder.length) ? (
that.currentItem === 'car' ? addrs => `
<div>
<p style="font-weight:bold;">*** 信息:</p>
${item.currentOrder.map(info => `
<div>
<p><span style="margin-right:6px;">${info.startDate}</span><sapn>${info.endData === null ? '' : info.endData}</sapn></p>
<p> 信息 1:${info.soType}</p>
<p> 信息 2:${info.so}</p>
<p> 信息 3:${info.cusName}</p>
<p> 信息 4:${info.vin}</p>
</div>
`)}
</div><br>
` : addrs => `
<div>
<p style="font-weight:bold;">*** 信息:</p>
${item.currentOrder.map(info => `
<div>
<p> 信息 1:${info.type}</p>
<p> 信息 2:${info.so}</p>
<p> 信息 3:${info.cusName}</p>
</div>
`)}
</div><br>
`
) : addrs => `<p></p>`
//------------------------ 结束 -----------------
html = `<div class="info__box">
${infoHtml()}
${orderHtml()}
<p style="font-weight:bold;"> 当前位置:<span style="font-weight:normal;">${item.address}</span></p>
</div>`
// 打开信息窗口
openInfo(html, point)
}, false);
markers.push(marker)
that.myMap.addOverlay(marker);
that.myMap.setViewport(that.geo)
};
} else if (!arr.length) {that.myMap.clearOverlays();
// that.myMap.centerAndZoom(that.formItem.cityName, 10)
}
// 开启信息窗口
function openInfo(content, point) {
var opts = {
boxStyle: {
width: "280px",
minHeight: "195px",
opacity: "0.8",
},
enableAutoPan: true,
// title: '信息',
closeIconUrl: close,
closeIconMargin: '0px',
closeIconZIndex: 1,
closeIconWidth: '15px'
}
// let point = new BMap.Point(e.target.point.lng, e.target.point.lat);
let infoWindow = new BMap.InfoWindow(content, opts); // 创建信息窗口对象
that.myMap.openInfoWindow(infoWindow, point); // 开启信息窗口
return false
}
})
},
async changeType(e) {
this.item = e;
this.myMap.clearOverlays();
if (this.geo.length === 1) {
let list = this.stewardCoordList;
this.myMap.centerAndZoom(new BMap.Point(list[0].geo.lng, list[0].geo.lat), 18);
} else {if (e === 'all') {await this.addMarker(this.stewardCoordList)
this.myMap.setViewport(this.geo)
}
}
},
// 设置地图高度
setMapHeight() {let boxHeight = document.querySelector('.content-wrapper').offsetHeight;
// document.querySelector('.bm-view').style.height = boxHeight * 2 + 'px'
},
}
}
</script>
绘制轨迹的方法????
export const drawTrack = (map, arr, name) => {
// 先清空之前的 marker
map.clearOverlays();
let driving = new BMap.DrivingRoute(map); // 创建轨迹实例
// 生成坐标点
let trackPoint = arr.map(v => new BMap.Point(''+ v.lng,'' + v.lat))
for (let i = 0, len = trackPoint.length; i < len; i++) {if (i != trackPoint.length - 1) {driving.search(trackPoint[i], trackPoint[i + 1]);
}
}
// icon 的参数
let size = new BMap.Size(28, 32),
offset = new BMap.Size(0, -13),
imageSize = new BMap.Size(28, 32),
icon = new BMap.Icon("/static/car.png", size, {imageSize: imageSize});
driving.setSearchCompleteCallback(() => {let pts = driving.getResults().getPlan(0).getRoute(0)
.getPath(); // 通过轨迹实例,获得一系列点的数组
let polyline = new BMap.Polyline(pts, {
strokeColor: '#00bebebe',
strokeOpacity: 1,
});
map.addOverlay(polyline);
let labStyle = {
minWidth: '28px',
height: '28px',
lineHeight: '28px',
textAlign: 'center',
color: '#FFF',
fontSize: '12px',
backgroundColor: 'none',
border: '1px solid #00bebebe',
borderRadius: '4px',
}
// 画图标、想要展示的起点终点途经点
for (let i = 0, len = trackPoint.length; i < len; i++) {let marker = new BMap.Marker(trackPoint[i], {
icon: icon,
offset: offset
});
let lab,
nameLab;
if (i == 0) {
lab = new BMap.Label("起点", {position: trackPoint[i]
});
lab.setStyle(labStyle)
} else if (i == trackPoint.length - 1) {
lab = new BMap.Label("终点", {position: trackPoint[i]
});
lab.setStyle(labStyle)
// 添加一个名字 label
nameLab = new BMap.Label(name, {offset:new BMap.Size(0,-30),
position: trackPoint[i]
})
nameLab.setStyle(labStyle)
}
map.addOverlay(lab);
map.addOverlay(nameLab);
}
map.setViewport(trackPoint);
});
}
mapStyleJson.json
的内容我也贴出来
[{
"featureType": "water",
"elementType": "all",
"stylers": {"color": "#00253d"}
},
{
"featureType": "highway",
"elementType": "geometry.fill",
"stylers": {"color": "#000000"}
},
{
"featureType": "highway",
"elementType": "geometry.stroke",
"stylers": {"color": "#296f82ff"}
},
{
"featureType": "arterial",
"elementType": "geometry.fill",
"stylers": {"color": "#000000"}
},
{
"featureType": "arterial",
"elementType": "geometry.stroke",
"stylers": {"color": "#296f82"}
},
{
"featureType": "local",
"elementType": "geometry",
"stylers": {"color": "#000000"}
},
{
"featureType": "land",
"elementType": "all",
"stylers": {"color": "#011D32"}
},
{
"featureType": "railway",
"elementType": "geometry.fill",
"stylers": {"color": "#000000"}
},
{
"featureType": "railway",
"elementType": "geometry.stroke",
"stylers": {"color": "#296f82ff"}
},
{
"featureType": "subway",
"elementType": "geometry",
"stylers": {"lightness": -70}
},
{
"featureType": "building",
"elementType": "geometry.fill",
"stylers": {"color": "#000000"}
},
{
"featureType": "all",
"elementType": "labels.text.fill",
"stylers": {"color": "#857f7fff"}
},
{
"featureType": "all",
"elementType": "labels.text.stroke",
"stylers": {"color": "#000000"}
},
{
"featureType": "building",
"elementType": "geometry",
"stylers": {"color": "#022338"}
},
{
"featureType": "green",
"elementType": "geometry",
"stylers": {"color": "#062032"}
},
{
"featureType": "boundary",
"elementType": "all",
"stylers": {"color": "#1e1c1c"}
},
{
"featureType": "manmade",
"elementType": "geometry",
"stylers": {"color": "#022338"}
},
{
"featureType": "poi",
"elementType": "all",
"stylers": {"visibility": "on"}
},
{
"featureType": "all",
"elementType": "labels.icon",
"stylers": {"visibility": "off"}
},
{
"featureType": "all",
"elementType": "labels.text.fill",
"stylers": {
"color": "#2da0c6ff",
"visibility": "on"
}
},
{
"featureType": "highway",
"elementType": "geometry",
"stylers": {"visibility": "off"}
},
{
"featureType": "local",
"elementType": "geometry",
"stylers": {
"color": "#00ff00ff",
"visibility": "off"
}
},
{
"featureType": "railway",
"elementType": "geometry",
"stylers": {
"color": "#00ff00ff",
"visibility": "off"
}
},
{
"featureType": "subway",
"elementType": "geometry",
"stylers": {
"color": "#00ff00ff",
"visibility": "off"
}
},
{
"featureType": "arterial",
"elementType": "geometry",
"stylers": {
"color": "#073146",
"visibility": "on"
}
}
]
css 样式代码我就不贴出来了,你们随意发挥~
注意的问题点如下????
- 地图容器的 div 一定要设置宽高,不然地图显示不出来
- 确认拿到的坐标系。如果是其他坐标系,需要转为百度地图的坐标系,否则显示的位置会有偏差,坐标系的转换我上面已经贴了链接,目前国内主要有以下三种坐标系:
WGS84:为一种大地坐标系,也是目前广泛使用的 GPS 全球卫星定位系统使用的坐标系。GCJ02:又称火星坐标系,是由中国国家测绘局制订的地理信息系统的坐标系统。由 WGS84 坐标系经加密后的坐标系。
BD09:为百度坐标系,在 GCJ02 坐标系基础上再次加密。其中 bd09ll 表示百度经纬度坐标,bd09mc 表示百度墨卡托米制坐标。
非中国地区地图,服务坐标统一使用 WGS84 坐标。
百度对外接口的坐标系为 BD09 坐标系,并不是 GPS 采集的真实经纬度,在使用百度地图 JavaScript API 服务前,需先将非百度坐标通过坐标转换接口转换成百度坐标。 - 特别注意的是,保证每个坐标数组都是转成百度地图坐标之后的值,这个很关键。
-
- *
代码有点乱,我自己都有点难理清楚,如果有小伙伴遇到问题了可以联系我
QQ:602353272
如果我分享的这个文章对你有帮助的话,不要吝啬你的赞,给我点个赞吧。谢谢~~