大致問題,由於百度地圖點聚合在超過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(); } }