问题说明

在地图开发中,当地图中绘制大量的标记点后,无论是拖动或者缩放,都会感觉到明显的卡顿现象。(一般超过800个点后就比较明显了).在平时的工作业务中,由于公司的实时监控页面需要展现5000-20000车辆的实时定位跟踪,特别是切换到车辆密集的港口码头卡顿现象非常严重(如下图),看起来非常难看,用户体验也非常差。在此写下一些开发优化中的心得体会(本文中使用的是高德地图为参考)。

解决思路

  1. 首先我们应该将地图上的所有覆盖物(包括Marker,Icon,Text,Polygon)分组添加到多个OverlayGroup中对每一类统一管理。部分覆盖物默认隐藏,需要时才展现。

  2. 不要将大量的覆盖物信息都直接挂载在地图对象上,可以按需加载,减少地图在移动后重新渲染的工作量

  3. 合理的使用覆盖物聚合(聚合虽然会减少卡顿,但是覆盖物数量太多依旧会比较卡)

解决方法

首先,添加一个地图设置勾选面板,将除车辆点标记以外的所有覆盖物按类型分类,添加到一个个单独的OverlayGroup中默认不挂载在地图上,在勾选时才调用OverlayGroup.setMap(map)方法添加到地图上。去掉了这些不必要覆盖物,首次进入时立马清爽了许多。

尽管去掉了各类网点,文字,等覆盖物,但是我们需要渲染的车辆标记点依旧是非常多的。在此基础上,我们采用按屏幕加载,筛选出屏幕内的点,屏幕外的标记点不渲染,监控每次地图的moveend,zoomend,resize,每次的改变后重新计算屏幕内的标记点,当屏幕内标记点较少,比如200以下(这个随个人需要定),直接绘制在地图上。当屏幕内标记点比较多,大于200,调用聚合方法,将标记点聚合后绘制在地图上。

参考代码

    /**监听地图拖拽,放大事件,根据屏幕范围动态渲染点位 */
      reloadMarks(){
        AMap.event.addListener(this.map,'moveend',()=> {
          this.computeMarkers();
        })
        AMap.event.addListener(this.map,'zoomend',()=> {
          this.computeMarkers();
        })
        AMap.event.addListener(this.map,'resize',()=> {
          this.computeMarkers();
        })
      },
      /** 根据当前屏幕范围帅选marker */
      computeMarkers(){
        this.newViewData={};
        //获取当前视图范围
        let now_bounds = this.map.getBounds();
        //遍历车辆数据,为了减少后台传入的重复数据,我的车辆信息列表一直使用对象保存
        for(let sel_deviceNo in this.carDataObj0){
          let item=this.carDataObj0[sel_deviceNo];
          //判断当前点的坐标是否存在于视图内
          if(now_bounds.contains(item.gcj02_coords.split(','))){
            //将当前屏幕内的视图点保存
            this.newViewData[sel_deviceNo] = item;
          }
        }
        this.renderMarker();
        now_bounds=null;
      },
      /** 创建聚合**/
      createCluser(markerArr) {
        AMap.plugin(['AMap.MarkerClusterer'], () => {
          this.cluster = new AMap.MarkerClusterer(this.map, markerArr, {
            gridSize: 80,
            renderCluserMarker,
            minClusterSize: 1,
            maxZoom: 18
          });
        })
      },
      /** 将markers渲染到map上 */
      renderMarker(){
        //判断目前是否有当前覆盖物组
        !this.overLayGroup && this.overLayGroup = new AMap.OverlayGroup([]);
        let MarkerAddArr = [];
        for(let sel_deviceNo in this.newViewData){
            /**我的所有创建的Marker点对象都是长期存在于this.markerObj中,并不销毁,每次数据更
            新后调用marker对象的setPosition(),setAngle(),setIcon()等方法改变状态,再筛选出
            需要挂载上的一起加到地图上 **/
            if(this.markerObj[sel_deviceNo]){
              MarkerAddArr.push(this.markerObj[sel_deviceNo]);
            }
          }
        this.overLayGroup.clearOverlays();
        //超过200个点则开启聚合模式  且在聚合模式下不渲染面板展示点
        if(MarkerAddArr.length>=200){
            if(this.cluster){
              this.cluster.clearMarkers();
              this.cluster.setMarkers(MarkerAddArr);
            }else{
              this.createCluser(MarkerAddArr);
            }
        }else{
            if(this.cluster){
                this.cluster.clearMarkers();
            }
            this.overLayGroup.addOverlays(MarkerAddArr);
            this.overLayGroup.setMap(this.map);
        }
        MarkerAddArr=null;
      },

代码直接从业务中复制的,耦合度比较高,还请见谅。 做完了标记点按屏幕分组加载之后,我们在添加一个简单的防抖方法,防止连续的拖动或者缩放导致computeMarkers方法被触发,减少方法调用次数

const debounce = (fn, wait=500) =>{
  return function() {
    clearTimeout(fn.timer)
    fn.timer = setTimeout(fn.bind(this, ...arguments), wait)
  }
}

完成后效果如下:减少了地图的计算与渲染,每次只计算视野内的点数,因此总数的大小不会影响地图性能,当视野内超过200个点都会聚合,200以下时显示。

平时写文章比较少,文笔很差,还请多多见谅,谢谢。

Logo

前往低代码交流专区

更多推荐