1/* 2 Leaflet.markercluster, Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps. 3 https://github.com/Leaflet/Leaflet.markercluster 4 (c) 2012-2013, Dave Leaver, smartrak 5*/ 6(function (window, document, undefined) {/* 7 * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within 8 */ 9 10L.MarkerClusterGroup = L.FeatureGroup.extend({ 11 12 options: { 13 maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center 14 iconCreateFunction: null, 15 16 spiderfyOnMaxZoom: true, 17 showCoverageOnHover: true, 18 zoomToBoundsOnClick: true, 19 singleMarkerMode: false, 20 21 disableClusteringAtZoom: null, 22 23 // Setting this to false prevents the removal of any clusters outside of the viewpoint, which 24 // is the default behaviour for performance reasons. 25 removeOutsideVisibleBounds: true, 26 27 //Whether to animate adding markers after adding the MarkerClusterGroup to the map 28 // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains. 29 animateAddingMarkers: false, 30 31 //Increase to increase the distance away that spiderfied markers appear from the center 32 spiderfyDistanceMultiplier: 1, 33 34 // When bulk adding layers, adds markers in chunks. Means addLayers may not add all the layers in the call, others will be loaded during setTimeouts 35 chunkedLoading: false, 36 chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback) 37 chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser 38 chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator) 39 40 //Options to pass to the L.Polygon constructor 41 polygonOptions: {} 42 }, 43 44 initialize: function (options) { 45 L.Util.setOptions(this, options); 46 if (!this.options.iconCreateFunction) { 47 this.options.iconCreateFunction = this._defaultIconCreateFunction; 48 } 49 50 this._featureGroup = L.featureGroup(); 51 this._featureGroup.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); 52 53 this._nonPointGroup = L.featureGroup(); 54 this._nonPointGroup.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); 55 56 this._inZoomAnimation = 0; 57 this._needsClustering = []; 58 this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of 59 //The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move 60 this._currentShownBounds = null; 61 62 this._queue = []; 63 }, 64 65 addLayer: function (layer) { 66 67 if (layer instanceof L.LayerGroup) { 68 var array = []; 69 for (var i in layer._layers) { 70 array.push(layer._layers[i]); 71 } 72 return this.addLayers(array); 73 } 74 75 //Don't cluster non point data 76 if (!layer.getLatLng) { 77 this._nonPointGroup.addLayer(layer); 78 return this; 79 } 80 81 if (!this._map) { 82 this._needsClustering.push(layer); 83 return this; 84 } 85 86 if (this.hasLayer(layer)) { 87 return this; 88 } 89 90 91 //If we have already clustered we'll need to add this one to a cluster 92 93 if (this._unspiderfy) { 94 this._unspiderfy(); 95 } 96 97 this._addLayer(layer, this._maxZoom); 98 99 //Work out what is visible 100 var visibleLayer = layer, 101 currentZoom = this._map.getZoom(); 102 if (layer.__parent) { 103 while (visibleLayer.__parent._zoom >= currentZoom) { 104 visibleLayer = visibleLayer.__parent; 105 } 106 } 107 108 if (this._currentShownBounds.contains(visibleLayer.getLatLng())) { 109 if (this.options.animateAddingMarkers) { 110 this._animationAddLayer(layer, visibleLayer); 111 } else { 112 this._animationAddLayerNonAnimated(layer, visibleLayer); 113 } 114 } 115 return this; 116 }, 117 118 removeLayer: function (layer) { 119 120 if (layer instanceof L.LayerGroup) 121 { 122 var array = []; 123 for (var i in layer._layers) { 124 array.push(layer._layers[i]); 125 } 126 return this.removeLayers(array); 127 } 128 129 //Non point layers 130 if (!layer.getLatLng) { 131 this._nonPointGroup.removeLayer(layer); 132 return this; 133 } 134 135 if (!this._map) { 136 if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) { 137 this._needsRemoving.push(layer); 138 } 139 return this; 140 } 141 142 if (!layer.__parent) { 143 return this; 144 } 145 146 if (this._unspiderfy) { 147 this._unspiderfy(); 148 this._unspiderfyLayer(layer); 149 } 150
151 //Remove the marker from clusters 152 this._removeLayer(layer, true); 153 154 if (this._featureGroup.hasLayer(layer)) { 155 this._featureGroup.removeLayer(layer); 156 if (layer.setOpacity) { 157 layer.setOpacity(1); 158 } 159 } 160 161 return this; 162 }, 163 164 //Takes an array of markers and adds them in bulk 165 addLayers: function (layersArray) { 166 var fg = this._featureGroup, 167 npg = this._nonPointGroup, 168 chunked = this.options.chunkedLoading, 169 chunkInterval = this.options.chunkInterval, 170 chunkProgress = this.options.chunkProgress, 171 newMarkers, i, l, m; 172 173 if (this._map) { 174 var offset = 0, 175 started = (new Date()).getTime(); 176 var process = L.bind(function () { 177 var start = (new Date()).getTime(); 178 for (; offset < layersArray.length; offset++) { 179 if (chunked && offset % 200 === 0) { 180 // every couple hundred markers, instrument the time elapsed since processing started: 181 var elapsed = (new Date()).getTime() - start; 182 if (elapsed > chunkInterval) { 183 break; // been working too hard, time to take a break :-) 184 } 185 } 186 187 m = layersArray[offset]; 188 189 //Not point data, can't be clustered 190 if (!m.getLatLng) { 191 npg.addLayer(m); 192 continue; 193 } 194 195 if (this.hasLayer(m)) { 196 continue; 197 } 198 199 this._addLayer(m, this._maxZoom); 200 201 //If we just made a cluster of size 2 then we need to remove the other marker from the map (if it is) or we never will 202 if (m.__parent) { 203 if (m.__parent.getChildCount() === 2) { 204 var markers = m.__parent.getAllChildMarkers(), 205 otherMarker = markers[0] === m ? markers[1] : markers[0]; 206 fg.removeLayer(otherMarker); 207 } 208 } 209 } 210 211 if (chunkProgress) { 212 // report progress and time elapsed: 213 chunkProgress(offset, layersArray.length, (new Date()).getTime() - started); 214 } 215 216 if (offset === layersArray.length) { 217 //Update the icons of all those visible clusters that were affected 218 this._featureGroup.eachLayer(function (c) { 219 if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) { 220 c._updateIcon(); 221 } 222 }); 223 224 this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds); 225 } else { 226 setTimeout(process, this.options.chunkDelay); 227 } 228 }, this); 229 230 process(); 231 } else { 232 newMarkers = []; 233 for (i = 0, l = layersArray.length; i < l; i++) { 234 m = layersArray[i]; 235 236 //Not point data, can't be clustered 237 if (!m.getLatLng) { 238 npg.addLayer(m); 239 continue; 240 } 241 242 if (this.hasLayer(m)) { 243 continue; 244 } 245 246 newMarkers.push(m); 247 } 248 this._needsClustering = this._needsClustering.concat(newMarkers); 249 } 250 return this; 251 }, 252 253 //Takes an array of markers and removes them in bulk 254 removeLayers: function (layersArray) { 255 var i, l, m, 256 fg = this._featureGroup, 257 npg = this._nonPointGroup; 258 259 if (!this._map) { 260 for (i = 0, l = layersArray.length; i < l; i++) { 261 m = layersArray[i]; 262 this._arraySplice(this._needsClustering, m); 263 npg.removeLayer(m); 264 } 265 return this; 266 } 267 268 for (i = 0, l = layersArray.length; i < l; i++) { 269 m = layersArray[i]; 270 271 if (!m.__parent) { 272 npg.removeLayer(m); 273 continue; 274 } 275 276 this._removeLayer(m, true, true); 277 278 if (fg.hasLayer(m)) { 279 fg.removeLayer(m); 280 if (m.setOpacity) { 281 m.setOpacity(1); 282 } 283 } 284 } 285 286 //Fix up the clusters and markers on the map 287 this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds); 288 289 fg.eachLayer(function (c) { 290 if (c instanceof L.MarkerCluster) { 291 c._updateIcon(); 292 } 293 }); 294 295 return this; 296 }, 297 298 //Removes all layers from the MarkerClusterGroup 299 clearLayers: function () { 300 //Need our own special implementation as the LayerGroup one doesn't work for us 301 302 //If we aren't on the map (yet), blow away the markers we know of 303 if (!this._map) { 304 this._needsClustering = []; 305 delete this._gridClusters; 306 delete this._gridUnclustered; 307 } 308 309 if (this._noanimationUnspiderfy) { 310 this._noanimationUnspiderfy(); 311 } 312 313 //Remove all the visible layers 314 this._featureGroup.clearLayers(); 315 this._nonPointGroup.clearLayers(); 316 317 this.eachLayer(function (marker) { 318 delete marker.__parent; 319 }); 320 321 if (this._map) { 322 //Reset _topClusterLevel and the DistanceGrids 323 this._generateInitialClusters(); 324 } 325 326 return this; 327 }, 328 329 //Override FeatureGroup.getBounds as it doesn't work 330 getBounds: function () { 331 var bounds = new L.LatLngBounds(); 332 333 if (this._topClusterLevel) { 334 bounds.extend(this._topClusterLevel._bounds); 335 } 336 337 for (var i = this._needsClustering.length - 1; i >= 0; i--) { 338 bounds.extend(this._needsClustering[i].getLatLng()); 339 } 340 341 bounds.extend(this._nonPointGroup.getBounds()); 342 343 return bounds; 344 }, 345 346 //Overrides LayerGroup.eachLayer 347 eachLayer: function (method, context) { 348 var markers = this._needsClustering.slice(), 349 i; 350 351 if (this._topClusterLevel) { 352 this._topClusterLevel.getAllChildMarkers(markers); 353 } 354 355 for (i = markers.length - 1; i >= 0; i--) { 356 method.call(context, markers[i]); 357 } 358 359 this._nonPointGroup.eachLayer(method, context); 360 }, 361 362 //Overrides LayerGroup.getLayers 363 getLayers: function () { 364 var layers = []; 365 this.eachLayer(function (l) { 366 layers.push(l); 367 }); 368 return layers; 369 }, 370 371 //Overrides LayerGroup.getLayer, WARNING: Really bad performance 372 getLayer: function (id) { 373 var result = null; 374 375 this.eachLayer(function (l) { 376 if (L.stamp(l) === id) { 377 result = l; 378 } 379 }); 380 381 return result; 382 }, 383 384 //Returns true if the given layer is in this MarkerClusterGroup 385 hasLayer: function (layer) { 386 if (!layer) { 387 return false; 388 } 389 390 var i, anArray = this._needsClustering; 391 392 for (i = anArray.length - 1; i >= 0; i--) { 393 if (anArray[i] === layer) { 394 return true; 395 } 396 } 397 398 anArray = this._needsRemoving; 399 for (i = anArray.length - 1; i >= 0; i--) { 400 if (anArray[i] === layer) { 401 return false; 402 } 403 } 404 405 return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer); 406 }, 407 408 //Zoom down to show the given layer (spiderfying if necessary) then calls the callback 409 zoomToShowLayer: function (layer, callback) { 410 411 var showMarker = function () { 412 if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) { 413 this._map.off('moveend', showMarker, this); 414 this.off('animationend', showMarker, this); 415 416 if (layer._icon) { 417 callback(); 418 } else if (layer.__parent._icon) { 419 var afterSpiderfy = function () { 420 this.off('spiderfied', afterSpiderfy, this); 421 callback(); 422 }; 423 424 this.on('spiderfied', afterSpiderfy, this); 425 layer.__parent.spiderfy(); 426 } 427 } 428 }; 429 430 if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) { 431 //Layer is visible ond on screen, immediate return 432 callback(); 433 } else if (layer.__parent._zoom < this._map.getZoom()) { 434 //Layer should be visible at this zoom level. It must not be on screen so just pan over to it 435 this._map.on('moveend', showMarker, this); 436 this._map.panTo(layer.getLatLng()); 437 } else { 438 var moveStart = function () { 439 this._map.off('movestart', moveStart, this); 440 moveStart = null; 441 }; 442 443 this._map.on('movestart', moveStart, this); 444 this._map.on('moveend', showMarker, this); 445 this.on('animationend', showMarker, this); 446 layer.__parent.zoomToBounds(); 447 448 if (moveStart) { 449 //Never started moving, must already be there, probably need clustering however 450 showMarker.call(this); 451 } 452 } 453 }, 454 455 //Overrides FeatureGroup.onAdd 456 onAdd: function (map) { 457 this._map = map; 458 var i, l, layer; 459 460 if (!isFinite(this._map.getMaxZoom())) { 461 throw "Map has no maxZoom specified"; 462 } 463
464 this._featureGroup.onAdd(map); 465 this._nonPointGroup.onAdd(map); 466 467 if (!this._gridClusters) { 468 this._generateInitialClusters(); 469 } 470 471 for (i = 0, l = this._needsRemoving.length; i < l; i++) { 472 layer = this._needsRemoving[i]; 473 this._removeLayer(layer, true); 474 } 475 this._needsRemoving = []; 476 477 //Remember the current zoom level and bounds 478 this._zoom = this._map.getZoom(); 479 this._currentShownBounds = this._getExpandedVisibleBounds(); 480 481 this._map.on('zoomend', this._zoomEnd, this); 482 this._map.on('moveend', this._moveEnd, this); 483 484 if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely 485 this._spiderfierOnAdd(); 486 } 487 488 this._bindEvents(); 489 490 //Actually add our markers to the map: 491 l = this._needsClustering; 492 this._needsClustering = []; 493 this.addLayers(l); 494 }, 495 496 //Overrides FeatureGroup.onRemove 497 onRemove: function (map) { 498 map.off('zoomend', this._zoomEnd, this); 499 map.off('moveend', this._moveEnd, this); 500 501 this._unbindEvents(); 502 503 //In case we are in a cluster animation 504 this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', ''); 505 506 if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely 507 this._spiderfierOnRemove(); 508 } 509 510 511 512 //Clean up all the layers we added to the map 513 this._hideCoverage(); 514 this._featureGroup.onRemove(map); 515 this._nonPointGroup.onRemove(map); 516 517 this._featureGroup.clearLayers(); 518 519 this._map = null; 520 }, 521 522 getVisibleParent: function (marker) { 523 var vMarker = marker; 524 while (vMarker && !vMarker._icon) { 525 vMarker = vMarker.__parent; 526 } 527 return vMarker || null; 528 }, 529 530 //Remove the given object from the given array 531 _arraySplice: function (anArray, obj) { 532 for (var i = anArray.length - 1; i >= 0; i--) { 533 if (anArray[i] === obj) { 534 anArray.splice(i, 1); 535 return true; 536 } 537 } 538 }, 539 540 //Internal function for removing a marker from everything. 541 //dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions) 542 _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) { 543 var gridClusters = this._gridClusters, 544 gridUnclustered = this._gridUnclustered, 545 fg = this._featureGroup, 546 map = this._map; 547 548 //Remove the marker from distance clusters it might be in 549 if (removeFromDistanceGrid) { 550 for (var z = this._maxZoom; z >= 0; z--) { 551 if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) { 552 break; 553 } 554 } 555 } 556 557 //Work our way up the clusters removing them as we go if required 558 var cluster = marker.__parent, 559 markers = cluster._markers, 560 otherMarker; 561 562 //Remove the marker from the immediate parents marker list 563 this._arraySplice(markers, marker); 564 565 while (cluster) { 566 cluster._childCount--; 567 568 if (cluster._zoom < 0) { 569 //Top level, do nothing 570 break; 571 } else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required 572 //We need to push the other marker up to the parent 573 otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0]; 574 575 //Update distance grid 576 gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom)); 577 gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom)); 578 579 //Move otherMarker up to parent 580 this._arraySplice(cluster.__parent._childClusters, cluster); 581 cluster.__parent._markers.push(otherMarker); 582 otherMarker.__parent = cluster.__parent; 583 584 if (cluster._icon) { 585 //Cluster is currently on the map, need to put the marker on the map instead 586 fg.removeLayer(cluster); 587 if (!dontUpdateMap) { 588 fg.addLayer(otherMarker); 589 } 590 } 591 } else { 592 cluster._recalculateBounds(); 593 if (!dontUpdateMap || !cluster._icon) { 594 cluster._updateIcon(); 595 } 596 } 597 598 cluster = cluster.__parent; 599 } 600 601 delete marker.__parent; 602 }, 603 604 _isOrIsParent: function (el, oel) { 605 while (oel) { 606 if (el === oel) { 607 return true; 608 } 609 oel = oel.parentNode; 610 } 611 return false; 612 }, 613
614 _propagateEvent: function (e) { 615 if (e.layer instanceof L.MarkerCluster) { 616 //Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget) 617 if (e.originalEvent && this._isOrIsParent(e.layer._icon, e.originalEvent.relatedTarget)) { 618 return; 619 } 620 e.type = 'cluster' + e.type; 621 } 622 623 this.fire(e.type, e); 624 }, 625 626 //Default functionality 627 _defaultIconCreateFunction: function (cluster) { 628 var childCount = cluster.getChildCount(); 629 630 var c = ' marker-cluster-'; 631 if (childCount < 10) { 632 c += 'small'; 633 } else if (childCount < 100) { 634 c += 'medium'; 635 } else { 636 c += 'large'; 637 } 638 639 return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) }); 640 }, 641 642 _bindEvents: function () { 643 var map = this._map, 644 spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom, 645 showCoverageOnHover = this.options.showCoverageOnHover, 646 zoomToBoundsOnClick = this.options.zoomToBoundsOnClick; 647 648 //Zoom on cluster click or spiderfy if we are at the lowest level 649 if (spiderfyOnMaxZoom || zoomToBoundsOnClick) { 650 this.on('clusterclick', this._zoomOrSpiderfy, this); 651 } 652 653 //Show convex hull (boundary) polygon on mouse over 654 if (showCoverageOnHover) { 655 this.on('clustermouseover', this._showCoverage, this); 656 this.on('clustermouseout', this._hideCoverage, this); 657 map.on('zoomend', this._hideCoverage, this); 658 } 659 }, 660 661 _zoomOrSpiderfy: function (e) { 662 var map = this._map; 663 if (map.getMaxZoom() === map.getZoom()) { 664 if (this.options.spiderfyOnMaxZoom) { 665 e.layer.spiderfy(); 666 } 667 } else if (this.options.zoomToBoundsOnClick) { 668 e.layer.zoomToBounds(); 669 } 670 671 // Focus the map again for keyboard users. 672 if (e.originalEvent && e.originalEvent.keyCode === 13) { 673 map._container.focus(); 674 } 675 }, 676 677 _showCoverage: function (e) { 678 var map = this._map; 679 if (this._inZoomAnimation) { 680 return; 681 } 682 if (this._shownPolygon) { 683 map.removeLayer(this._shownPolygon); 684 } 685 if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) { 686 this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions); 687 map.addLayer(this._shownPolygon); 688 } 689 }, 690 691 _hideCoverage: function () { 692 if (this._shownPolygon) { 693 this._map.removeLayer(this._shownPolygon); 694 this._shownPolygon = null; 695 } 696 }, 697 698 _unbindEvents: function () { 699 var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom, 700 showCoverageOnHover = this.options.showCoverageOnHover, 701 zoomToBoundsOnClick = this.options.zoomToBoundsOnClick, 702 map = this._map; 703 704 if (spiderfyOnMaxZoom || zoomToBoundsOnClick) { 705 this.off('clusterclick', this._zoomOrSpiderfy, this); 706 } 707 if (showCoverageOnHover) { 708 this.off('clustermouseover', this._showCoverage, this); 709 this.off('clustermouseout', this._hideCoverage, this); 710 map.off('zoomend', this._hideCoverage, this); 711 } 712 }, 713 714 _zoomEnd: function () { 715 if (!this._map) { //May have been removed from the map by a zoomEnd handler 716 return; 717 } 718 this._mergeSplitClusters(); 719 720 this._zoom = this._map._zoom; 721 this._currentShownBounds = this._getExpandedVisibleBounds(); 722 }, 723 724 _moveEnd: function () { 725 if (this._inZoomAnimation) { 726 return; 727 } 728 729 var newBounds = this._getExpandedVisibleBounds(); 730 731 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, newBounds); 732 this._topClusterLevel._recursivelyAddChildrenToMap(null, this._map._zoom, newBounds); 733 734 this._currentShownBounds = newBounds; 735 return; 736 }, 737 738 _generateInitialClusters: function () { 739 var maxZoom = this._map.getMaxZoom(), 740 radius = this.options.maxClusterRadius, 741 radiusFn = radius; 742 743 //If we just set maxClusterRadius to a single number, we need to create 744 //a simple function to return that number. Otherwise, we just have to 745 //use the function we've passed in. 746 if (typeof radius !== "function") {
747 radiusFn = function () { return radius; }; 748 } 749 750 if (this.options.disableClusteringAtZoom) { 751 maxZoom = this.options.disableClusteringAtZoom - 1; 752 } 753 this._maxZoom = maxZoom; 754 this._gridClusters = {}; 755 this._gridUnclustered = {}; 756 757 //Set up DistanceGrids for each zoom 758 for (var zoom = maxZoom; zoom >= 0; zoom--) { 759 this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom)); 760 this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom)); 761 } 762 763 this._topClusterLevel = new L.MarkerCluster(this, -1); 764 }, 765 766 //Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom) 767 _addLayer: function (layer, zoom) { 768 var gridClusters = this._gridClusters, 769 gridUnclustered = this._gridUnclustered, 770 markerPoint, z; 771 772 if (this.options.singleMarkerMode) { 773 layer.options.icon = this.options.iconCreateFunction({ 774 getChildCount: function () { 775 return 1; 776 }, 777 getAllChildMarkers: function () { 778 return [layer]; 779 } 780 }); 781 } 782 783 //Find the lowest zoom level to slot this one in 784 for (; zoom >= 0; zoom--) { 785 markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position 786 787 //Try find a cluster close by 788 var closest = gridClusters[zoom].getNearObject(markerPoint); 789 if (closest) { 790 closest._addChild(layer); 791 layer.__parent = closest; 792 return; 793 } 794 795 //Try find a marker close by to form a new cluster with 796 closest = gridUnclustered[zoom].getNearObject(markerPoint); 797 if (closest) { 798 var parent = closest.__parent; 799 if (parent) { 800 this._removeLayer(closest, false); 801 } 802 803 //Create new cluster with these 2 in it 804 805 var newCluster = new L.MarkerCluster(this, zoom, closest, layer); 806 gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom)); 807 closest.__parent = newCluster; 808 layer.__parent = newCluster; 809 810 //First create any new intermediate parent clusters that don't exist 811 var lastParent = newCluster; 812 for (z = zoom - 1; z > parent._zoom; z--) { 813 lastParent = new L.MarkerCluster(this, z, lastParent); 814 gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z)); 815 } 816 parent._addChild(lastParent); 817 818 //Remove closest from this zoom level and any above that it is in, replace with newCluster 819 for (z = zoom; z >= 0; z--) { 820 if (!gridUnclustered[z].removeObject(closest, this._map.project(closest.getLatLng(), z))) { 821 break; 822 } 823 } 824 825 return; 826 } 827 828 //Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards 829 gridUnclustered[zoom].addObject(layer, markerPoint); 830 } 831 832 //Didn't get in anything, add us to the top 833 this._topClusterLevel._addChild(layer); 834 layer.__parent = this._topClusterLevel; 835 return; 836 }, 837 838 //Enqueue code to fire after the marker expand/contract has happened 839 _enqueue: function (fn) { 840 this._queue.push(fn); 841 if (!this._queueTimeout) { 842 this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300); 843 } 844 }, 845 _processQueue: function () { 846 for (var i = 0; i < this._queue.length; i++) { 847 this._queue[i].call(this); 848 } 849 this._queue.length = 0; 850 clearTimeout(this._queueTimeout); 851 this._queueTimeout = null; 852 }, 853 854 //Merge and split any existing clusters that are too big or small 855 _mergeSplitClusters: function () { 856 857 //Incase we are starting to split before the animation finished 858 this._processQueue(); 859 860 if (this._zoom < this._map._zoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split 861 this._animationStart(); 862 //Remove clusters now off screen 863 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, this._getExpandedVisibleBounds()); 864 865 this._animationZoomIn(this._zoom, this._map._zoom); 866 867 } else if (this._zoom > this._map._zoom) { //Zoom out, merge 868 this._animationStart(); 869 870 this._animationZoomOut(this._zoom, this._map._zoom); 871 } else { 872 this._moveEnd(); 873 } 874 }, 875 876 //Gets the maps visible bounds expanded in each direction by the size of the screen (so the user cannot see an area we do not cover in one pan) 877 _getExpandedVisibleBounds: function () { 878 if (!this.options.removeOutsideVisibleBounds) { 879 return this.getBounds(); 880 } 881 882 var map = this._map, 883 bounds = map.getBounds(), 884 sw = bounds._southWest, 885 ne = bounds._northEast, 886 latDiff = L.Browser.mobile ? 0 : Math.abs(sw.lat - ne.lat), 887 lngDiff = L.Browser.mobile ? 0 : Math.abs(sw.lng - ne.lng); 888 889 return new L.LatLngBounds( 890 new L.LatLng(sw.lat - latDiff, sw.lng - lngDiff, true), 891 new L.LatLng(ne.lat + latDiff, ne.lng + lngDiff, true)); 892 }, 893 894 //Shared animation code 895 _animationAddLayerNonAnimated: function (layer, newCluster) { 896 if (newCluster === layer) { 897 this._featureGroup.addLayer(layer); 898 } else if (newCluster._childCount === 2) { 899 newCluster._addToMap(); 900 901 var markers = newCluster.getAllChildMarkers(); 902 this._featureGroup.removeLayer(markers[0]); 903 this._featureGroup.removeLayer(markers[1]); 904 } else { 905 newCluster._updateIcon(); 906 } 907 } 908}); 909 910L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? { 911
912 //Non Animated versions of everything 913 _animationStart: function () { 914 //Do nothing... 915 }, 916 _animationZoomIn: function (previousZoomLevel, newZoomLevel) { 917 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel); 918 this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()); 919 920 //We didn't actually animate, but we use this event to mean "clustering animations have finished" 921 this.fire('animationend'); 922 }, 923 _animationZoomOut: function (previousZoomLevel, newZoomLevel) { 924 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel); 925 this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()); 926 927 //We didn't actually animate, but we use this event to mean "clustering animations have finished" 928 this.fire('animationend'); 929 }, 930 _animationAddLayer: function (layer, newCluster) { 931 this._animationAddLayerNonAnimated(layer, newCluster); 932 } 933} : { 934 935 //Animated versions here 936 _animationStart: function () { 937 this._map._mapPane.className += ' leaflet-cluster-anim'; 938 this._inZoomAnimation++; 939 }, 940 _animationEnd: function () { 941 if (this._map) { 942 this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', ''); 943 } 944 this._inZoomAnimation--; 945 this.fire('animationend'); 946 }, 947 _animationZoomIn: function (previousZoomLevel, newZoomLevel) { 948 var bounds = this._getExpandedVisibleBounds(), 949 fg = this._featureGroup, 950 i; 951 952 //Add all children of current clusters to map and remove those clusters from map 953 this._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) { 954 var startPos = c._latlng, 955 markers = c._markers, 956 m; 957 958 if (!bounds.contains(startPos)) { 959 startPos = null; 960 } 961 962 if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us 963 fg.removeLayer(c); 964 c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds); 965 } else { 966 //Fade out old cluster 967 c.setOpacity(0); 968 c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds); 969 } 970 971 //Remove all markers that aren't visible any more 972 //TODO: Do we actually need to do this on the higher levels too? 973 for (i = markers.length - 1; i >= 0; i--) { 974 m = markers[i]; 975 if (!bounds.contains(m._latlng)) { 976 fg.removeLayer(m); 977 } 978 } 979 980 }); 981 982 this._forceLayout(); 983 984 //Update opacities 985 this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel); 986 //TODO Maybe? Update markers in _recursivelyBecomeVisible 987 fg.eachLayer(function (n) { 988 if (!(n instanceof L.MarkerCluster) && n._icon) { 989 n.setOpacity(1); 990 } 991 }); 992 993 //update the positions of the just added clusters/markers 994 this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) { 995 c._recursivelyRestoreChildPositions(newZoomLevel); 996 }); 997 998 //Remove the old clusters and close the zoom animation 999 this._enqueue(function () { 1000 //update the positions of the just added clusters/markers 1001 this._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) { 1002 fg.removeLayer(c); 1003 c.setOpacity(1); 1004 }); 1005 1006 this._animationEnd(); 1007 }); 1008 }, 1009 1010 _animationZoomOut: function (previousZoomLevel, newZoomLevel) { 1011 this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel); 1012 1013 //Need to add markers for those that weren't on the map before but are now 1014 this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds()); 1015 //Remove markers that were on the map before but won't be now 1016 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel, this._getExpandedVisibleBounds()); 1017 }, 1018 _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) { 1019 var bounds = this._getExpandedVisibleBounds(); 1020
1021 //Animate all of the markers in the clusters to move to their cluster center point 1022 cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, previousZoomLevel + 1, newZoomLevel); 1023 1024 var me = this; 1025 1026 //Update the opacity (If we immediately set it they won't animate) 1027 this._forceLayout(); 1028 cluster._recursivelyBecomeVisible(bounds, newZoomLevel); 1029 1030 //TODO: Maybe use the transition timing stuff to make this more reliable 1031 //When the animations are done, tidy up 1032 this._enqueue(function () { 1033 1034 //This cluster stopped being a cluster before the timeout fired 1035 if (cluster._childCount === 1) { 1036 var m = cluster._markers[0]; 1037 //If we were in a cluster animation at the time then the opacity and position of our child could be wrong now, so fix it 1038 m.setLatLng(m.getLatLng()); 1039 if (m.setOpacity) { 1040 m.setOpacity(1); 1041 } 1042 } else { 1043 cluster._recursively(bounds, newZoomLevel, 0, function (c) { 1044 c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel + 1); 1045 }); 1046 } 1047 me._animationEnd(); 1048 }); 1049 }, 1050 _animationAddLayer: function (layer, newCluster) { 1051 var me = this, 1052 fg = this._featureGroup; 1053 1054 fg.addLayer(layer); 1055 if (newCluster !== layer) { 1056 if (newCluster._childCount > 2) { //Was already a cluster 1057 1058 newCluster._updateIcon(); 1059 this._forceLayout(); 1060 this._animationStart(); 1061 1062 layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng())); 1063 layer.setOpacity(0); 1064 1065 this._enqueue(function () { 1066 fg.removeLayer(layer); 1067 layer.setOpacity(1); 1068 1069 me._animationEnd(); 1070 }); 1071 1072 } else { //Just became a cluster 1073 this._forceLayout(); 1074 1075 me._animationStart(); 1076 me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._map.getZoom()); 1077 } 1078 } 1079 }, 1080 1081 //Force a browser layout of stuff in the map 1082 // Should apply the current opacity and location to all elements so we can update them again for an animation 1083 _forceLayout: function () { 1084 //In my testing this works, infact offsetWidth of any element seems to work. 1085 //Could loop all this._layers and do this for each _icon if it stops working 1086 1087 L.Util.falseFn(document.body.offsetWidth); 1088 } 1089}); 1090 1091L.markerClusterGroup = function (options) { 1092 return new L.MarkerClusterGroup(options); 1093}; 1094 1095 1096L.MarkerCluster = L.Marker.extend({ 1097 initialize: function (group, zoom, a, b) { 1098 1099 L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), { icon: this }); 1100 1101 1102 this._group = group; 1103 this._zoom = zoom; 1104 1105 this._markers = []; 1106 this._childClusters = []; 1107 this._childCount = 0; 1108 this._iconNeedsUpdate = true; 1109 1110 this._bounds = new L.LatLngBounds(); 1111 1112 if (a) { 1113 this._addChild(a); 1114 } 1115 if (b) { 1116 this._addChild(b); 1117 } 1118 }, 1119 1120 //Recursively retrieve all child markers of this cluster 1121 getAllChildMarkers: function (storageArray) { 1122 storageArray = storageArray || []; 1123 1124 for (var i = this._childClusters.length - 1; i >= 0; i--) { 1125 this._childClusters[i].getAllChildMarkers(storageArray); 1126 } 1127 1128 for (var j = this._markers.length - 1; j >= 0; j--) { 1129 storageArray.push(this._markers[j]); 1130 } 1131 1132 return storageArray; 1133 }, 1134 1135 //Returns the count of how many child markers we have 1136 getChildCount: function () { 1137 return this._childCount; 1138 }, 1139 1140 //Zoom to the minimum of showing all of the child markers, or the extents of this cluster 1141 zoomToBounds: function () { 1142 var childClusters = this._childClusters.slice(), 1143 map = this._group._map, 1144 boundsZoom = map.getBoundsZoom(this._bounds), 1145 zoom = this._zoom + 1, 1146 mapZoom = map.getZoom(), 1147 i; 1148 1149 //calculate how far we need to zoom down to see all of the markers 1150 while (childClusters.length > 0 && boundsZoom > zoom) { 1151 zoom++; 1152 var newClusters = []; 1153 for (i = 0; i < childClusters.length; i++) { 1154 newClusters = newClusters.concat(childClusters[i]._childClusters); 1155 } 1156 childClusters = newClusters; 1157 } 1158 1159 if (boundsZoom > zoom) {
1160 this._group._map.setView(this._latlng, zoom); 1161 } else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead 1162 this._group._map.setView(this._latlng, mapZoom + 1); 1163 } else { 1164 this._group._map.fitBounds(this._bounds); 1165 } 1166 }, 1167 1168 getBounds: function () { 1169 var bounds = new L.LatLngBounds(); 1170 bounds.extend(this._bounds); 1171 return bounds; 1172 }, 1173 1174 _updateIcon: function () { 1175 this._iconNeedsUpdate = true; 1176 if (this._icon) { 1177 this.setIcon(this); 1178 } 1179 }, 1180 1181 //Cludge for Icon, we pretend to be an icon for performance 1182 createIcon: function () { 1183 if (this._iconNeedsUpdate) { 1184 this._iconObj = this._group.options.iconCreateFunction(this); 1185 this._iconNeedsUpdate = false; 1186 } 1187 return this._iconObj.createIcon(); 1188 }, 1189 createShadow: function () { 1190 return this._iconObj.createShadow(); 1191 }, 1192 1193 1194 _addChild: function (new1, isNotificationFromChild) { 1195 1196 this._iconNeedsUpdate = true; 1197 this._expandBounds(new1); 1198 1199 if (new1 instanceof L.MarkerCluster) { 1200 if (!isNotificationFromChild) { 1201 this._childClusters.push(new1); 1202 new1.__parent = this; 1203 } 1204 this._childCount += new1._childCount; 1205 } else { 1206 if (!isNotificationFromChild) { 1207 this._markers.push(new1); 1208 } 1209 this._childCount++; 1210 } 1211 1212 if (this.__parent) { 1213 this.__parent._addChild(new1, true); 1214 } 1215 }, 1216 1217 //Expand our bounds and tell our parent to 1218 _expandBounds: function (marker) { 1219 var addedCount, 1220 addedLatLng = marker._wLatLng || marker._latlng; 1221 1222 if (marker instanceof L.MarkerCluster) { 1223 this._bounds.extend(marker._bounds); 1224 addedCount = marker._childCount; 1225 } else { 1226 this._bounds.extend(addedLatLng); 1227 addedCount = 1; 1228 } 1229 1230 if (!this._cLatLng) { 1231 // when clustering, take position of the first point as the cluster center 1232 this._cLatLng = marker._cLatLng || addedLatLng; 1233 } 1234 1235 // when showing clusters, take weighted average of all points as cluster center 1236 var totalCount = this._childCount + addedCount; 1237 1238 //Calculate weighted latlng for display 1239 if (!this._wLatLng) { 1240 this._latlng = this._wLatLng = new L.LatLng(addedLatLng.lat, addedLatLng.lng); 1241 } else { 1242 this._wLatLng.lat = (addedLatLng.lat * addedCount + this._wLatLng.lat * this._childCount) / totalCount; 1243 this._wLatLng.lng = (addedLatLng.lng * addedCount + this._wLatLng.lng * this._childCount) / totalCount; 1244 } 1245 }, 1246 1247 //Set our markers position as given and add it to the map 1248 _addToMap: function (startPos) { 1249 if (startPos) { 1250 this._backupLatlng = this._latlng; 1251 this.setLatLng(startPos); 1252 } 1253 this._group._featureGroup.addLayer(this); 1254 }, 1255 1256 _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) { 1257 this._recursively(bounds, 0, maxZoom - 1, 1258 function (c) { 1259 var markers = c._markers, 1260 i, m; 1261 for (i = markers.length - 1; i >= 0; i--) { 1262 m = markers[i]; 1263 1264 //Only do it if the icon is still on the map 1265 if (m._icon) { 1266 m._setPos(center); 1267 m.setOpacity(0); 1268 } 1269 } 1270 }, 1271 function (c) { 1272 var childClusters = c._childClusters, 1273 j, cm; 1274 for (j = childClusters.length - 1; j >= 0; j--) { 1275 cm = childClusters[j]; 1276 if (cm._icon) { 1277 cm._setPos(center); 1278 cm.setOpacity(0); 1279 } 1280 } 1281 } 1282 ); 1283 }, 1284 1285 _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, previousZoomLevel, newZoomLevel) { 1286 this._recursively(bounds, newZoomLevel, 0, 1287 function (c) { 1288 c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel); 1289 1290 //TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be. 1291 //As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate 1292 if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) { 1293 c.setOpacity(1); 1294 c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds 1295 } else { 1296 c.setOpacity(0); 1297 } 1298 1299 c._addToMap(); 1300 } 1301 ); 1302 }, 1303 1304 _recursivelyBecomeVisible: function (bounds, zoomLevel) { 1305 this._recursively(bounds, 0, zoomLevel, null, function (c) { 1306 c.setOpacity(1); 1307 }); 1308 }, 1309 1310 _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) { 1311 this._recursively(bounds, -1, zoomLevel, 1312 function (c) { 1313 if (zoomLevel === c._zoom) { 1314 return; 1315 } 1316 1317 //Add our child markers at startPos (so they can be animated out) 1318 for (var i = c._markers.length - 1; i >= 0; i--) { 1319 var nm = c._markers[i];
1320 1321 if (!bounds.contains(nm._latlng)) { 1322 continue; 1323 } 1324 1325 if (startPos) { 1326 nm._backupLatlng = nm.getLatLng(); 1327 1328 nm.setLatLng(startPos); 1329 if (nm.setOpacity) { 1330 nm.setOpacity(0); 1331 } 1332 } 1333 1334 c._group._featureGroup.addLayer(nm); 1335 } 1336 }, 1337 function (c) { 1338 c._addToMap(startPos); 1339 } 1340 ); 1341 }, 1342 1343 _recursivelyRestoreChildPositions: function (zoomLevel) { 1344 //Fix positions of child markers 1345 for (var i = this._markers.length - 1; i >= 0; i--) { 1346 var nm = this._markers[i]; 1347 if (nm._backupLatlng) { 1348 nm.setLatLng(nm._backupLatlng); 1349 delete nm._backupLatlng; 1350 } 1351 } 1352 1353 if (zoomLevel - 1 === this._zoom) { 1354 //Reposition child clusters 1355 for (var j = this._childClusters.length - 1; j >= 0; j--) { 1356 this._childClusters[j]._restorePosition(); 1357 } 1358 } else { 1359 for (var k = this._childClusters.length - 1; k >= 0; k--) { 1360 this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel); 1361 } 1362 } 1363 }, 1364 1365 _restorePosition: function () { 1366 if (this._backupLatlng) { 1367 this.setLatLng(this._backupLatlng); 1368 delete this._backupLatlng; 1369 } 1370 }, 1371 1372 //exceptBounds: If set, don't remove any markers/clusters in it 1373 _recursivelyRemoveChildrenFromMap: function (previousBounds, zoomLevel, exceptBounds) { 1374 var m, i; 1375 this._recursively(previousBounds, -1, zoomLevel - 1, 1376 function (c) { 1377 //Remove markers at every level 1378 for (i = c._markers.length - 1; i >= 0; i--) { 1379 m = c._markers[i]; 1380 if (!exceptBounds || !exceptBounds.contains(m._latlng)) { 1381 c._group._featureGroup.removeLayer(m); 1382 if (m.setOpacity) { 1383 m.setOpacity(1); 1384 } 1385 } 1386 } 1387 }, 1388 function (c) { 1389 //Remove child clusters at just the bottom level 1390 for (i = c._childClusters.length - 1; i >= 0; i--) { 1391 m = c._childClusters[i]; 1392 if (!exceptBounds || !exceptBounds.contains(m._latlng)) { 1393 c._group._featureGroup.removeLayer(m); 1394 if (m.setOpacity) { 1395 m.setOpacity(1); 1396 } 1397 } 1398 } 1399 } 1400 ); 1401 }, 1402 1403 //Run the given functions recursively to this and child clusters 1404 // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to 1405 // zoomLevelToStart: zoom level to start running functions (inclusive) 1406 // zoomLevelToStop: zoom level to stop running functions (inclusive) 1407 // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level 1408 // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level 1409 _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) { 1410 var childClusters = this._childClusters, 1411 zoom = this._zoom, 1412 i, c; 1413 1414 if (zoomLevelToStart > zoom) { //Still going down to required depth, just recurse to child clusters 1415 for (i = childClusters.length - 1; i >= 0; i--) { 1416 c = childClusters[i]; 1417 if (boundsToApplyTo.intersects(c._bounds)) { 1418 c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel); 1419 } 1420 } 1421 } else { //In required depth 1422 1423 if (runAtEveryLevel) { 1424 runAtEveryLevel(this); 1425 } 1426 if (runAtBottomLevel && this._zoom === zoomLevelToStop) { 1427 runAtBottomLevel(this); 1428 } 1429 1430 //TODO: This loop is almost the same as above 1431 if (zoomLevelToStop > zoom) { 1432 for (i = childClusters.length - 1; i >= 0; i--) { 1433 c = childClusters[i]; 1434 if (boundsToApplyTo.intersects(c._bounds)) { 1435 c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel); 1436 } 1437 } 1438 } 1439 } 1440 }, 1441 1442 _recalculateBounds: function () { 1443 var markers = this._markers, 1444 childClusters = this._childClusters, 1445 i; 1446 1447 this._bounds = new L.LatLngBounds(); 1448 delete this._wLatLng; 1449 1450 for (i = markers.length - 1; i >= 0; i--) { 1451 this._expandBounds(markers[i]); 1452 } 1453 for (i = childClusters.length - 1; i >= 0; i--) { 1454 this._expandBounds(childClusters[i]); 1455 } 1456 }, 1457 1458 1459 //Returns true if we are the parent of only one cluster and that cluster is the same as us 1460 _isSingleParent: function () { 1461 //Don't need to check this._markers as the rest won't work if there are any 1462 return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount; 1463 } 1464}); 1465 1466 1467 1468L.DistanceGrid = function (cellSize) { 1469 this._cellSize = cellSize; 1470 this._sqCellSize = cellSize * cellSize; 1471 this._grid = {}; 1472 this._objectPoint = { }; 1473};
vendor: 8,561 bytes, lines 1474-1785
1474 1475L.DistanceGrid.prototype = { 1476 1477 addObject: function (obj, point) { 1478 var x = this._getCoord(point.x), 1479 y = this._getCoord(point.y), 1480 grid = this._grid, 1481 row = grid[y] = grid[y] || {}, 1482 cell = row[x] = row[x] || [], 1483 stamp = L.Util.stamp(obj); 1484 1485 this._objectPoint[stamp] = point; 1486 1487 cell.push(obj); 1488 }, 1489 1490 updateObject: function (obj, point) { 1491 this.removeObject(obj); 1492 this.addObject(obj, point); 1493 }, 1494 1495 //Returns true if the object was found 1496 removeObject: function (obj, point) { 1497 var x = this._getCoord(point.x), 1498 y = this._getCoord(point.y), 1499 grid = this._grid, 1500 row = grid[y] = grid[y] || {}, 1501 cell = row[x] = row[x] || [], 1502 i, len; 1503 1504 delete this._objectPoint[L.Util.stamp(obj)]; 1505 1506 for (i = 0, len = cell.length; i < len; i++) { 1507 if (cell[i] === obj) { 1508 1509 cell.splice(i, 1); 1510 1511 if (len === 1) { 1512 delete row[x]; 1513 } 1514 1515 return true; 1516 } 1517 } 1518 1519 }, 1520 1521 eachObject: function (fn, context) { 1522 var i, j, k, len, row, cell, removed, 1523 grid = this._grid; 1524 1525 for (i in grid) { 1526 row = grid[i]; 1527 1528 for (j in row) { 1529 cell = row[j]; 1530 1531 for (k = 0, len = cell.length; k < len; k++) { 1532 removed = fn.call(context, cell[k]); 1533 if (removed) { 1534 k--; 1535 len--; 1536 } 1537 } 1538 } 1539 } 1540 }, 1541 1542 getNearObject: function (point) { 1543 var x = this._getCoord(point.x), 1544 y = this._getCoord(point.y), 1545 i, j, k, row, cell, len, obj, dist, 1546 objectPoint = this._objectPoint, 1547 closestDistSq = this._sqCellSize, 1548 closest = null; 1549 1550 for (i = y - 1; i <= y + 1; i++) { 1551 row = this._grid[i]; 1552 if (row) { 1553 1554 for (j = x - 1; j <= x + 1; j++) { 1555 cell = row[j]; 1556 if (cell) { 1557 1558 for (k = 0, len = cell.length; k < len; k++) { 1559 obj = cell[k]; 1560 dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point); 1561 if (dist < closestDistSq) { 1562 closestDistSq = dist; 1563 closest = obj; 1564 } 1565 } 1566 } 1567 } 1568 } 1569 } 1570 return closest; 1571 }, 1572 1573 _getCoord: function (x) { 1574 return Math.floor(x / this._cellSize); 1575 }, 1576 1577 _sqDist: function (p, p2) { 1578 var dx = p2.x - p.x, 1579 dy = p2.y - p.y; 1580 return dx * dx + dy * dy; 1581 } 1582}; 1583 1584 1585/* Copyright (c) 2012 the authors listed at the following URL, and/or 1586the authors of referenced articles or incorporated external code: 1587http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256 1588 1589Permission is hereby granted, free of charge, to any person obtaining 1590a copy of this software and associated documentation files (the 1591"Software"), to deal in the Software without restriction, including 1592without limitation the rights to use, copy, modify, merge, publish, 1593distribute, sublicense, and/or sell copies of the Software, and to 1594permit persons to whom the Software is furnished to do so, subject to 1595the following conditions: 1596 1597The above copyright notice and this permission notice shall be 1598included in all copies or substantial portions of the Software. 1599 1600THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 1601EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 1602MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 1603IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 1604CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 1605TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 1606SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1607 1608Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434 1609*/ 1610 1611(function () { 1612 L.QuickHull = { 1613 1614 /* 1615 * @param {Object} cpt a point to be measured from the baseline 1616 * @param {Array} bl the baseline, as represented by a two-element 1617 * array of latlng objects. 1618 * @returns {Number} an approximate distance measure 1619 */ 1620 getDistant: function (cpt, bl) { 1621 var vY = bl[1].lat - bl[0].lat, 1622 vX = bl[0].lng - bl[1].lng; 1623 return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng)); 1624 }, 1625 1626 /* 1627 * @param {Array} baseLine a two-element array of latlng objects 1628 * representing the baseline to project from 1629 * @param {Array} latLngs an array of latlng objects 1630 * @returns {Object} the maximum point and all new points to stay 1631 * in consideration for the hull. 1632 */ 1633 findMostDistantPointFromBaseLine: function (baseLine, latLngs) { 1634 var maxD = 0, 1635 maxPt = null, 1636 newPoints = [], 1637 i, pt, d; 1638 1639 for (i = latLngs.length - 1; i >= 0; i--) { 1640 pt = latLngs[i]; 1641 d = this.getDistant(pt, baseLine); 1642 1643 if (d > 0) { 1644 newPoints.push(pt); 1645 } else { 1646 continue; 1647 } 1648 1649 if (d > maxD) { 1650 maxD = d; 1651 maxPt = pt; 1652 } 1653 } 1654 1655 return { maxPoint: maxPt, newPoints: newPoints }; 1656 }, 1657 1658 1659 /* 1660 * Given a baseline, compute the convex hull of latLngs as an array 1661 * of latLngs. 1662 * 1663 * @param {Array} latLngs 1664 * @returns {Array} 1665 */ 1666 buildConvexHull: function (baseLine, latLngs) { 1667 var convexHullBaseLines = [], 1668 t = this.findMostDistantPointFromBaseLine(baseLine, latLngs); 1669 1670 if (t.maxPoint) { // if there is still a point "outside" the base line 1671 convexHullBaseLines = 1672 convexHullBaseLines.concat( 1673 this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints) 1674 ); 1675 convexHullBaseLines = 1676 convexHullBaseLines.concat( 1677 this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints) 1678 ); 1679 return convexHullBaseLines; 1680 } else { // if there is no more point "outside" the base line, the current base line is part of the convex hull 1681 return [baseLine[0]]; 1682 } 1683 }, 1684 1685 /* 1686 * Given an array of latlngs, compute a convex hull as an array 1687 * of latlngs 1688 * 1689 * @param {Array} latLngs 1690 * @returns {Array} 1691 */ 1692 getConvexHull: function (latLngs) { 1693 // find first baseline 1694 var maxLat = false, minLat = false, 1695 maxPt = null, minPt = null, 1696 i; 1697 1698 for (i = latLngs.length - 1; i >= 0; i--) { 1699 var pt = latLngs[i]; 1700 if (maxLat === false || pt.lat > maxLat) { 1701 maxPt = pt; 1702 maxLat = pt.lat; 1703 } 1704 if (minLat === false || pt.lat < minLat) { 1705 minPt = pt; 1706 minLat = pt.lat; 1707 } 1708 } 1709 var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs), 1710 this.buildConvexHull([maxPt, minPt], latLngs)); 1711 return ch; 1712 } 1713 }; 1714}()); 1715 1716L.MarkerCluster.include({ 1717 getConvexHull: function () { 1718 var childMarkers = this.getAllChildMarkers(), 1719 points = [], 1720 p, i; 1721 1722 for (i = childMarkers.length - 1; i >= 0; i--) { 1723 p = childMarkers[i].getLatLng(); 1724 points.push(p); 1725 } 1726 1727 return L.QuickHull.getConvexHull(points); 1728 } 1729}); 1730 1731 1732//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet 1733//Huge thanks to jawj for implementing it first to make my job easy :-) 1734 1735L.MarkerCluster.include({ 1736 1737 _2PI: Math.PI * 2, 1738 _circleFootSeparation: 25, //related to circumference of circle 1739 _circleStartAngle: Math.PI / 6, 1740 1741 _spiralFootSeparation: 28, //related to size of spiral (experiment!) 1742 _spiralLengthStart: 11, 1743 _spiralLengthFactor: 5, 1744 1745 _circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards. 1746 // 0 -> always spiral; Infinity -> always circle 1747 1748 spiderfy: function () { 1749 if (this._group._spiderfied === this || this._group._inZoomAnimation) { 1750 return; 1751 } 1752 1753 var childMarkers = this.getAllChildMarkers(), 1754 group = this._group, 1755 map = group._map, 1756 center = map.latLngToLayerPoint(this._latlng), 1757 positions; 1758 1759 this._group._unspiderfy(); 1760 this._group._spiderfied = this; 1761 1762 //TODO Maybe: childMarkers order by distance to center 1763 1764 if (childMarkers.length >= this._circleSpiralSwitchover) { 1765 positions = this._generatePointsSpiral(childMarkers.length, center); 1766 } else { 1767 center.y += 10; //Otherwise circles look wrong 1768 positions = this._generatePointsCircle(childMarkers.length, center); 1769 } 1770 1771 this._animationSpiderfy(childMarkers, positions); 1772 }, 1773 1774 unspiderfy: function (zoomDetails) { 1775 /// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param> 1776 if (this._group._inZoomAnimation) { 1777 return; 1778 } 1779 this._animationUnspiderfy(zoomDetails); 1780 1781 this._group._spiderfied = null; 1782 }, 1783 1784 _generatePointsCircle: function (count, centerPt) { 1785 var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
1786 legLength = circumference / this._2PI, //radius from circumference 1787 angleStep = this._2PI / count, 1788 res = [], 1789 i, angle; 1790 1791 res.length = count; 1792 1793 for (i = count - 1; i >= 0; i--) { 1794 angle = this._circleStartAngle + i * angleStep; 1795 res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round(); 1796 } 1797 1798 return res; 1799 }, 1800 1801 _generatePointsSpiral: function (count, centerPt) { 1802 var legLength = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthStart, 1803 separation = this._group.options.spiderfyDistanceMultiplier * this._spiralFootSeparation, 1804 lengthFactor = this._group.options.spiderfyDistanceMultiplier * this._spiralLengthFactor, 1805 angle = 0, 1806 res = [], 1807 i; 1808 1809 res.length = count; 1810 1811 for (i = count - 1; i >= 0; i--) { 1812 angle += separation / legLength + i * 0.0005; 1813 res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round(); 1814 legLength += this._2PI * lengthFactor / angle; 1815 } 1816 return res; 1817 }, 1818 1819 _noanimationUnspiderfy: function () { 1820 var group = this._group, 1821 map = group._map, 1822 fg = group._featureGroup, 1823 childMarkers = this.getAllChildMarkers(), 1824 m, i; 1825 1826 this.setOpacity(1); 1827 for (i = childMarkers.length - 1; i >= 0; i--) { 1828 m = childMarkers[i]; 1829 1830 fg.removeLayer(m); 1831 1832 if (m._preSpiderfyLatlng) { 1833 m.setLatLng(m._preSpiderfyLatlng); 1834 delete m._preSpiderfyLatlng; 1835 } 1836 if (m.setZIndexOffset) { 1837 m.setZIndexOffset(0); 1838 } 1839 1840 if (m._spiderLeg) { 1841 map.removeLayer(m._spiderLeg); 1842 delete m._spiderLeg; 1843 } 1844 } 1845 1846 group._spiderfied = null; 1847 } 1848}); 1849 1850L.MarkerCluster.include(!L.DomUtil.TRANSITION ? { 1851 //Non Animated versions of everything 1852 _animationSpiderfy: function (childMarkers, positions) { 1853 var group = this._group, 1854 map = group._map, 1855 fg = group._featureGroup, 1856 i, m, leg, newPos; 1857 1858 for (i = childMarkers.length - 1; i >= 0; i--) { 1859 newPos = map.layerPointToLatLng(positions[i]); 1860 m = childMarkers[i]; 1861 1862 m._preSpiderfyLatlng = m._latlng; 1863 m.setLatLng(newPos); 1864 if (m.setZIndexOffset) { 1865 m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING 1866 } 1867 1868 fg.addLayer(m); 1869 1870 1871 leg = new L.Polyline([this._latlng, newPos], { weight: 1.5, color: '#222' }); 1872 map.addLayer(leg); 1873 m._spiderLeg = leg; 1874 } 1875 this.setOpacity(0.3); 1876 group.fire('spiderfied'); 1877 }, 1878 1879 _animationUnspiderfy: function () { 1880 this._noanimationUnspiderfy(); 1881 } 1882} : { 1883 //Animated versions here 1884 SVG_ANIMATION: (function () { 1885 return document.createElementNS('http://www.w3.org/2000/svg', 'animate').toString().indexOf('SVGAnimate') > -1; 1886 }()), 1887 1888 _animationSpiderfy: function (childMarkers, positions) { 1889 var me = this, 1890 group = this._group, 1891 map = group._map, 1892 fg = group._featureGroup, 1893 thisLayerPos = map.latLngToLayerPoint(this._latlng), 1894 i, m, leg, newPos; 1895 1896 //Add markers to map hidden at our center point 1897 for (i = childMarkers.length - 1; i >= 0; i--) { 1898 m = childMarkers[i]; 1899 1900 //If it is a marker, add it now and we'll animate it out 1901 if (m.setOpacity) { 1902 m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING 1903 m.setOpacity(0); 1904 1905 fg.addLayer(m); 1906 1907 m._setPos(thisLayerPos); 1908 } else { 1909 //Vectors just get immediately added 1910 fg.addLayer(m); 1911 } 1912 } 1913 1914 group._forceLayout(); 1915 group._animationStart(); 1916 1917 var initialLegOpacity = L.Path.SVG ? 0 : 0.3, 1918 xmlns = L.Path.SVG_NS; 1919 1920 1921 for (i = childMarkers.length - 1; i >= 0; i--) { 1922 newPos = map.layerPointToLatLng(positions[i]); 1923 m = childMarkers[i]; 1924 1925 //Move marker to new position 1926 m._preSpiderfyLatlng = m._latlng; 1927 m.setLatLng(newPos); 1928 1929 if (m.setOpacity) { 1930 m.setOpacity(1); 1931 } 1932 1933 1934 //Add Legs. 1935 leg = new L.Polyline([me._latlng, newPos], { weight: 1.5, color: '#222', opacity: initialLegOpacity }); 1936 map.addLayer(leg); 1937 m._spiderLeg = leg; 1938 1939 //Following animations don't work for canvas 1940 if (!L.Path.SVG || !this.SVG_ANIMATION) { 1941 continue; 1942 } 1943 1944 //How this works: 1945 //http://stackoverflow.com/questions/5924238/how-do-you-animate-an-svg-path-in-ios 1946 //http://dev.opera.com/articles/view/advanced-svg-animation-techniques/ 1947 1948 //Animate length 1949 var length = leg._path.getTotalLength(); 1950 leg._path.setAttribute("stroke-dasharray", length + "," + length); 1951 1952 var anim = document.createElementNS(xmlns, "animate"); 1953 anim.setAttribute("attributeName", "stroke-dashoffset"); 1954 anim.setAttribute("begin", "indefinite"); 1955 anim.setAttribute("from", length); 1956 anim.setAttribute("to", 0); 1957 anim.setAttribute("dur", 0.25); 1958 leg._path.appendChild(anim); 1959 anim.beginElement(); 1960 1961 //Animate opacity 1962 anim = document.createElementNS(xmlns, "animate"); 1963 anim.setAttribute("attributeName", "stroke-opacity"); 1964 anim.setAttribute("attributeName", "stroke-opacity"); 1965 anim.setAttribute("begin", "indefinite"); 1966 anim.setAttribute("from", 0); 1967 anim.setAttribute("to", 0.5); 1968 anim.setAttribute("dur", 0.25); 1969 leg._path.appendChild(anim); 1970 anim.beginElement(); 1971 } 1972 me.setOpacity(0.3); 1973 1974 //Set the opacity of the spiderLegs back to their correct value 1975 // The animations above override this until they complete. 1976 // If the initial opacity of the spiderlegs isn't 0 then they appear before the animation starts. 1977 if (L.Path.SVG) { 1978 this._group._forceLayout(); 1979 1980 for (i = childMarkers.length - 1; i >= 0; i--) { 1981 m = childMarkers[i]._spiderLeg; 1982 1983 m.options.opacity = 0.5; 1984 m._path.setAttribute('stroke-opacity', 0.5); 1985 } 1986 } 1987 1988 setTimeout(function () { 1989 group._animationEnd(); 1990 group.fire('spiderfied'); 1991 }, 200); 1992 }, 1993 1994 _animationUnspiderfy: function (zoomDetails) { 1995 var group = this._group, 1996 map = group._map, 1997 fg = group._featureGroup, 1998 thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng), 1999 childMarkers = this.getAllChildMarkers(), 2000 svg = L.Path.SVG && this.SVG_ANIMATION, 2001 m, i, a; 2002 2003 group._animationStart(); 2004 2005 //Make us visible and bring the child markers back in 2006 this.setOpacity(1); 2007 for (i = childMarkers.length - 1; i >= 0; i--) { 2008 m = childMarkers[i];
2009 2010 //Marker was added to us after we were spidified 2011 if (!m._preSpiderfyLatlng) { 2012 continue; 2013 } 2014 2015 //Fix up the location to the real one 2016 m.setLatLng(m._preSpiderfyLatlng); 2017 delete m._preSpiderfyLatlng; 2018 //Hack override the location to be our center 2019 if (m.setOpacity) { 2020 m._setPos(thisLayerPos); 2021 m.setOpacity(0); 2022 } else { 2023 fg.removeLayer(m); 2024 } 2025 2026 //Animate the spider legs back in 2027 if (svg) { 2028 a = m._spiderLeg._path.childNodes[0]; 2029 a.setAttribute('to', a.getAttribute('from')); 2030 a.setAttribute('from', 0); 2031 a.beginElement(); 2032 2033 a = m._spiderLeg._path.childNodes[1]; 2034 a.setAttribute('from', 0.5); 2035 a.setAttribute('to', 0); 2036 a.setAttribute('stroke-opacity', 0); 2037 a.beginElement(); 2038 2039 m._spiderLeg._path.setAttribute('stroke-opacity', 0); 2040 } 2041 } 2042 2043 setTimeout(function () { 2044 //If we have only <= one child left then that marker will be shown on the map so don't remove it! 2045 var stillThereChildCount = 0; 2046 for (i = childMarkers.length - 1; i >= 0; i--) { 2047 m = childMarkers[i]; 2048 if (m._spiderLeg) { 2049 stillThereChildCount++; 2050 } 2051 } 2052 2053 2054 for (i = childMarkers.length - 1; i >= 0; i--) { 2055 m = childMarkers[i]; 2056 2057 if (!m._spiderLeg) { //Has already been unspiderfied 2058 continue; 2059 } 2060 2061 2062 if (m.setOpacity) { 2063 m.setOpacity(1); 2064 m.setZIndexOffset(0); 2065 } 2066 2067 if (stillThereChildCount > 1) { 2068 fg.removeLayer(m); 2069 } 2070 2071 map.removeLayer(m._spiderLeg); 2072 delete m._spiderLeg; 2073 } 2074 group._animationEnd(); 2075 }, 200); 2076 } 2077}); 2078 2079 2080L.MarkerClusterGroup.include({ 2081 //The MarkerCluster currently spiderfied (if any) 2082 _spiderfied: null, 2083 2084 _spiderfierOnAdd: function () { 2085 this._map.on('click', this._unspiderfyWrapper, this); 2086 2087 if (this._map.options.zoomAnimation) { 2088 this._map.on('zoomstart', this._unspiderfyZoomStart, this); 2089 } 2090 //Browsers without zoomAnimation or a big zoom don't fire zoomstart 2091 this._map.on('zoomend', this._noanimationUnspiderfy, this); 2092 2093 if (L.Path.SVG && !L.Browser.touch) { 2094 this._map._initPathRoot(); 2095 //Needs to happen in the pageload, not after, or animations don't work in webkit 2096 // http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements 2097 //Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable 2098 } 2099 }, 2100 2101 _spiderfierOnRemove: function () { 2102 this._map.off('click', this._unspiderfyWrapper, this); 2103 this._map.off('zoomstart', this._unspiderfyZoomStart, this); 2104 this._map.off('zoomanim', this._unspiderfyZoomAnim, this); 2105 2106 this._unspiderfy(); //Ensure that markers are back where they should be 2107 }, 2108 2109 2110 //On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated) 2111 //This means we can define the animation they do rather than Markers doing an animation to their actual location 2112 _unspiderfyZoomStart: function () { 2113 if (!this._map) { //May have been removed from the map by a zoomEnd handler 2114 return; 2115 } 2116 2117 this._map.on('zoomanim', this._unspiderfyZoomAnim, this); 2118 }, 2119 _unspiderfyZoomAnim: function (zoomDetails) { 2120 //Wait until the first zoomanim after the user has finished touch-zooming before running the animation 2121 if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) { 2122 return; 2123 } 2124 2125 this._map.off('zoomanim', this._unspiderfyZoomAnim, this); 2126 this._unspiderfy(zoomDetails); 2127 }, 2128 2129 2130 _unspiderfyWrapper: function () { 2131 /// <summary>_unspiderfy but passes no arguments</summary> 2132 this._unspiderfy(); 2133 }, 2134 2135 _unspiderfy: function (zoomDetails) { 2136 if (this._spiderfied) { 2137 this._spiderfied.unspiderfy(zoomDetails); 2138 } 2139 }, 2140 2141 _noanimationUnspiderfy: function () { 2142 if (this._spiderfied) { 2143 this._spiderfied._noanimationUnspiderfy(); 2144 } 2145 }, 2146 2147 //If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc 2148 _unspiderfyLayer: function (layer) { 2149 if (layer._spiderLeg) { 2150 this._featureGroup.removeLayer(layer); 2151 2152 layer.setOpacity(1); 2153 //Position will be fixed up immediately in _animationUnspiderfy 2154 layer.setZIndexOffset(0); 2155 2156 this._map.removeLayer(layer._spiderLeg); 2157 delete layer._spiderLeg; 2158 } 2159 } 2160}); 2161 2162 2163}(window, document));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.