百度地图-大数据量点实时更新

闲话

上一篇文章原本打算记录一下本身作的东西,没想到第一次获得了大哥们的点赞特别开心(●'◡'●),接下来我也会多写写东西的javascript

一.需求和思路

百度地图大多数前端开发者都使用过,或者是高德地图之类的第三插件。(要用好第三方插件,过程老是特别的痛苦┭┮﹏┭┮)不少人必定和我同样遇到过须要实时监控数据点的需求,经过WebSocket、SSe甚至http定时获取和后台创建链接,当数据点发生变化时,后台将数据推送过来,而后前端将地图上的点更新。前端

那么地图更新的时候会出现那些问题呢?

  1. 每次更新数据点,就算那个数据点经纬度和图标都没有变化,也会出现闪烁。
  2. 数据量过大会致使百度地图渲染很慢,出现浏览器卡顿甚至卡死的问题。
  3. 数据量过多在层级拉小的时候会由于点过于密集致使看不清楚。

思考一下。。。

  1. 若是能够作到每次只更新改变经纬度或者其它信息了的数据,这样就能避免大面积闪烁问题。
  2. 若是每次更新不用从新画点只改变经纬度和图标,应该可以提升一些性能。
  3. 百度地图有一个点聚合的功能,应该能够解决这个问题。

开始动手!

有了想法,那就能够开始动手去实现了!(●'◡'●),其实以前对于百度地图api不是特别的熟悉,只是会简单的使用,此次为了这个优化特意去补习了一下,可能有些地方仍是考虑好,欢迎您能评论和我交流交流*^____^*。

二.思路实现

如何监听数据变化?

(为了方便 我这里用定时器动态生成数据点模拟数据更新)vue

首先我对于以前思考的第一点,应该存粹是一个js的问题,只要把新获取的数据与以前的数据进行比较筛选以后拿到咱们须要的数据就好了。不过在这以前咱们先解决另一个问题,若是我想把它作成 一个组件我怎么才能知道点数据更新了呢,第一反应是用watch,不过用过的人应该都知道watch对于数组里面存储对象的这种数据没法监控到对象内部数据的改变,若是的确须要监控只能开启深度监听deep: true,考虑到点的数据不少,这种监控会对性能形成很大的影响我选择父容器拿到新的数据的时候主动告知组件更新数据,咱们看一下代码。java

//父级
<mapBaidu :mapChange="isChange" :mapDateOld="mapDataTableOld" :mapDateNew="mapDataTable"></mapBaidu>
data () {
    return {
      isChange:false,//数据是否改变
    }
  },
//组件mapBaidu
watch: {
    mapChange: {
      handler() {
       console.log("数据发现改变");
      }
    },
  }复制代码

咱们经过修改isChange的值来进入watch,而后作逻辑处理。git

下一步处理数据!

(数据是模拟数据 咱们现看一下数据格式,方便看懂后面的话)github

{
          id:index,//惟一标识
          lng:120+Math.random(),//经度
          name:`点${index}`,//名称
          lat:30+Math.random(),//维度
          icon:Math.random()>0.5?"car-normal.png":"car-speeding.png"//图标
        }复制代码

知道了数据变化以后咱们就应该开始对数据作处理了,首先咱们再来看一下咱们须要什么数据!web

  1. 旧数据中应该删除的点
  2. 新数据中应该添加的点
  3. 旧数据中应该修改经纬度或者图标之类信息的点
那么大脑中的 fitter、reduce、foreach、 indexof、 some、map、find都开始蠢蠢欲动了,首先 第1点第2点其实比较简单,咱们只须要分别获取新数据的id数组 newIDList,而后拿这个数组 newIDList和旧数据 oldPintList比较找到旧数据中的 id不在这个数组中的点那么这些点就是须要删除的点 delPointID,须要添加的点就是反过来找。那么 第3点呢其实也是同样的咱们在处理第一点的时候能够保存一下旧数据 oldPintList中须要删除的点觉得的点 otherPointList,而后循环这个数组 otherPointList和新数据 newPintList比较id相同的点的经纬度和图标是否相同,若是出现变化则保存新数据 newPintList中对应的点为须要修改的数组 changePonitList咱们看看代码,其实可能还要其余思路我暂时只想到这个若是你有更好的想法能够和我分享一下。

//比较新旧数组的不一样
    filterMap(oldPintList, newPintList) {
      let delPointID = [], //相对于新获取的点须要取消的点的id数组
        otherPointList = [], //相对于新获取的点不须要取消的点
        addPointList = [], //相对于旧的数据点须要添加的点
        newIDList = new Set(), //定义一个数组用来存新数据的id的集合
        oldIDList = new Set(); //定义一个数组用来存旧数据的id的集合 
      newPintList.forEach(item => {
        newIDList.add(item.id);     
      });
      oldPintList.forEach(item => {
        oldIDList.add(item.id);     
      });
      oldPintList.forEach(item =>
        newIDList.has(item.id)? otherPointList.push(item):delPointID.push(item.id)
      );
      newPintList.forEach(item =>{
          if(!oldIDList.has(item.id)){
            addPointList.push(item);
          }
        }
      );
      let changePonitList = this.filterChange(otherPointList, newPintList); //changePonitList:发生变化的点
      return {
        delPointID,
        addPointList,
        changePonitList
      };
    },
    //获取新数据中发生变化的点
    filterChange(otherPointList, newPintList) {
      var changePonitList = [];//变化了的点
      otherPointList.forEach(point => {
        let pList = newPintList.find(item => {
          return item.id == point.id;
        });//新获取的数据中对应的那个点
        if (pList.lng != point.lng || pList.lat != point.lat || pList.icon != point.icon ) {
          changePonitList.push(pList);
        }
      });
      return changePonitList;
    }复制代码

开始对百度地图动手!!!

数据处理好了接下来就是怎么处理这3个数组delPointID、addPointList、changePonitList首先咱们先获取一下全部的覆盖物overlaysList=this.map.getOverlays(),循环delPointID找到overlaysList对应的点经过百度地图提供的removeOverlay方法,删除对应的点。接下来循环changePonitList找到对应的点经过百度地图提供的setIcon、setPosition方法从新设置经纬度和图标。addPointList就不用多说了添加一个点到百度地图,大部分人应该都会用,值得一提的是最好把每一个点的id、icon的信息保存在marker上,这样获取覆盖物的时候咱们就能获取到它,方便判断。咱们看看代码(●'◡'●)。api

let { delPointID, addPointList, changePonitList } = 
    this.filterMap(this.mapDateOld,this.mapDateNew);//获取删除点、新增点、修改点
    this.delEditMarker(changePonitList,delPointID);//修改删除点
    addPointList.forEach(add=>{//添加点
        this.addMarker(add);
    })


    //删除点
    delEditMarker(changePonitList,delPointID) {
        let overlaysList;
        if (this.pointAggregationType) {//开启点聚合经过markerClusterer类获取点
          overlaysList = this.markerClusterer.getMarkers().slice(0);
        } else {//未开启点聚合获取全部覆盖物
          overlaysList = this.map.getOverlays();
        }
        if (changePonitList.length > 0 || delPointID.length > 0) {//若是存在须要修改和删除的点
          overlaysList.forEach(item => {
            //删除点
            if (delPointID.indexOf(item.id) > -1) {
              if (this.pointAggregationType) {
                this.markerClusterer.removeMarker(item);
              } else {
                this.map.removeOverlay(item);
              }
            }
            //修改点
            changePonitList.forEach(edit=>{
              if(item.id == changePonitList[i].id){
                let point = new BMap.Point(editPoint.lat, editPoint.lng);
                let icon = new BMap.Icon(editPoint.icon, new BMap.Size(29, 29));
                item.setIcon(icon);//从新设置图标
                item.setPosition(point);//从新设置经纬度
                if (this.pointAggregationType) {
                  this.markerClusterer.setMarkers(item.id, item);
                }
              }
            });
          });
        }
    },

     //添加点
    addMarker(add) {
      let point = new BMap.Point(add.lng, add.lat);
      // console.log(point);
      
      var icon = new BMap.Icon("/img/"+add.icon, new BMap.Size(29, 29)); //设置图标大小
      let marker = new BMap.Marker(point, {
        icon: icon
      });
      marker.id = add.id;
      marker.icon = add.icon;
      let opts = {
        position: point, // 指定文本标注所在的地理位置
        offset: new BMap.Size(-10, 26) //设置文本偏移量
      };
      let label = new BMap.Label(add.name, opts); // 建立文本标注对象
      label.setStyle({
        color: "000",
        fontSize: "12px",
        height: "20px",
        lineHeight: "20px",
        border: "1px solid #000",
        fontFamily: "微软雅黑"
      });
      this.map.addOverlay(marker);//添加到地图
      marker.disableMassClear();
      marker.setLabel(label);
      if (this.pointAggregationType) {
        this.markerClusterer.addMarker(marker);
      }
    },
复制代码

三.融合点聚合

什么是点聚合?

首先咱们来看看官网点聚合的效果图数组


图中图标上有数字的就是聚合点,其实就是再层级拉到很小的时候,把点聚合显示,而后拉大以后又再显示出来,能够看的更加直观。浏览器

加入点聚合后的影响?

首先通常来讲加入点聚合以后,可让地图看上去更整洁。咱们仍是按以前的思路走,看看有什么问题,问题基本出如今删除点添加点和修改点的时候,不能再像之前同样处理了,由于聚合点下的点是没有渲染的经过获取图层没法取到它们。那我改如何去更新删除添加点呢,有点头痛!!!┭┮﹏┭┮。假如咱们实现了这个效果,那么咱们添加点或者删除点是聚合点里面的点,那么聚合点上的数字就会发生变化。考虑到这一点我以为必须去操做百度提供的点聚合的js才能作到了(MarkerClusterer_min.js)。内容我就不贴了,我直接放一个连接吧,东西比较多api.map.baidu.com/library/Mar….

咱们来看一下主要对咱们有用的东西

  1. 首先是获取咱们初始化的时候传进去的markers(这就是全部的点)

    MarkerClusterer.prototype.getMarkers = function() {
            return this._markers//全部的点
        };
     _map//传进来Map的对象复制代码

  2. 删除指定的marker

    var indexOf = function(item, source) {
            var index = -1;
            if (isArray(source)) {
                if (source.indexOf) {
                    index = source.indexOf(item)
                } else {
                    for (var i = 0,
                    m; m = source[i]; i++) {
                        if (m === item) {
                            index = i;
                            break
                        }
                    }
                }
            }
            return index
        };
    MarkerClusterer.prototype._removeMarker = function(marker) {
            var index = indexOf(marker, this._markers);
            if (index === -1) {
                return false
            }
            tmplabel = marker.getLabel();
            this._map.removeOverlay(marker);
            marker.setLabel(tmplabel); 
            this._markers.splice(index, 1);
            return true
        };
        MarkerClusterer.prototype.removeMarker = function(marker) {
            var success = this._removeMarker(marker);
            if (success) {
                this._clearLastClusters();
                this._createClusters()
            }
            return success
        };复制代码

  3. 添加指定的marker

    MarkerClusterer.prototype._pushMarkerTo = function(marker) {
            var index = indexOf(marker, this._markers);
            if (index === -1) {
                marker.isInCluster = false;
                this._markers.push(marker)
            }
        };
        MarkerClusterer.prototype.addMarker = function(marker) {
            this._pushMarkerTo(marker);
            this._createClusters()
        };
        MarkerClusterer.prototype._createClusters = function() {
            var mapBounds = this._map.getBounds();
            var extendedBounds = getExtendedBounds(this._map, mapBounds, this._gridSize);
            for (var i = 0,
            marker; marker = this._markers[i]; i++) {
                if (!marker.isInCluster && extendedBounds.containsPoint(marker.getPosition())) {
                    this._addToClosestCluster(marker)
                }
            }
        };复制代码

  4. 并无修改指定maker的方法,js都拿到了本身动手写一个,或者直接获取marker对象而后修改,由于浅拷贝的缘由直接能修改到。

    MarkerClusterer.prototype.setMarkers = function(id,marker) {
            this._markers.forEach(
                (item)=>{
                    if(item.id==id){
                        item=marker;
                    }
                }
            )
        };复制代码

好了解决了这些,咱们就能够融合点聚合了,(●'◡'●),为了知足更多人,咱们加一个布尔值 pointAggregationType来控制是否开启点聚合咱们来看看代码。

if (this.map == null) {//地图还未初始化
          return;
        }
        let { delPointID, addPointList, changePonitList } = this.filterMap(this.mapDateOld,this.mapDateNew);//获取删除点、新增点、修改点
        if (this.markerClusterer == null && this.pointAggregationType) {//若是点击后对象为null,且开启点聚合,则从新建立点聚合
          this.markerClusterer = new BMapLib.MarkerClusterer(this.map, {markers: []});
        }
        this.delEditMarker(changePonitList,delPointID);//修改删除点
        addPointList.forEach(add=>{//添加点
          this.addMarker(add);
        })
    //添加点

    addMarker(add) {
      let point = new BMap.Point(add.lng, add.lat);
      var icon = new BMap.Icon("/img/"+add.icon, new BMap.Size(29, 29)); //设置图标大小
      let marker = new BMap.Marker(point, {
        icon: icon
      });
      marker.id = add.id;
      marker.icon = add.icon;
      let opts = {
        position: point, // 指定文本标注所在的地理位置
        offset: new BMap.Size(-10, 26) //设置文本偏移量
      };
      let label = new BMap.Label(add.name, opts); // 建立文本标注对象
      label.setStyle({
        color: "000",
        fontSize: "12px",
        height: "20px",
        lineHeight: "20px",
        border: "1px solid #000",
        fontFamily: "微软雅黑"
      });
      marker.disableMassClear();
      marker.setLabel(label);
      if (this.pointAggregationType) {
        this.markerClusterer.addMarker(marker);//添加到地图覆盖物体而且加入到点聚合的makers中
      }else{
        this.map.addOverlay(marker);//添加到地图覆盖物
      }
    },
    //删除点
    delEditMarker(changePonitList,delPointID) {
        let overlaysList;
        if (this.pointAggregationType) {//开启点聚合经过markerClusterer类获取点
          overlaysList = this.markerClusterer.getMarkers().slice(0);
        } else {//未开启点聚合获取全部覆盖物
          overlaysList = this.map.getOverlays();
        }
        if (changePonitList.length > 0 || delPointID.length > 0) {//若是存在须要修改和删除的点
          overlaysList.forEach(item => {
            //删除点
            if (delPointID.indexOf(item.id) > -1) {
              if (this.pointAggregationType) {
                this.markerClusterer.removeMarker(item);
              } else {
                this.map.removeOverlay(item);
              }
            }
            //修改点
            changePonitList.forEach(edit=>{
              if(item.id == changePonitList[i].id){
                let point = new BMap.Point(editPoint.lat, editPoint.lng);
                let icon = new BMap.Icon(editPoint.icon, new BMap.Size(29, 29));
                item.setIcon(icon);//从新设置图标
                item.setPosition(point);//从新设置经纬度 
              }
            });
          });
        }
    }
复制代码

四.总结

编写过程当中几个须要提一下的点

  1. 如何咱们须要渲染一个label而且使用点聚合的时候,拖动地图会存在label不显示的问题,我在点聚合的js中从新赋值了一遍label解决这个问题。
  2. 当咱们的数据达到一千个点以上,首次打开存在卡顿问题,咱们这里有几个能够优化的地方,异步初始化地图,让用户的体验变好,打点的图片尽可能压缩一下达到最小。
  3. 数据更新的时候拖动地图容易致使卡顿咱们能够直接在数据更新的时候禁止拖拽,更新完成后开启拖拽(这个给用户的体验并不必定好,能够选择显示个过分动画,只是个想法不必定要这样作)。

看看效果图



喜欢能够给我点个赞鼓励我一下(●'◡'●)

附上github连接github.com/github30789…

其余文章传送门

  1. 基于vue实现web端超大数据量表格:juejin.im/post/5ca1a9…
  2. js对象数组Date的比较:juejin.im/post/5c739e…
相关文章
相关标签/搜索