公司要求做个百度地图点聚合的性能优化,需一次性加载9万条数据。
记录下自己的优化过程。(只想看优化代码的可直接移步:步骤三)
一、引入百度地图
- vue项目中,在index.html文件中用script标签引入百度地图(ak=秘钥)
<script type="text/javascript" src="http://api.map.baidu.com/api?v=3.0&ak=6BUBoNWirHMQajt9f2hzWF1mDK7MsR8n"></script>
- 并初始化地图
initMap() {
this.map = new BMap.Map('map_layer', {mapType: BMAP_HYBRID_MAP});
this.map.centerAndZoom(new BMap.Point(116.404, 39.915), 5);
this.map.enableScrollWheelZoom(true);
},
二、在页面的头部应用点聚合工具开源库的文件
vue项目,可在index.html中用script标签引入
<script type="text/javascript" src="http://api.map.baidu.com/library/MarkerClusterer/1.2/src/MarkerClusterer_min.js"></script> <script type="text/javascript" src="./static/MarkerClusterer.js"></script>
百度的点聚合算法 是基于方格和距离的聚合算法,即开始的时候地图上没有任何已知的聚合点,然后遍历所有的点,去计算点的外包正方形(由gridSize指定),若此点的外包正方形与现有的聚合点的外包正方形不相交,则新建聚合点,若相交就把该点加到该聚合点,效果如下图。
应用百度地图源码,超过2000点,就卡顿的无法使用了。于是对源码进行优化。
百度地图中的点聚合主要有两个文件,一个控制聚合即MarkerClusterer.js,一个控制显示的样子TextIconOverlay.js,(这里吐槽一下,官方给的文档并没有说明!!!)
http://api.map.baidu.com/library/TextIconOverlay/1.2/src/TextIconOverlay_min.js(无格式压缩版)
http://api.map.baidu.com/library/TextIconOverlay/1.2/src/TextIconOverlay.js
http://api.map.baidu.com/library/MarkerClusterer/1.2/src/MarkerClusterer_min.js(无格式压缩版)
http://api.map.baidu.com/library/MarkerClusterer/1.2/src/MarkerClusterer.js
百度地图源码解析,可参考博客
这里展示百度地图MarkerClusterer.js的部分源码,用以说明优化的原理。
/** * 根据所给定的标记,创建聚合点 * @return 无返回值 */ 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); } } }; /** * 根据标记的位置,把它添加到最近的聚合中 * @param {BMap.Marker} marker 要进行聚合的单个标记 * * @return 无返回值。 */ MarkerClusterer.prototype._addToClosestCluster = function (marker){ var distance = 4000000; var clusterToAddTo = null; var position = marker.getPosition(); for(var i = 0, cluster; cluster = this._clusters[i]; i++){ var center = cluster.getCenter(); if(center){ var d = this._map.getDistance(center, marker.getPosition()); if(d < distance){ distance = d; clusterToAddTo = cluster; } } } if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)){ clusterToAddTo.addMarker(marker); } else { var cluster = new Cluster(this); cluster.addMarker(marker); this._clusters.push(cluster); } };
以上两个方法就是前文所述的算法的具体实现,
先排除所有不在可视范围的点,然后通过比较marker点和聚合点的距离,拿到距离最近的聚合点,判断marker点是否在聚合点的外包正方形内;
这一段是正常算法需要没啥问题,看起来问题只能出在 cluster.addMarker(marker)了
if(this.isMarkerInCluster(marker)){ return false; }//也可用marker.isInCluster判断,外面判断OK,这里基本不会命中 if (!this._center){ this._center = marker.getPosition(); this.updateGridBounds();// } else { if(this._isAverageCenter){ var l = this._markers.length + 1; var lat = (this._center.lat * (l - 1) + marker.getPosition().lat) / l; var lng = (this._center.lng * (l - 1) + marker.getPosition().lng) / l; this._center = new BMap.Point(lng, lat); this.updateGridBounds(); }//计算新的Center } marker.isInCluster = true; this._markers.push(marker); var len = this._markers.length; if(len < this._minClusterSize ){ this._map.addOverlay(marker); //this.updateClusterMarker(); return true; } else if (len