这是一个h5页面–
效果图


录图的时候网络条件不是很好,wifi卡了,所以数据加载有点慢。当网络情况不好的时候,这个组件还需要进一步的优化
前置条件
- 在你的index.html页面上加载百度地图js(获取ak)
- 正确安装了vue
- 使用vue-cli可以快速开发
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=yourAK"></script>
功能点分解
- 分析数据
- 实现
css
布局 - 挂载百度
map
- 定位当前位置
- 获取周边
poi
- 搜索提示
- 点击右下角图标重新定位到当前位置
- 固定中心点,拖拽地图选择位置
分析数据
根据UI图分析组件需要用到哪些数据。
data(){return{
map:null,//实例化map
searchValue:"",//搜索model
currentAddress:"",//当前定位的地址
point:{},//当前地址的经纬度
poiKeyword:"",//周边的关键词,用来搜索,从adress中获取street拼接
potentialLocation:[],//周边信息}}
css布局
这样的布局很简单,请随意发挥
挂载百度map
initMaps(){this.map=newBMap.Map("map");let mPoint=newBMap.Point(116.404,39.915);//天安门this.map.centerAndZoom(mPoint,18);}
为了确保dom
成功创建了,我们在nextTick
中进行初始化
mounted(){this.$toast({text:"点击或拖动选址~",position:"top"});//提示信息this.$nextTick(()=>{this.initMaps();//调用初始化函数})
到这里我们应该能在id
为map
中的div
里能够看到地图了
定位
我们调用百度提供的api进行定位,这里api定位的内部实现我猜测是基于h5的navigator。
根据警告提示,我们进去源码看看
警告信息说的是需要安全的origin
,这里由于是在本地开发环境我们可以忽略这个警告。到了线上还是需要https
的,不然遇到dns
劫持也是很麻烦的。
好的,现在实现locate方法用于定位:
locate(){let map=this.map;let geolocation=newBMap.Geolocation();const vm=this;
geolocation.getCurrentPosition(function(r){if(this.getStatus()===BMAP_STATUS_SUCCESS){let mk=newBMap.Marker(r.point);
map.addOverlay(mk);
map.panTo(r.point);}else{
console.log('failed ',this.getStatus());}});
这里我们就成功定位到了当前所在位置,当然别忘记了在mounted方法里调用这个方法了。
this.$nextTick(()=>{this.initMaps();this.locate();})
显示当前位置
刚刚在locate方法中获取到了当前的point值,也就是经纬度。那么如何显示地址信息呢?locate中回调函数参数r其实是包括了address信息的,但是!有时候address精确度只是到了城市级别–比如我这里console.log(r);
打印出来的信息如下:
为了解决这个问题,我们需要从拿到的point
对象入手,逆地址解析一次。
这里我们创建一个analyze
函数调用百度的getLocation
方法进行解析
/**
* Attention: 解析地址会有异常--有时候会解析正确,有时候只会解析到区
* @param point lng and lat
*/analyze(point){//point:{lat:"",lng:""}const geoc=newBMap.Geocoder();
geoc.getLocation(point, rs=>{this.point= rs.point;this.currentAddress= rs.address;this.poiKeyword= rs.street||rs.address;});},
并利用vue的数据响应相应的更新当前位置,当前经纬度,当前poiKeyword
。poiKeyword
有值了应该做什么呢?我们就可以开始根据point来搜索周边,提供周围的位置信息啦。
那么这里我们为poiKeyword
创建一个监听器,进行实时操作。
watch:{poiKeyword(n){this.getAroundPOI(["栋","店","小区","学校","餐饮",n]);//n就是最新值}}
实现getAroundPOI
函数
在监听器中我们提供了poi的搜索关键词,于是我们可以调用百度地图LocalSearch
的searchNearby
函数。这里我们搜索周围方圆1000m的周边数据。
getAroundPOI(keyword){let map=this.map;let mPoint=newBMap.Point(this.point.lng,this.point.lat);let vm=this;let local=newBMap.LocalSearch(map,{onSearchComplete(results){if(local.getStatus()===BMAP_STATUS_SUCCESS){let temp=[];
results.forEach(item=>{
temp= temp.concat(item.Ar);});
vm.potentialLocation= temp;//更新ui}else{
console.warn("get poi error ,code -> ",local.getStatus());}}});
local.searchNearby(keyword,mPoint,1000);},
在onSearchComplete
回调中更新potentialLocation
的值之后vue
会帮助我们更新这部分ui
。
搜索提示
百度地图的搜索提示其实很简单,只需要创建一个自动装填对象。搜索下拉提示框会根据你传入的input
参数(input id)进行定位,宽度与input
相同。
getSuggestion(){let ac=newBMap.Autocomplete({"input":"suggestId","location":this.map});
ac.addEventListener("onconfirm", e=>{let _value= e.item.value;this.searchValue= _value.province+ _value.city+ _value.district+ _value.street+ _value.business;this.setPlace(this.searchValue);});},
在上述代码中,我们定义了自动装填对象的监听器(点击下拉列表项时触发),并更新了input
的值,在vue
中我们一般用v-model
进行双向绑定。
获取到了地址信息之后,我们调用了setPlace
方法并传入了搜索值。setPlace
方法主要用作给地图重定位,移动地图中心到搜索值所在地点。
setPlace(val){let map=this.map;
map.clearOverlays();const vm=this;let local=newBMap.LocalSearch(map,{onSearchComplete(){let pp= local.getResults().getPoi(0).point;
map.centerAndZoom(pp,18);
map.addOverlay(newBMap.Marker(pp));
vm.analyze(pp);}});
local.search(val);}
同时,注意在上面的代码中我们调用了analyze
方法,用来更新周边信息。为什么能更新呢,忘了就往上翻回去看看analyze
方法是怎么实现的吧。
重定位及拖拽中心
为了实现这个需求,首先我们要想办法地图上的自定义控件。自定义控件很麻烦,而且也会影响网页性能,所以我们应该另辟蹊径。论坛上其实有更简单的方法,这里我将论坛中的思路实现了。
关键就在于将控件看成网页的dom
去相对于百度canvas
地图进行定位。
看一下dom
结构:
<divclass="map-wrapper"><divid="map"></div><imgclass="position"src="../../assets/icon/position.svg"alt="position"><imgclass="nowposition"@click="locate"src="../../assets/icon/nowposition.svg"alt="nowposition"></div>
以及css
.map-wrapper{height: 50%;position: relative;
#map{height: 100%;}img{width: 32px;object-fit: contain;}.position{position: absolute;left: 50%;top: 50%;transform:translate(-50%,-75%);//The bottom of the icon is centered,75 =50(center) +25(top)z-index: 100;}.nowposition{position: absolute;right: 20px;bottom:20px;z-index: 100;}}
这里要注意的一点就是利用position
以及translate(-50%,-50%)
居中以后为什么要上调25%?因为中间的图标是尖尖的,肉眼更倾向于利用图标的底部(那个尖尖)进行定位。我们给它的样式是正方形,并且是contain的,所以x轴是正好居中的不用管。但是y轴的问题是这样做仅仅是将y的中心点放在地图的中心位置,会有一点点的纬度偏差。我们应该与肉眼保持一致,因此需要将底部位于地图的正中心。因此我们还要上调25%。(自身高度的一半,因为就剩一半了)
为了能够让中心点定位,我们还需要给map注册一个监听器。
在initMaps
方法中加上一个监听器。
this.map.addEventListener('dragend',()=>{let pixel=this.map.pointToOverlayPixel(this.map.getCenter());let point=this.map.overlayPixelToPoint({x:pixel.x,y:pixel.y});this.analyze(point);})
在方法的回调里再调用analyze
方法重新获取poi
,实时更新周边位置信息。
源码
<template><divclass="ys-map"><divclass="map-wrapper"><div id="map"></div><imgclass="position" src="../../assets/icon/position.svg" alt="position"><imgclass="nowposition" @click="locate" src="../../assets/icon/nowposition.svg" alt="nowposition"></div><div id="tips"><AddressItem:title="'当前位置'":address="currentAddress":extra="'(以图上标记位置为准)'"/><AddressItem v-for="(item,index) in potentialLocation" v-bind:key="index":title="item.title":address="item.address" @click.native="selectAddress(item)"/><div v-if="potentialLocation.length===0">{{point.lng}},{{point.lat}}</div></div><divclass="ys-search-address"><imgclass="back" src="../../assets/icon/back.svg" alt="back" @click="onBackClick"><divclass="ys-search-wrapper"><input type="text" v-model="searchValue" title="" id="suggestId" placeholder="定位不准?试试手动输入"><img src="../../assets/icon/close.svg" alt="search" @click="searchValue=''"></div><aclass="okBtn" href="javascript:;" @click="onOkClick">确定</a></div><divclass="search-tips" id="result">
tips</div></div></template><script>import AddressItemfrom"./AddressItem";exportdefault{
name:"Map",
components:{AddressItem},mounted(){this.$toast({text:"点击或拖动选址~",position:"top"});this.$nextTick(()=>{this.initMaps();this.locate();this.getSuggestion();})},
methods:{initMaps(){this.map=newBMap.Map("map");let mPoint=newBMap.Point(116.404,39.915);//Tiananmen Squarethis.map.centerAndZoom(mPoint,18);this.map.addEventListener("click",this.onMapClicked);this.map.addEventListener('dragend',()=>{let pixel=this.map.pointToOverlayPixel(this.map.getCenter());let point=this.map.overlayPixelToPoint({x:pixel.x,y:pixel.y});this.analyze(point);})},locate(){let map=this.map;let geolocation=newBMap.Geolocation();const vm=this;
geolocation.getCurrentPosition(function(r){if(this.getStatus()===BMAP_STATUS_SUCCESS){let mk=newBMap.Marker(r.point);
map.addOverlay(mk);
map.panTo(r.point);
vm.analyze(r.point);}else{
console.log('failed ',this.getStatus());}});//loading--},getAroundPOI(keyword){let map=this.map;let mPoint=newBMap.Point(this.point.lng,this.point.lat);//h5 112.983323,28.141431let vm=this;let local=newBMap.LocalSearch(map,{onSearchComplete(results){if(local.getStatus()===BMAP_STATUS_SUCCESS){let temp=[];
results.forEach(item=>{
temp= temp.concat(item.Ar);});
vm.potentialLocation= temp;}else{
console.warn("get poi error ,code -> ",local.getStatus());}}});
local.searchNearby(keyword,mPoint,1000);},/**
* Attention: 解析地址会有异常--有时候会解析正确,有时候只会解析到区
* @param point lng and lat
*/analyze(point){//point:{lat:"",lng:""}const geoc=newBMap.Geocoder();
geoc.getLocation(point, rs=>{this.point= rs.point;//===r.pointthis.currentAddress= rs.address;this.poiKeyword= rs.street||rs.address;});},/**
* search tips
*/getSuggestion(){let ac=newBMap.Autocomplete({"input":"suggestId","location":this.map});
ac.addEventListener("onconfirm", e=>{let _value= e.item.value;this