Vue实现功能齐全的地图组件 - 附效果图及源码

2022-07-13 13:46:23

这是一个h5页面–

效果图

录图的时候网络条件不是很好,wifi卡了,所以数据加载有点慢。当网络情况不好的时候,这个组件还需要进一步的优化

前置条件

<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=yourAK"></script>

功能点分解

  1. 分析数据
  2. 实现css布局
  3. 挂载百度map
  4. 定位当前位置
  5. 获取周边poi
  6. 搜索提示
  7. 点击右下角图标重新定位到当前位置
  8. 固定中心点,拖拽地图选择位置

分析数据

根据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();//调用初始化函数})

到这里我们应该能在idmap中的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的搜索关键词,于是我们可以调用百度地图LocalSearchsearchNearby函数。这里我们搜索周围方圆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
  • 作者:csu_zipple
  • 原文链接:https://blog.csdn.net/csu_passer/article/details/85218670
    更新时间:2022-07-13 13:46:23