前言
由于项目里面涉及到高德地图web端相关业务需求,自己也是从不会到看文档慢慢学会,诚惶诚恐,这篇文章与其说是技术分享,不如说是给没接触过高德地图jsapi的前端开发者分享一些小小的建议以及自己的一些经验之谈,说的有不对的地方欢迎大家指正~
接下来我会从【新手怎么看文档】【怎么根据业务需求合理使用api】【怎么封装业务组件】【那些年Tony爬过的坑】这几个方面来具体聊一聊
1.新手怎么看文档
个人认为高德地图api文档相对百度地图、bing地图而言更加清晰明了,对新手比较友好,我拿到需求如果不熟悉也要先看一下文档,只需要看web端jsapi即可,主要看这几个板块:
- 示例中心(这里有一些前人写好的demo,拿来稍微改动一下就可以变成业务代码)
- 参考手册(如果示例中心还不能满足需求,或者对某个类某个方法不熟悉,可以移步到这里定位到相关类或者方法仔细观察一下)
- AMapUi组件(这个是高德自己封装好的一些组件,可以实现一些复杂业务,比如用巡航器结合el-slider组件实现带进度条的轨迹回放效果)
2.怎么根据业务需求合理使用api
2.1.地图
2.2.1.创建地图
我一般使用vue-amap插件引入高德地图,首先安装插件npm i vue-amap -S
main.js
// 引入vue-amap
import VueAMap from 'vue-amap';
Vue.use(VueAMap);
// 初始化vue-amap
VueAMap.initAMapApiLoader({
// 高德的key
key: '你申请的key',
// 插件集合
plugin: ['Autocomplete', 'PlaceSearch', 'Scale', 'HawkEye', 'ToolBar', 'ControlBar', 'MapType', 'PolyEditor', 'AMap.CircleEditor', 'AMap.MarkerClusterer', 'AMap.Geocoder'],
// api版本
v: '2.0',
// AMapUi组件库版本
uiVersion: '1.1.1'
});
单文件组件内使用
import { lazyAMapApiLoaderInstance } from 'vue-amap'
initMap () {
lazyAMapApiLoaderInstance.load().then(() => {
this.map = new AMap.Map('map', {
viewMode: '2D',
center: this.center,
zoom: this.zoom
})
let toolBar = new AMap.ToolBar({
position: {
top: '110px',
left: '40px'
}
})
let controlBar = new AMap.ControlBar({
position: {
top: '10px',
left: '10px'
}
})
let scale = new AMap.Scale()
this.map.addControl(toolBar)
this.map.addControl(controlBar)
this.map.addControl(scale)
})
} // 这个是2.0版本写法,1.x版本写法稍微有所不同
2.2.点标记
2.2.1.创建点标记
点击按钮给地图绑定点击事件--->根据事件对象获取点击位置经纬度--->将经纬度传入AMap.Marker构造函数---> new实例化AMap.Marker构造函数--->调用map的add方法将生成的实例渲染到地图上 或者 直接使用AMap.MouseTool鼠标绘制工具类 建议使用第二种方法,用户体验比较好
2.2.2.点标记添加弹跳动画
需要注意的是如果使用1.4.15版本api直接调用相关方法即可,如果使用2.0版本ap好像需要自己实现,实现代码如下:
/deep/.bounce-marker {
animation: bounce 0.5s cubic-bezier(0.1, 0.25, 0.1, 1) 0s infinite
alternate both;
}
@keyframes bounce {
from {
transform: translateY(0px);
}
to {
transform: translateY(-40px);
}
}
2.2.3.点标记轨迹回放
这个参考文档即可,2.0版本写法和1.x版本有所不同
2.2.4.点标记聚合显示
这个参考文档即可,2.0版本写法和1.x版本有所不同
2.3.折线
使用AMap.Polyline构建函数创建实例--->调用map的add方法将实例添加到地图上
2.4.多边形
2.4.1创建多边形
在地图上点击按钮给地图绑定点击事件--->根据事件对象获取点标记的经纬度--->组成一个数组传入AMap.Polygon构造函数--->调用add方法将生成的实例渲染到地图上
或者
直接使用AMap.MouseTool鼠标绘制工具类 建议使用第二种方法,用户体验比较好
2.5.圆
2.5.1.创建圆
使用AMap.Circle(opt:CircleOptions)构建函数创建实例--->调用map的add方法将实例添加到地图上
2.5.2.编辑圆
首先根据后端返回的圆心经纬度和半径回显圆--->然后使用AMap.CircleEditor(Map,Circle)创建实例--->调用open方法开启编辑--->监听move、adjust事件
2.5.3.根据id唯一标识查看(放大)圆
遍历后端返回的圆形覆盖物列表数据(array),将唯一标识id绑定到AMap.Circle的extData参数上面,这样点击表格数据的每一行就可以根据这个唯一标识结合Circle类的getExtData()方法找到这个圆形覆盖物,然后调用map.setFitView()方法将该圆形覆盖物放大到合适的视野级别
2.5.4.判断圆和圆的位置关系
这个高德好像没有提供相关方法,自己实现,思路也很简单,大致思路就是判断圆心距离和半径之和的大小关系。怎么计算圆心距离可以参考api文档里面的【参考手册 数学计算库】
doesCircleIntersect() {
const circlesData = this.allCircleData.filter(item => item.type !== 3)
if (!circlesData.length) {
return
}
const isIntersect = circlesData.some(item => {
const marker1 = new window.AMap.LngLat(item.lon, item.lat)
const marker2 = new window.AMap.LngLat(this.circleOptions.lon, this.circleOptions.lat)
const circleCenterDistance = marker1.distance(marker2)
const circleRadiusSum = item.radius + this.circleOptions.radius
return circleCenterDistance < circleRadiusSum
})
return isIntersect
},
2.5.5.判断圆和点标记的位置关系
直接调用Circle类的contains(point:LngLat)方法,这个方法返回一个boolean,用于判断圆形覆盖物是否包含点标记
2.6.信息窗体
2.6.1.打开信息窗体
// 方法一:html字符串拼接思路实现
// 打开信息窗体
openInfoWindow(info) {
var infoList = ['<div class="info-container">']
infoList.push(`<div class="info-container__header">飞手信息</div>`)
infoList.push(`<div class="info-container__body">`)
infoList.push(`<div>飞手姓名:${info.pilotName}</div>`)
infoList.push(`<div>身份证号:${info.identityCard}</div>`)
infoList.push(`<div>飞行时长:${info.duration}h</div>`)
infoList.push(`<div>地址:${info.address}</div>`)
infoList.push(`<div>经度:${info.pilotLon} 纬度:${info.pilotLat}</div>`)
infoList.push(`</div></div>`)
this.infoWindow = new window.AMap.InfoWindow({
content: infoList.join(''), // 使用默认信息窗体框样式,显示信息内容
offset: new window.AMap.Pixel(-5, -35)
})
this.infoWindow.open(
this.map,
new window.AMap.LngLat(info.pilotLon, info.pilotLat)
)
}
// 方法二:使用组件的思路去实现,设置isCustom属性为true就可以随心所欲的去用组件去实现一个自定义的信息窗体
// 打开信息窗体
openInfoWindow(info) {
this.showInfoWindow = true
this.infoWindowContent = info
const infoWindow = new window.AMap.InfoWindow({
isCustom: true, // isCustom设置true 自定义信息窗体
content: this.$refs.infoWindowRef.$el, // 指向自定义信息窗体dom元素
offset: new window.AMap.Pixel(0, 0)
})
this.infoWindow = infoWindow
this.infoWindow.open(this.map, new window.AMap.LngLat(info.lon, info.lat))
}
以上两种方法都可以实现,方法一适用于展示内容比较简单的信息窗体,方法二相对而言更加灵活可扩展,写起来也很爽,也好维护,后期迭代只需要改动子组件即可
2.7.覆盖物群组
如果需要一次性给地图添加很多覆盖物,其中包括点标记、圆形、多边形等等,可以考虑使用AMap.OverlayGroup,比较简单粗暴
2.8.LBS服务
2.8.1.地理编码(地址->经纬度)
/**
* 根据关键词查询经纬度
*/
getLngLatByKeywords(keywords) {
const geocoder = new window.AMap.Geocoder()
geocoder.getLocation(keywords, (status, result) => {
if (status === 'complete' && result.geocodes.length) {
const center = result.geocodes[0].location
this.map.setCenter(center)
this.map.setZoom(18)
} else {
this.$message.info('根据地址查询位置失败,请输入详细地址搜索')
}
})
},
2.8.2.逆地理编码(经纬度->地址)
/**
* 根据经纬度查询位置
*/
getAddressByLngLat(lnglat) {
const geocoder = new window.AMap.Geocoder({
city: '全国'
})
geocoder.getAddress(lnglat, (status, result) => {
if (status === 'complete' && result.regeocode) {
const address = result.regeocode.formattedAddress
this.form.remarks = `以${address}为中心,以${this.circleOptions.radius}米为半径的圆形区域`
} else {
this.$message.info('根据经纬度查询地址失败')
}
})
},
2.8.3.行政区划查询
这里就不详细说,分两种,行政区边界查询和下属行政区查询,一般会结合el-cascader级联选择器做一些行政区划查询的业务需求。
3.怎么封装业务组件
封装组件也很简单,不外乎组件通信,slot,mixins这些东西,当然,想要封装一个高可用、低耦合的组件还是比较考验人的功底,我一般是首先实现业务需求,然后再将很多页面复用的data、methods单独抽离出来,放到mixins里面,前提是这些data、methods具备单独抽离出来的条件,这个条件怎么界定的其实也没有一个标准,就看到底是在页面写还是在mixins里面写比较优雅。当然也可以直接封装一个单独的业务组件,比如信息窗体自定义组件,电子围栏组件等等
4.那些年Tony爬过的坑
什么...Tony???没错,不要怀疑,我以前不是理发的,只是英语不好所以取了这个简单又好记的名字,我很喜欢,毕竟已经伴随我好几年的职业生涯了
4.1.绘制多边形使用第一种方法点标记不按套路出牌,不按顺时针连接,也不按逆时针连接,却是交叉连接。这么说可能还是有些懵逼,我手绘几张图对比看一下就明白了
期望的连接顺序是1-2-3-4-1 或者 1-4-3-2-1 然而 他偏偏不给面子逆天而行 1-2-4-3-1 或者 1-3-4-2-1
解决办法:先将经纬度坐标转换成平面坐标,根据平面坐标对点标记按照一定的规则排序,排好序再创建多边形,规则如下
// start表示中心点 end表示多边形顶点
getAngle (start, end) {
// 首先将经纬度坐标转换为平面坐标
const p_start = this.map.lngLatToContainer(start)
const p_end = this.map.lngLatToContainer(end)
const diff_x = p_end.x - p_start.x
const diff_y = p_end.y - p_start.y
return (360 * Math.atan2(diff_y, diff_x)) / (2 * Math.PI) + 180
}
// 遍历顶点集合,调用getAngle方法获取每个顶点和中心点成的角度(<90°),根据角度对顶点进行排序,排好序再绘制多边形即可,后续代码省略
4.2.列表页新增圆形(多边形)覆盖物成功之后,立即点击编辑这条新增记录,打开el-dialog对话框,对话框里面的地图会出现首次渲染失败的神奇bug
解决办法:新增页面和编辑页面地图容器id名取名区别开