百度地图,点聚合展示速度优化
大致问题,由于百度地图点聚合在超过1000个点的时候会出现稍许卡顿,而超过5000之后明显卡坤甚至浏览器崩溃的情况,所以需要修改百度地图外链引入的MarkerClusterer_min.js文件。
以下为修改后的文件内容
1 /** 2 * @fileoverview MarkerClusterer标记聚合器用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能。 3 * 主入口类是<a href="symbols/BMapLib.MarkerClusterer.html">MarkerClusterer</a>, 4 * 基于Baidu Map API 1.2。 5 * 6 * @author Baidu Map Api Group 7 * @version 1.2 8 */ 9 10 11 /** 12 * @namespace BMap的所有library类均放在BMapLib命名空间下 13 */ 14 var BMapLib = window.BMapLib = BMapLib || {}; 15 (function(){ 16 17 /** 18 * 获取一个扩展的视图范围,把上下左右都扩大一样的像素值。 19 * @param {Map} map BMap.Map的实例化对象 20 * @param {BMap.Bounds} bounds BMap.Bounds的实例化对象 21 * @param {Number} gridSize 要扩大的像素值 22 * 23 * @return {BMap.Bounds} 返回扩大后的视图范围。 24 */ 25 var getExtendedBounds = function(map, bounds, gridSize){ 26 bounds = cutBoundsInRange(bounds); 27 var pixelNE = map.pointToPixel(bounds.getNorthEast()); 28 var pixelSW = map.pointToPixel(bounds.getSouthWest()); 29 pixelNE.x += gridSize; 30 pixelNE.y -= gridSize; 31 pixelSW.x -= gridSize; 32 pixelSW.y += gridSize; 33 var newNE = map.pixelToPoint(pixelNE); 34 var newSW = map.pixelToPoint(pixelSW); 35 return new BMap.Bounds(newSW, newNE); 36 }; 37 38 /** 39 * 按照百度地图支持的世界范围对bounds进行边界处理 40 * @param {BMap.Bounds} bounds BMap.Bounds的实例化对象 41 * 42 * @return {BMap.Bounds} 返回不越界的视图范围 43 */ 44 var cutBoundsInRange = function (bounds) { 45 var maxX = getRange(bounds.getNorthEast().lng, -180, 180); 46 var minX = getRange(bounds.getSouthWest().lng, -180, 180); 47 var maxY = getRange(bounds.getNorthEast().lat, -74, 74); 48 var minY = getRange(bounds.getSouthWest().lat, -74, 74); 49 return new BMap.Bounds(new BMap.Point(minX, minY), new BMap.Point(maxX, maxY)); 50 }; 51 52 /** 53 * 对单个值进行边界处理。 54 * @param {Number} i 要处理的数值 55 * @param {Number} min 下边界值 56 * @param {Number} max 上边界值 57 * 58 * @return {Number} 返回不越界的数值 59 */ 60 var getRange = function (i, mix, max) { 61 mix && (i = Math.max(i, mix)); 62 max && (i = Math.min(i, max)); 63 return i; 64 }; 65 66 /** 67 * 判断给定的对象是否为数组 68 * @param {Object} source 要测试的对象 69 * 70 * @return {Boolean} 如果是数组返回true,否则返回false 71 */ 72 var isArray = function (source) { 73 return '[object Array]' === Object.prototype.toString.call(source); 74 }; 75 76 /** 77 * 返回item在source中的索引位置 78 * @param {Object} item 要测试的对象 79 * @param {Array} source 数组 80 * 81 * @return {Number} 如果在数组内,返回索引,否则返回-1 82 */ 83 var indexOf = function(item, source){ 84 var index = -1; 85 if(isArray(source)){ 86 if (source.indexOf) { 87 index = source.indexOf(item); 88 } else { 89 for (var i = 0, m; m = source[i]; i++) { 90 if (m === item) { 91 index = i; 92 break; 93 } 94 } 95 } 96 } 97 return index; 98 }; 99 100 /** 101 *@exports MarkerClusterer as BMapLib.MarkerClusterer 102 */ 103 var MarkerClusterer = 104 /** 105 * MarkerClusterer 106 * @class 用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能 107 * @constructor 108 * @param {Map} map 地图的一个实例。 109 * @param {Json Object} options 可选参数,可选项包括:<br /> 110 * markers {Array<Marker>} 要聚合的标记数组<br /> 111 * girdSize {Number} 聚合计算时网格的像素大小,默认60<br /> 112 * maxZoom {Number} 最大的聚合级别,大于该级别就不进行相应的聚合<br /> 113 * minClusterSize {Number} 最小的聚合数量,小于该数量的不能成为一个聚合,默认为2<br /> 114 * isAverangeCenter {Boolean} 聚合点的落脚位置是否是所有聚合在内点的平均值,默认为否,落脚在聚合内的第一个点<br /> 115 * styles {Array<IconStyle>} 自定义聚合后的图标风格,请参考TextIconOverlay类<br /> 116 */ 117 BMapLib.MarkerClusterer = function(map, options){ 118 if (!map){ 119 return; 120 } 121 this._map = map; 122 this._markers = []; 123 this._clusters = []; 124 125 var opts = options || {}; 126 this._gridSize = opts["gridSize"] || 60; 127 this._maxZoom = opts["maxZoom"] || 18; 128 this._minClusterSize = opts["minClusterSize"] || 2; 129 this._isAverageCenter = false; 130 if (opts['isAverageCenter'] != undefined) { 131 this._isAverageCenter = opts['isAverageCenter']; 132 } 133 this._styles = opts["styles"] || []; 134 135 var that = this; 136 this._map.addEventListener("zoomend",function(){ 137 that._redraw(); 138 }); 139 140 this._map.addEventListener("moveend",function(){ 141 that._redraw(); 142 }); 143 144 var mkrs = opts["markers"]; 145 isArray(mkrs) && this.addMarkers(mkrs); 146 }; 147 148 /** 149 * 添加要聚合的标记数组。 150 * @param {Array<Marker>} markers 要聚合的标记数组 151 * 152 * @return 无返回值。 153 */ 154 MarkerClusterer.prototype.addMarkers = function(markers){ 155 for(var i = 0, len = markers.length; i <len ; i++){ 156 this._pushMarkerTo(markers[i]); 157 } 158 this._createClusters(); 159 }; 160 161 /** 162 * 把一个标记添加到要聚合的标记数组中 163 * @param {BMap.Marker} marker 要添加的标记 164 * 165 * @return 无返回值。 166 */ 167 MarkerClusterer.prototype._pushMarkerTo = function(marker){ 168 var index = indexOf(marker, this._markers); 169 if(index === -1){ 170 marker.isInCluster = false; 171 this._markers.push(marker);//Marker拖放后enableDragging不做变化,忽略 172 } 173 }; 174 175 /** 176 * 添加一个聚合的标记。 177 * @param {BMap.Marker} marker 要聚合的单个标记。 178 * @return 无返回值。 179 */ 180 MarkerClusterer.prototype.addMarker = function(marker) { 181 this._pushMarkerTo(marker); 182 this._createClusters(); 183 }; 184 185 /** 186 * 根据所给定的标记,创建聚合点,并遍历所有聚合点 187 * @return 无返回值 188 */ 189 MarkerClusterer.prototype._createClusters = function(){ 190 var mapBounds = this._map.getBounds(); 191 var extendedBounds = getExtendedBounds(this._map, mapBounds, this._gridSize); 192 for(var i = 0, marker; marker = this._markers[i]; i++){ 193 if(!marker.isInCluster && extendedBounds.containsPoint(marker.getPosition()) ){ 194 this._addToClosestCluster(marker); 195 } 196 } 197 198 var len = this._markers.length; 199 for (var i = 0; i < len; i++) { 200 if(this._clusters[i]){ 201 this._clusters[i].render(); 202 } 203 } 204 }; 205 206 /** 207 * 根据标记的位置,把它添加到最近的聚合中 208 * @param {BMap.Marker} marker 要进行聚合的单个标记 209 * 210 * @return 无返回值。 211 */ 212 MarkerClusterer.prototype._addToClosestCluster = function (marker){ 213 var distance = 4000000; 214 var clusterToAddTo = null; 215 var position = marker.getPosition(); 216 for(var i = 0, cluster; cluster = this._clusters[i]; i++){ 217 var center = cluster.getCenter(); 218 if(center){ 219 var d = this._map.getDistance(center, marker.getPosition()); 220 if(d < distance){ 221 distance = d; 222 clusterToAddTo = cluster; 223 } 224 } 225 } 226 227 if (clusterToAddTo && clusterToAddTo.isMarkerInClusterBounds(marker)){ 228 clusterToAddTo.addMarker(marker); 229 } else { 230 var cluster = new Cluster(this); 231 cluster.addMarker(marker); 232 this._clusters.push(cluster); 233 } 234 }; 235 236 /** 237 * 清除上一次的聚合的结果 238 * @return 无返回值。 239 */ 240 MarkerClusterer.prototype._clearLastClusters = function(){ 241 for(var i = 0, cluster; cluster = this._clusters[i]; i++){ 242 cluster.remove(); 243 } 244 this._clusters = [];//置空Cluster数组 245 this._removeMarkersFromCluster();//把Marker的cluster标记设为false 246 }; 247 248 /** 249 * 清除某个聚合中的所有标记 250 * @return 无返回值 251 */ 252 MarkerClusterer.prototype._removeMarkersFromCluster = function(){ 253 for(var i = 0, marker; marker = this._markers[i]; i++){ 254 marker.isInCluster = false; 255 } 256 }; 257 258 /** 259 * 把所有的标记从地图上清除 260 * @return 无返回值 261 */ 262 MarkerClusterer.prototype._removeMarkersFromMap = function(){ 263 for(var i = 0, marker; marker = this._markers[i]; i++){ 264 marker.isInCluster = false; 265 this._map.removeOverlay(marker); 266 } 267 }; 268 269 /** 270 * 删除单个标记 271 * @param {BMap.Marker} marker 需要被删除的marker 272 * 273 * @return {Boolean} 删除成功返回true,否则返回false 274 */ 275 MarkerClusterer.prototype._removeMarker = function(marker) { 276 var index = indexOf(marker, this._markers); 277 if (index === -1) { 278 return false; 279 } 280 this._map.removeOverlay(marker); 281 this._markers.splice(index, 1); 282 return true; 283 }; 284 285 /** 286 * 删除单个标记 287 * @param {BMap.Marker} marker 需要被删除的marker 288 * 289 * @return {Boolean} 删除成功返回true,否则返回false 290 */ 291 MarkerClusterer.prototype.removeMarker = function(marker) { 292 var success = this._removeMarker(marker); 293 if (success) { 294 this._clearLastClusters(); 295 this._createClusters(); 296 } 297 return success; 298 }; 299 300 /** 301 * 删除一组标记 302 * @param {Array<BMap.Marker>} markers 需要被删除的marker数组 303 * 304 * @return {Boolean} 删除成功返回true,否则返回false 305 */ 306 MarkerClusterer.prototype.removeMarkers = function(markers) { 307 var success = false; 308 for (var i = 0; i < markers.length; i++) { 309 var r = this._removeMarker(markers[i]); 310 success = success || r; 311 } 312 313 if (success) { 314 this._clearLastClusters(); 315 this._createClusters(); 316 } 317 return success; 318 }; 319 320 /** 321 * 从地图上彻底清除所有的标记 322 * @return 无返回值 323 */ 324 MarkerClusterer.prototype.clearMarkers = function() { 325 this._clearLastClusters(); 326 this._removeMarkersFromMap(); 327 this._markers = []; 328 }; 329 330 /** 331 * 重新生成,比如改变了属性等 332 * @return 无返回值 333 */ 334 MarkerClusterer.prototype._redraw = function () { 335 this._clearLastClusters(); 336 this._createClusters(); 337 }; 338 339 /** 340 * 获取网格大小 341 * @return {Number} 网格大小 342 */ 343 MarkerClusterer.prototype.getGridSize = function() { 344 return this._gridSize; 345 }; 346 347 /** 348 * 设置网格大小 349 * @param {Number} size 网格大小 350 * @return 无返回值 351 */ 352 MarkerClusterer.prototype.setGridSize = function(size) { 353 this._gridSize = size; 354 this._redraw(); 355 }; 356 357 /** 358 * 获取聚合的最大缩放级别。 359 * @return {Number} 聚合的最大缩放级别。 360 */ 361 MarkerClusterer.prototype.getMaxZoom = function() { 362 return this._maxZoom; 363 }; 364 365 /** 366 * 设置聚合的最大缩放级别 367 * @param {Number} maxZoom 聚合的最大缩放级别 368 * @return 无返回值 369 */ 370 MarkerClusterer.prototype.setMaxZoom = function(maxZoom) { 371 this._maxZoom = maxZoom; 372 this._redraw(); 373 }; 374 375 /** 376 * 获取聚合的样式风格集合 377 * @return {Array<IconStyle>} 聚合的样式风格集合 378 */ 379 MarkerClusterer.prototype.getStyles = function() { 380 return this._styles; 381 }; 382 383 /** 384 * 设置聚合的样式风格集合 385 * @param {Array<IconStyle>} styles 样式风格数组 386 * @return 无返回值 387 */ 388 MarkerClusterer.prototype.setStyles = function(styles) { 389 this._styles = styles; 390 this._redraw(); 391 }; 392 393 /** 394 * 获取单个聚合的最小数量。 395 * @return {Number} 单个聚合的最小数量。 396 */ 397 MarkerClusterer.prototype.getMinClusterSize = function() { 398 return this._minClusterSize; 399 }; 400 401 /** 402 * 设置单个聚合的最小数量。 403 * @param {Number} size 单个聚合的最小数量。 404 * @return 无返回值。 405 */ 406 MarkerClusterer.prototype.setMinClusterSize = function(size) { 407 this._minClusterSize = size; 408 this._redraw(); 409 }; 410 411 /** 412 * 获取单个聚合的落脚点是否是聚合内所有标记的平均中心。 413 * @return {Boolean} true或false。 414 */ 415 MarkerClusterer.prototype.isAverageCenter = function() { 416 return this._isAverageCenter; 417 }; 418 419 /** 420 * 获取聚合的Map实例。 421 * @return {Map} Map的示例。 422 */ 423 MarkerClusterer.prototype.getMap = function() { 424 return this._map; 425 }; 426 427 /** 428 * 获取所有的标记数组。 429 * @return {Array<Marker>} 标记数组。 430 */ 431 MarkerClusterer.prototype.getMarkers = function() { 432 return this._markers; 433 }; 434 435 /** 436 * 获取聚合的总数量。 437 * @return {Number} 聚合的总数量。 438 */ 439 MarkerClusterer.prototype.getClustersCount = function() { 440 var count = 0; 441 for(var i = 0, cluster; cluster = this._clusters[i]; i++){ 442 cluster.isReal() && count++; 443 } 444 return count; 445 }; 446 447 /** 448 * @ignore 449 * Cluster 450 * @class 表示一个聚合对象,该聚合,包含有N个标记,这N个标记组成的范围,并有予以显示在Map上的TextIconOverlay等。 451 * @constructor 452 * @param {MarkerClusterer} markerClusterer 一个标记聚合器示例。 453 */ 454 function Cluster(markerClusterer){ 455 this._markerClusterer = markerClusterer; 456 this._map = markerClusterer.getMap(); 457 this._minClusterSize = markerClusterer.getMinClusterSize(); 458 this._isAverageCenter = markerClusterer.isAverageCenter(); 459 this._center = null;//落脚位置 460 this._markers = [];//这个Cluster中所包含的markers 461 this._gridBounds = null;//以中心点为准,向四边扩大gridSize个像素的范围,也即网格范围 462 this._isReal = false; //真的是个聚合 463 464 this._clusterMarker = new BMapLib.TextIconOverlay(this._center, this._markers.length, {"styles":this._markerClusterer.getStyles()}); 465 //this._map.addOverlay(this._clusterMarker); 466 } 467 468 /** 469 * 向该聚合添加一个标记。 470 * @param {Marker} marker 要添加的标记。 471 * @return 无返回值。 472 */ 473 Cluster.prototype.addMarker = function(marker){ 474 if(this.isMarkerInCluster(marker)){ 475 return false; 476 }//也可用marker.isInCluster判断,外面判断OK,这里基本不会命中 477 478 if (!this._center){ 479 this._center = marker.getPosition(); 480 this.updateGridBounds();// 481 } else { 482 if(this._isAverageCenter){ 483 var l = this._markers.length + 1; 484 var lat = (this._center.lat * (l - 1) + marker.getPosition().lat) / l; 485 var lng = (this._center.lng * (l - 1) + marker.getPosition().lng) / l; 486 this._center = new BMap.Point(lng, lat); 487 this.updateGridBounds(); 488 }//计算新的Center 489 } 490 491 marker.isInCluster = true; 492 this._markers.push(marker); 493 494 // var len = this._markers.length; 495 // if(len < this._minClusterSize ){ 496 // this._map.addOverlay(marker); 497 // //this.updateClusterMarker(); 498 // return true; 499 // } else if (len === this._minClusterSize) { 500 // for (var i = 0; i < len; i++) { 501 // this._markers[i].getMap() && this._map.removeOverlay(this._markers[i]); 502 // } 503 // 504 // } 505 // this._map.addOverlay(this._clusterMarker); 506 // this._isReal = true; 507 // this.updateClusterMarker(); 508 // return true; 509 }; 510 511 /** 512 * 进行dom操作 513 * @return 无返回值 514 */ 515 Cluster.prototype.render = function(){ 516 var len = this._markers.length; 517 518 if (len < this._minClusterSize) { 519 for (var i = 0; i < len; i++) { 520 this._map.addOverlay(this._markers[i]); 521 } 522 } else { 523 this._map.addOverlay(this._clusterMarker); 524 this._isReal = true; 525 this.updateClusterMarker(); 526 } 527 } 528 529 /** 530 * 判断一个标记是否在该聚合中。 531 * @param {Marker} marker 要判断的标记。 532 * @return {Boolean} true或false。 533 */ 534 Cluster.prototype.isMarkerInCluster= function(marker){ 535 if (this._markers.indexOf) { 536 return this._markers.indexOf(marker) != -1; 537 } else { 538 for (var i = 0, m; m = this._markers[i]; i++) { 539 if (m === marker) { 540 return true; 541 } 542 } 543 } 544 return false; 545 }; 546 547 /** 548 * 判断一个标记是否在该聚合网格范围中。 549 * @param {Marker} marker 要判断的标记。 550 * @return {Boolean} true或false。 551 */ 552 Cluster.prototype.isMarkerInClusterBounds = function(marker) { 553 return this._gridBounds.containsPoint(marker.getPosition()); 554 }; 555 556 Cluster.prototype.isReal = function(marker) { 557 return this._isReal; 558 }; 559 560 /** 561 * 更新该聚合的网格范围。 562 * @return 无返回值。 563 */ 564 Cluster.prototype.updateGridBounds = function() { 565 var bounds = new BMap.Bounds(this._center, this._center); 566 this._gridBounds = getExtendedBounds(this._map, bounds, this._markerClusterer.getGridSize()); 567 }; 568 569 /** 570 * 更新该聚合的显示样式,也即TextIconOverlay。 571 * @return 无返回值。 572 */ 573 Cluster.prototype.updateClusterMarker = function () { 574 if (this._map.getZoom() > this._markerClusterer.getMaxZoom()) { 575 this._clusterMarker && this._map.removeOverlay(this._clusterMarker); 576 for (var i = 0, marker; marker = this._markers[i]; i++) { 577 this._map.addOverlay(marker); 578 } 579 return; 580 } 581 582 if (this._markers.length < this._minClusterSize) { 583 this._clusterMarker.hide(); 584 return; 585 } 586 587 this._clusterMarker.setPosition(this._center); 588 589 this._clusterMarker.setText(this._markers.length); 590 591 var thatMap = this._map; 592 var thatBounds = this.getBounds(); 593 this._clusterMarker.addEventListener("click", function(event){ 594 thatMap.setViewport(thatBounds); 595 }); 596 597 }; 598 599 /** 600 * 删除该聚合。 601 * @return 无返回值。 602 */ 603 Cluster.prototype.remove = function(){ 604 for (var i = 0, m; m = this._markers[i]; i++) { 605 this._markers[i].getMap() && this._map.removeOverlay(this._markers[i]); 606 }//清除散的标记点 607 this._map.removeOverlay(this._clusterMarker); 608 this._markers.length = 0; 609 delete this._markers; 610 } 611 612 /** 613 * 获取该聚合所包含的所有标记的最小外接矩形的范围。 614 * @return {BMap.Bounds} 计算出的范围。 615 */ 616 Cluster.prototype.getBounds = function() { 617 var bounds = new BMap.Bounds(this._center,this._center); 618 for (var i = 0, marker; marker = this._markers[i]; i++) { 619 bounds.extend(marker.getPosition()); 620 } 621 return bounds; 622 }; 623 624 /** 625 * 获取该聚合的落脚点。 626 * @return {BMap.Point} 该聚合的落脚点。 627 */ 628 Cluster.prototype.getCenter = function() { 629 return this._center; 630 }; 631 632 })();
被修改的地方为三处
第一:在第198行,MarkerClusterer.createClusters最后加入了以下代码
var len = this._markers.length; for (var i = 0; i < len; i++) { if(this._clusters[i]){ this._clusters[i].render(); } }
第二:在第494行注释了如下代码
// var len = this._markers.length; // if(len < this._minClusterSize ){ // this._map.addOverlay(marker); // //this.updateClusterMarker(); // return true; // } else if (len === this._minClusterSize) { // for (var i = 0; i < len; i++) { // this._markers[i].getMap() && this._map.removeOverlay(this._markers[i]); // } // // } // this._map.addOverlay(this._clusterMarker); // this._isReal = true; // this.updateClusterMarker(); // return true;
第三:在第511行,加入了新方法
/** * 进行dom操作 * @return 无返回值 */ Cluster.prototype.render = function(){ var len = this._markers.length; if (len < this._minClusterSize) { for (var i = 0; i < len; i++) { this._map.addOverlay(this._markers[i]); } } else { this._map.addOverlay(this._clusterMarker); this._isReal = true; this.updateClusterMarker(); } }