fix
[wolnelektury.git] / src / wolnelektury / static / contrib / leaflet.markercluster-1.4.1 / leaflet.markercluster-src.js
1 /*
2  * Leaflet.markercluster 1.4.1+master.37ab9a2,
3  * Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps.
4  * https://github.com/Leaflet/Leaflet.markercluster
5  * (c) 2012-2017, Dave Leaver, smartrak
6  */
7 (function (global, factory) {
8         typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
9         typeof define === 'function' && define.amd ? define(['exports'], factory) :
10         (factory((global.Leaflet = global.Leaflet || {}, global.Leaflet.markercluster = global.Leaflet.markercluster || {})));
11 }(this, (function (exports) { 'use strict';
12
13 /*
14  * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
15  */
16
17 var MarkerClusterGroup = L.MarkerClusterGroup = L.FeatureGroup.extend({
18
19         options: {
20                 maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center
21                 iconCreateFunction: null,
22                 clusterPane: L.Marker.prototype.options.pane,
23
24                 spiderfyOnMaxZoom: true,
25                 showCoverageOnHover: true,
26                 zoomToBoundsOnClick: true,
27                 singleMarkerMode: false,
28
29                 disableClusteringAtZoom: null,
30
31                 // Setting this to false prevents the removal of any clusters outside of the viewpoint, which
32                 // is the default behaviour for performance reasons.
33                 removeOutsideVisibleBounds: true,
34
35                 // Set to false to disable all animations (zoom and spiderfy).
36                 // If false, option animateAddingMarkers below has no effect.
37                 // If L.DomUtil.TRANSITION is falsy, this option has no effect.
38                 animate: true,
39
40                 //Whether to animate adding markers after adding the MarkerClusterGroup to the map
41                 // If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
42                 animateAddingMarkers: false,
43
44                 //Increase to increase the distance away that spiderfied markers appear from the center
45                 spiderfyDistanceMultiplier: 1,
46
47                 // Make it possible to specify a polyline options on a spider leg
48                 spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 },
49
50                 // 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
51                 chunkedLoading: false,
52                 chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
53                 chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
54                 chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
55
56                 //Options to pass to the L.Polygon constructor
57                 polygonOptions: {}
58         },
59
60         initialize: function (options) {
61                 L.Util.setOptions(this, options);
62                 if (!this.options.iconCreateFunction) {
63                         this.options.iconCreateFunction = this._defaultIconCreateFunction;
64                 }
65
66                 this._featureGroup = L.featureGroup();
67                 this._featureGroup.addEventParent(this);
68
69                 this._nonPointGroup = L.featureGroup();
70                 this._nonPointGroup.addEventParent(this);
71
72                 this._inZoomAnimation = 0;
73                 this._needsClustering = [];
74                 this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of
75                 //The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
76                 this._currentShownBounds = null;
77
78                 this._queue = [];
79
80                 this._childMarkerEventHandlers = {
81                         'dragstart': this._childMarkerDragStart,
82                         'move': this._childMarkerMoved,
83                         'dragend': this._childMarkerDragEnd,
84                 };
85
86                 // Hook the appropriate animation methods.
87                 var animate = L.DomUtil.TRANSITION && this.options.animate;
88                 L.extend(this, animate ? this._withAnimation : this._noAnimation);
89                 // Remember which MarkerCluster class to instantiate (animated or not).
90                 this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated;
91         },
92
93         addLayer: function (layer) {
94
95                 if (layer instanceof L.LayerGroup) {
96                         return this.addLayers([layer]);
97                 }
98
99                 //Don't cluster non point data
100                 if (!layer.getLatLng) {
101                         this._nonPointGroup.addLayer(layer);
102                         this.fire('layeradd', { layer: layer });
103                         return this;
104                 }
105
106                 if (!this._map) {
107                         this._needsClustering.push(layer);
108                         this.fire('layeradd', { layer: layer });
109                         return this;
110                 }
111
112                 if (this.hasLayer(layer)) {
113                         return this;
114                 }
115
116
117                 //If we have already clustered we'll need to add this one to a cluster
118
119                 if (this._unspiderfy) {
120                         this._unspiderfy();
121                 }
122
123                 this._addLayer(layer, this._maxZoom);
124                 this.fire('layeradd', { layer: layer });
125
126                 // Refresh bounds and weighted positions.
127                 this._topClusterLevel._recalculateBounds();
128
129                 this._refreshClustersIcons();
130
131                 //Work out what is visible
132                 var visibleLayer = layer,
133                     currentZoom = this._zoom;
134                 if (layer.__parent) {
135                         while (visibleLayer.__parent._zoom >= currentZoom) {
136                                 visibleLayer = visibleLayer.__parent;
137                         }
138                 }
139
140                 if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
141                         if (this.options.animateAddingMarkers) {
142                                 this._animationAddLayer(layer, visibleLayer);
143                         } else {
144                                 this._animationAddLayerNonAnimated(layer, visibleLayer);
145                         }
146                 }
147                 return this;
148         },
149
150         removeLayer: function (layer) {
151
152                 if (layer instanceof L.LayerGroup) {
153                         return this.removeLayers([layer]);
154                 }
155
156                 //Non point layers
157                 if (!layer.getLatLng) {
158                         this._nonPointGroup.removeLayer(layer);
159                         this.fire('layerremove', { layer: layer });
160                         return this;
161                 }
162
163                 if (!this._map) {
164                         if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
165                                 this._needsRemoving.push({ layer: layer, latlng: layer._latlng });
166                         }
167                         this.fire('layerremove', { layer: layer });
168                         return this;
169                 }
170
171                 if (!layer.__parent) {
172                         return this;
173                 }
174
175                 if (this._unspiderfy) {
176                         this._unspiderfy();
177                         this._unspiderfyLayer(layer);
178                 }
179
180                 //Remove the marker from clusters
181                 this._removeLayer(layer, true);
182                 this.fire('layerremove', { layer: layer });
183
184                 // Refresh bounds and weighted positions.
185                 this._topClusterLevel._recalculateBounds();
186
187                 this._refreshClustersIcons();
188
189                 layer.off(this._childMarkerEventHandlers, this);
190
191                 if (this._featureGroup.hasLayer(layer)) {
192                         this._featureGroup.removeLayer(layer);
193                         if (layer.clusterShow) {
194                                 layer.clusterShow();
195                         }
196                 }
197
198                 return this;
199         },
200
201         //Takes an array of markers and adds them in bulk
202         addLayers: function (layersArray, skipLayerAddEvent) {
203                 if (!L.Util.isArray(layersArray)) {
204                         return this.addLayer(layersArray);
205                 }
206
207                 var fg = this._featureGroup,
208                     npg = this._nonPointGroup,
209                     chunked = this.options.chunkedLoading,
210                     chunkInterval = this.options.chunkInterval,
211                     chunkProgress = this.options.chunkProgress,
212                     l = layersArray.length,
213                     offset = 0,
214                     originalArray = true,
215                     m;
216
217                 if (this._map) {
218                         var started = (new Date()).getTime();
219                         var process = L.bind(function () {
220                                 var start = (new Date()).getTime();
221                                 for (; offset < l; offset++) {
222                                         if (chunked && offset % 200 === 0) {
223                                                 // every couple hundred markers, instrument the time elapsed since processing started:
224                                                 var elapsed = (new Date()).getTime() - start;
225                                                 if (elapsed > chunkInterval) {
226                                                         break; // been working too hard, time to take a break :-)
227                                                 }
228                                         }
229
230                                         m = layersArray[offset];
231
232                                         // Group of layers, append children to layersArray and skip.
233                                         // Side effects:
234                                         // - Total increases, so chunkProgress ratio jumps backward.
235                                         // - Groups are not included in this group, only their non-group child layers (hasLayer).
236                                         // Changing array length while looping does not affect performance in current browsers:
237                                         // http://jsperf.com/for-loop-changing-length/6
238                                         if (m instanceof L.LayerGroup) {
239                                                 if (originalArray) {
240                                                         layersArray = layersArray.slice();
241                                                         originalArray = false;
242                                                 }
243                                                 this._extractNonGroupLayers(m, layersArray);
244                                                 l = layersArray.length;
245                                                 continue;
246                                         }
247
248                                         //Not point data, can't be clustered
249                                         if (!m.getLatLng) {
250                                                 npg.addLayer(m);
251                                                 if (!skipLayerAddEvent) {
252                                                         this.fire('layeradd', { layer: m });
253                                                 }
254                                                 continue;
255                                         }
256
257                                         if (this.hasLayer(m)) {
258                                                 continue;
259                                         }
260
261                                         this._addLayer(m, this._maxZoom);
262                                         if (!skipLayerAddEvent) {
263                                                 this.fire('layeradd', { layer: m });
264                                         }
265
266                                         //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
267                                         if (m.__parent) {
268                                                 if (m.__parent.getChildCount() === 2) {
269                                                         var markers = m.__parent.getAllChildMarkers(),
270                                                             otherMarker = markers[0] === m ? markers[1] : markers[0];
271                                                         fg.removeLayer(otherMarker);
272                                                 }
273                                         }
274                                 }
275
276                                 if (chunkProgress) {
277                                         // report progress and time elapsed:
278                                         chunkProgress(offset, l, (new Date()).getTime() - started);
279                                 }
280
281                                 // Completed processing all markers.
282                                 if (offset === l) {
283
284                                         // Refresh bounds and weighted positions.
285                                         this._topClusterLevel._recalculateBounds();
286
287                                         this._refreshClustersIcons();
288
289                                         this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
290                                 } else {
291                                         setTimeout(process, this.options.chunkDelay);
292                                 }
293                         }, this);
294
295                         process();
296                 } else {
297                         var needsClustering = this._needsClustering;
298
299                         for (; offset < l; offset++) {
300                                 m = layersArray[offset];
301
302                                 // Group of layers, append children to layersArray and skip.
303                                 if (m instanceof L.LayerGroup) {
304                                         if (originalArray) {
305                                                 layersArray = layersArray.slice();
306                                                 originalArray = false;
307                                         }
308                                         this._extractNonGroupLayers(m, layersArray);
309                                         l = layersArray.length;
310                                         continue;
311                                 }
312
313                                 //Not point data, can't be clustered
314                                 if (!m.getLatLng) {
315                                         npg.addLayer(m);
316                                         continue;
317                                 }
318
319                                 if (this.hasLayer(m)) {
320                                         continue;
321                                 }
322
323                                 needsClustering.push(m);
324                         }
325                 }
326                 return this;
327         },
328
329         //Takes an array of markers and removes them in bulk
330         removeLayers: function (layersArray) {
331                 var i, m,
332                     l = layersArray.length,
333                     fg = this._featureGroup,
334                     npg = this._nonPointGroup,
335                     originalArray = true;
336
337                 if (!this._map) {
338                         for (i = 0; i < l; i++) {
339                                 m = layersArray[i];
340
341                                 // Group of layers, append children to layersArray and skip.
342                                 if (m instanceof L.LayerGroup) {
343                                         if (originalArray) {
344                                                 layersArray = layersArray.slice();
345                                                 originalArray = false;
346                                         }
347                                         this._extractNonGroupLayers(m, layersArray);
348                                         l = layersArray.length;
349                                         continue;
350                                 }
351
352                                 this._arraySplice(this._needsClustering, m);
353                                 npg.removeLayer(m);
354                                 if (this.hasLayer(m)) {
355                                         this._needsRemoving.push({ layer: m, latlng: m._latlng });
356                                 }
357                                 this.fire('layerremove', { layer: m });
358                         }
359                         return this;
360                 }
361
362                 if (this._unspiderfy) {
363                         this._unspiderfy();
364
365                         // Work on a copy of the array, so that next loop is not affected.
366                         var layersArray2 = layersArray.slice(),
367                             l2 = l;
368                         for (i = 0; i < l2; i++) {
369                                 m = layersArray2[i];
370
371                                 // Group of layers, append children to layersArray and skip.
372                                 if (m instanceof L.LayerGroup) {
373                                         this._extractNonGroupLayers(m, layersArray2);
374                                         l2 = layersArray2.length;
375                                         continue;
376                                 }
377
378                                 this._unspiderfyLayer(m);
379                         }
380                 }
381
382                 for (i = 0; i < l; i++) {
383                         m = layersArray[i];
384
385                         // Group of layers, append children to layersArray and skip.
386                         if (m instanceof L.LayerGroup) {
387                                 if (originalArray) {
388                                         layersArray = layersArray.slice();
389                                         originalArray = false;
390                                 }
391                                 this._extractNonGroupLayers(m, layersArray);
392                                 l = layersArray.length;
393                                 continue;
394                         }
395
396                         if (!m.__parent) {
397                                 npg.removeLayer(m);
398                                 this.fire('layerremove', { layer: m });
399                                 continue;
400                         }
401
402                         this._removeLayer(m, true, true);
403                         this.fire('layerremove', { layer: m });
404
405                         if (fg.hasLayer(m)) {
406                                 fg.removeLayer(m);
407                                 if (m.clusterShow) {
408                                         m.clusterShow();
409                                 }
410                         }
411                 }
412
413                 // Refresh bounds and weighted positions.
414                 this._topClusterLevel._recalculateBounds();
415
416                 this._refreshClustersIcons();
417
418                 //Fix up the clusters and markers on the map
419                 this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
420
421                 return this;
422         },
423
424         //Removes all layers from the MarkerClusterGroup
425         clearLayers: function () {
426                 //Need our own special implementation as the LayerGroup one doesn't work for us
427
428                 //If we aren't on the map (yet), blow away the markers we know of
429                 if (!this._map) {
430                         this._needsClustering = [];
431                         this._needsRemoving = [];
432                         delete this._gridClusters;
433                         delete this._gridUnclustered;
434                 }
435
436                 if (this._noanimationUnspiderfy) {
437                         this._noanimationUnspiderfy();
438                 }
439
440                 //Remove all the visible layers
441                 this._featureGroup.clearLayers();
442                 this._nonPointGroup.clearLayers();
443
444                 this.eachLayer(function (marker) {
445                         marker.off(this._childMarkerEventHandlers, this);
446                         delete marker.__parent;
447                 }, this);
448
449                 if (this._map) {
450                         //Reset _topClusterLevel and the DistanceGrids
451                         this._generateInitialClusters();
452                 }
453
454                 return this;
455         },
456
457         //Override FeatureGroup.getBounds as it doesn't work
458         getBounds: function () {
459                 var bounds = new L.LatLngBounds();
460
461                 if (this._topClusterLevel) {
462                         bounds.extend(this._topClusterLevel._bounds);
463                 }
464
465                 for (var i = this._needsClustering.length - 1; i >= 0; i--) {
466                         bounds.extend(this._needsClustering[i].getLatLng());
467                 }
468
469                 bounds.extend(this._nonPointGroup.getBounds());
470
471                 return bounds;
472         },
473
474         //Overrides LayerGroup.eachLayer
475         eachLayer: function (method, context) {
476                 var markers = this._needsClustering.slice(),
477                         needsRemoving = this._needsRemoving,
478                         thisNeedsRemoving, i, j;
479
480                 if (this._topClusterLevel) {
481                         this._topClusterLevel.getAllChildMarkers(markers);
482                 }
483
484                 for (i = markers.length - 1; i >= 0; i--) {
485                         thisNeedsRemoving = true;
486
487                         for (j = needsRemoving.length - 1; j >= 0; j--) {
488                                 if (needsRemoving[j].layer === markers[i]) {
489                                         thisNeedsRemoving = false;
490                                         break;
491                                 }
492                         }
493
494                         if (thisNeedsRemoving) {
495                                 method.call(context, markers[i]);
496                         }
497                 }
498
499                 this._nonPointGroup.eachLayer(method, context);
500         },
501
502         //Overrides LayerGroup.getLayers
503         getLayers: function () {
504                 var layers = [];
505                 this.eachLayer(function (l) {
506                         layers.push(l);
507                 });
508                 return layers;
509         },
510
511         //Overrides LayerGroup.getLayer, WARNING: Really bad performance
512         getLayer: function (id) {
513                 var result = null;
514
515                 id = parseInt(id, 10);
516
517                 this.eachLayer(function (l) {
518                         if (L.stamp(l) === id) {
519                                 result = l;
520                         }
521                 });
522
523                 return result;
524         },
525
526         //Returns true if the given layer is in this MarkerClusterGroup
527         hasLayer: function (layer) {
528                 if (!layer) {
529                         return false;
530                 }
531
532                 var i, anArray = this._needsClustering;
533
534                 for (i = anArray.length - 1; i >= 0; i--) {
535                         if (anArray[i] === layer) {
536                                 return true;
537                         }
538                 }
539
540                 anArray = this._needsRemoving;
541                 for (i = anArray.length - 1; i >= 0; i--) {
542                         if (anArray[i].layer === layer) {
543                                 return false;
544                         }
545                 }
546
547                 return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer);
548         },
549
550         //Zoom down to show the given layer (spiderfying if necessary) then calls the callback
551         zoomToShowLayer: function (layer, callback) {
552
553                 if (typeof callback !== 'function') {
554                         callback = function () {};
555                 }
556
557                 var showMarker = function () {
558                         if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
559                                 this._map.off('moveend', showMarker, this);
560                                 this.off('animationend', showMarker, this);
561
562                                 if (layer._icon) {
563                                         callback();
564                                 } else if (layer.__parent._icon) {
565                                         this.once('spiderfied', callback, this);
566                                         layer.__parent.spiderfy();
567                                 }
568                         }
569                 };
570
571                 if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
572                         //Layer is visible ond on screen, immediate return
573                         callback();
574                 } else if (layer.__parent._zoom < Math.round(this._map._zoom)) {
575                         //Layer should be visible at this zoom level. It must not be on screen so just pan over to it
576                         this._map.on('moveend', showMarker, this);
577                         this._map.panTo(layer.getLatLng());
578                 } else {
579                         this._map.on('moveend', showMarker, this);
580                         this.on('animationend', showMarker, this);
581                         layer.__parent.zoomToBounds();
582                 }
583         },
584
585         //Overrides FeatureGroup.onAdd
586         onAdd: function (map) {
587                 this._map = map;
588                 var i, l, layer;
589
590                 if (!isFinite(this._map.getMaxZoom())) {
591                         throw "Map has no maxZoom specified";
592                 }
593
594                 this._featureGroup.addTo(map);
595                 this._nonPointGroup.addTo(map);
596
597                 if (!this._gridClusters) {
598                         this._generateInitialClusters();
599                 }
600
601                 this._maxLat = map.options.crs.projection.MAX_LATITUDE;
602
603                 //Restore all the positions as they are in the MCG before removing them
604                 for (i = 0, l = this._needsRemoving.length; i < l; i++) {
605                         layer = this._needsRemoving[i];
606                         layer.newlatlng = layer.layer._latlng;
607                         layer.layer._latlng = layer.latlng;
608                 }
609                 //Remove them, then restore their new positions
610                 for (i = 0, l = this._needsRemoving.length; i < l; i++) {
611                         layer = this._needsRemoving[i];
612                         this._removeLayer(layer.layer, true);
613                         layer.layer._latlng = layer.newlatlng;
614                 }
615                 this._needsRemoving = [];
616
617                 //Remember the current zoom level and bounds
618                 this._zoom = Math.round(this._map._zoom);
619                 this._currentShownBounds = this._getExpandedVisibleBounds();
620
621                 this._map.on('zoomend', this._zoomEnd, this);
622                 this._map.on('moveend', this._moveEnd, this);
623
624                 if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
625                         this._spiderfierOnAdd();
626                 }
627
628                 this._bindEvents();
629
630                 //Actually add our markers to the map:
631                 l = this._needsClustering;
632                 this._needsClustering = [];
633                 this.addLayers(l, true);
634         },
635
636         //Overrides FeatureGroup.onRemove
637         onRemove: function (map) {
638                 map.off('zoomend', this._zoomEnd, this);
639                 map.off('moveend', this._moveEnd, this);
640
641                 this._unbindEvents();
642
643                 //In case we are in a cluster animation
644                 this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
645
646                 if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
647                         this._spiderfierOnRemove();
648                 }
649
650                 delete this._maxLat;
651
652                 //Clean up all the layers we added to the map
653                 this._hideCoverage();
654                 this._featureGroup.remove();
655                 this._nonPointGroup.remove();
656
657                 this._featureGroup.clearLayers();
658
659                 this._map = null;
660         },
661
662         getVisibleParent: function (marker) {
663                 var vMarker = marker;
664                 while (vMarker && !vMarker._icon) {
665                         vMarker = vMarker.__parent;
666                 }
667                 return vMarker || null;
668         },
669
670         //Remove the given object from the given array
671         _arraySplice: function (anArray, obj) {
672                 for (var i = anArray.length - 1; i >= 0; i--) {
673                         if (anArray[i] === obj) {
674                                 anArray.splice(i, 1);
675                                 return true;
676                         }
677                 }
678         },
679
680         /**
681          * Removes a marker from all _gridUnclustered zoom levels, starting at the supplied zoom.
682          * @param marker to be removed from _gridUnclustered.
683          * @param z integer bottom start zoom level (included)
684          * @private
685          */
686         _removeFromGridUnclustered: function (marker, z) {
687                 var map = this._map,
688                     gridUnclustered = this._gridUnclustered,
689                         minZoom = Math.floor(this._map.getMinZoom());
690
691                 for (; z >= minZoom; z--) {
692                         if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
693                                 break;
694                         }
695                 }
696         },
697
698         _childMarkerDragStart: function (e) {
699                 e.target.__dragStart = e.target._latlng;
700         },
701
702         _childMarkerMoved: function (e) {
703                 if (!this._ignoreMove && !e.target.__dragStart) {
704                         var isPopupOpen = e.target._popup && e.target._popup.isOpen();
705
706                         this._moveChild(e.target, e.oldLatLng, e.latlng);
707
708                         if (isPopupOpen) {
709                                 e.target.openPopup();
710                         }
711                 }
712         },
713
714         _moveChild: function (layer, from, to) {
715                 layer._latlng = from;
716                 this.removeLayer(layer);
717
718                 layer._latlng = to;
719                 this.addLayer(layer);
720         },
721
722         _childMarkerDragEnd: function (e) {
723                 var dragStart = e.target.__dragStart;
724                 delete e.target.__dragStart;
725                 if (dragStart) {
726                         this._moveChild(e.target, dragStart, e.target._latlng);
727                 }               
728         },
729
730
731         //Internal function for removing a marker from everything.
732         //dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
733         _removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
734                 var gridClusters = this._gridClusters,
735                         gridUnclustered = this._gridUnclustered,
736                         fg = this._featureGroup,
737                         map = this._map,
738                         minZoom = Math.floor(this._map.getMinZoom());
739
740                 //Remove the marker from distance clusters it might be in
741                 if (removeFromDistanceGrid) {
742                         this._removeFromGridUnclustered(marker, this._maxZoom);
743                 }
744
745                 //Work our way up the clusters removing them as we go if required
746                 var cluster = marker.__parent,
747                         markers = cluster._markers,
748                         otherMarker;
749
750                 //Remove the marker from the immediate parents marker list
751                 this._arraySplice(markers, marker);
752
753                 while (cluster) {
754                         cluster._childCount--;
755                         cluster._boundsNeedUpdate = true;
756
757                         if (cluster._zoom < minZoom) {
758                                 //Top level, do nothing
759                                 break;
760                         } else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required
761                                 //We need to push the other marker up to the parent
762                                 otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
763
764                                 //Update distance grid
765                                 gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
766                                 gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
767
768                                 //Move otherMarker up to parent
769                                 this._arraySplice(cluster.__parent._childClusters, cluster);
770                                 cluster.__parent._markers.push(otherMarker);
771                                 otherMarker.__parent = cluster.__parent;
772
773                                 if (cluster._icon) {
774                                         //Cluster is currently on the map, need to put the marker on the map instead
775                                         fg.removeLayer(cluster);
776                                         if (!dontUpdateMap) {
777                                                 fg.addLayer(otherMarker);
778                                         }
779                                 }
780                         } else {
781                                 cluster._iconNeedsUpdate = true;
782                         }
783
784                         cluster = cluster.__parent;
785                 }
786
787                 delete marker.__parent;
788         },
789
790         _isOrIsParent: function (el, oel) {
791                 while (oel) {
792                         if (el === oel) {
793                                 return true;
794                         }
795                         oel = oel.parentNode;
796                 }
797                 return false;
798         },
799
800         //Override L.Evented.fire
801         fire: function (type, data, propagate) {
802                 if (data && data.layer instanceof L.MarkerCluster) {
803                         //Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
804                         if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) {
805                                 return;
806                         }
807                         type = 'cluster' + type;
808                 }
809
810                 L.FeatureGroup.prototype.fire.call(this, type, data, propagate);
811         },
812
813         //Override L.Evented.listens
814         listens: function (type, propagate) {
815                 return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate);
816         },
817
818         //Default functionality
819         _defaultIconCreateFunction: function (cluster) {
820                 var childCount = cluster.getChildCount();
821
822                 var c = ' marker-cluster-';
823                 if (childCount < 10) {
824                         c += 'small';
825                 } else if (childCount < 100) {
826                         c += 'medium';
827                 } else {
828                         c += 'large';
829                 }
830
831                 return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
832         },
833
834         _bindEvents: function () {
835                 var map = this._map,
836                     spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
837                     showCoverageOnHover = this.options.showCoverageOnHover,
838                     zoomToBoundsOnClick = this.options.zoomToBoundsOnClick;
839
840                 //Zoom on cluster click or spiderfy if we are at the lowest level
841                 if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
842                         this.on('clusterclick', this._zoomOrSpiderfy, this);
843                 }
844
845                 //Show convex hull (boundary) polygon on mouse over
846                 if (showCoverageOnHover) {
847                         this.on('clustermouseover', this._showCoverage, this);
848                         this.on('clustermouseout', this._hideCoverage, this);
849                         map.on('zoomend', this._hideCoverage, this);
850                 }
851         },
852
853         _zoomOrSpiderfy: function (e) {
854                 var cluster = e.layer,
855                     bottomCluster = cluster;
856
857                 while (bottomCluster._childClusters.length === 1) {
858                         bottomCluster = bottomCluster._childClusters[0];
859                 }
860
861                 if (bottomCluster._zoom === this._maxZoom &&
862                         bottomCluster._childCount === cluster._childCount &&
863                         this.options.spiderfyOnMaxZoom) {
864
865                         // All child markers are contained in a single cluster from this._maxZoom to this cluster.
866                         cluster.spiderfy();
867                 } else if (this.options.zoomToBoundsOnClick) {
868                         cluster.zoomToBounds();
869                 }
870
871                 // Focus the map again for keyboard users.
872                 if (e.originalEvent && e.originalEvent.keyCode === 13) {
873                         this._map._container.focus();
874                 }
875         },
876
877         _showCoverage: function (e) {
878                 var map = this._map;
879                 if (this._inZoomAnimation) {
880                         return;
881                 }
882                 if (this._shownPolygon) {
883                         map.removeLayer(this._shownPolygon);
884                 }
885                 if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
886                         this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions);
887                         map.addLayer(this._shownPolygon);
888                 }
889         },
890
891         _hideCoverage: function () {
892                 if (this._shownPolygon) {
893                         this._map.removeLayer(this._shownPolygon);
894                         this._shownPolygon = null;
895                 }
896         },
897
898         _unbindEvents: function () {
899                 var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
900                         showCoverageOnHover = this.options.showCoverageOnHover,
901                         zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
902                         map = this._map;
903
904                 if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
905                         this.off('clusterclick', this._zoomOrSpiderfy, this);
906                 }
907                 if (showCoverageOnHover) {
908                         this.off('clustermouseover', this._showCoverage, this);
909                         this.off('clustermouseout', this._hideCoverage, this);
910                         map.off('zoomend', this._hideCoverage, this);
911                 }
912         },
913
914         _zoomEnd: function () {
915                 if (!this._map) { //May have been removed from the map by a zoomEnd handler
916                         return;
917                 }
918                 this._mergeSplitClusters();
919
920                 this._zoom = Math.round(this._map._zoom);
921                 this._currentShownBounds = this._getExpandedVisibleBounds();
922         },
923
924         _moveEnd: function () {
925                 if (this._inZoomAnimation) {
926                         return;
927                 }
928
929                 var newBounds = this._getExpandedVisibleBounds();
930
931                 this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, newBounds);
932                 this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds);
933
934                 this._currentShownBounds = newBounds;
935                 return;
936         },
937
938         _generateInitialClusters: function () {
939                 var maxZoom = Math.ceil(this._map.getMaxZoom()),
940                         minZoom = Math.floor(this._map.getMinZoom()),
941                         radius = this.options.maxClusterRadius,
942                         radiusFn = radius;
943
944                 //If we just set maxClusterRadius to a single number, we need to create
945                 //a simple function to return that number. Otherwise, we just have to
946                 //use the function we've passed in.
947                 if (typeof radius !== "function") {
948                         radiusFn = function () { return radius; };
949                 }
950
951                 if (this.options.disableClusteringAtZoom !== null) {
952                         maxZoom = this.options.disableClusteringAtZoom - 1;
953                 }
954                 this._maxZoom = maxZoom;
955                 this._gridClusters = {};
956                 this._gridUnclustered = {};
957
958                 //Set up DistanceGrids for each zoom
959                 for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
960                         this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
961                         this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
962                 }
963
964                 // Instantiate the appropriate L.MarkerCluster class (animated or not).
965                 this._topClusterLevel = new this._markerCluster(this, minZoom - 1);
966         },
967
968         //Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
969         _addLayer: function (layer, zoom) {
970                 var gridClusters = this._gridClusters,
971                     gridUnclustered = this._gridUnclustered,
972                         minZoom = Math.floor(this._map.getMinZoom()),
973                     markerPoint, z;
974
975                 if (this.options.singleMarkerMode) {
976                         this._overrideMarkerIcon(layer);
977                 }
978
979                 layer.on(this._childMarkerEventHandlers, this);
980
981                 //Find the lowest zoom level to slot this one in
982                 for (; zoom >= minZoom; zoom--) {
983                         markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
984
985                         //Try find a cluster close by
986                         var closest = gridClusters[zoom].getNearObject(markerPoint);
987                         if (closest) {
988                                 closest._addChild(layer);
989                                 layer.__parent = closest;
990                                 return;
991                         }
992
993                         //Try find a marker close by to form a new cluster with
994                         closest = gridUnclustered[zoom].getNearObject(markerPoint);
995                         if (closest) {
996                                 var parent = closest.__parent;
997                                 if (parent) {
998                                         this._removeLayer(closest, false);
999                                 }
1000
1001                                 //Create new cluster with these 2 in it
1002
1003                                 var newCluster = new this._markerCluster(this, zoom, closest, layer);
1004                                 gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
1005                                 closest.__parent = newCluster;
1006                                 layer.__parent = newCluster;
1007
1008                                 //First create any new intermediate parent clusters that don't exist
1009                                 var lastParent = newCluster;
1010                                 for (z = zoom - 1; z > parent._zoom; z--) {
1011                                         lastParent = new this._markerCluster(this, z, lastParent);
1012                                         gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
1013                                 }
1014                                 parent._addChild(lastParent);
1015
1016                                 //Remove closest from this zoom level and any above that it is in, replace with newCluster
1017                                 this._removeFromGridUnclustered(closest, zoom);
1018
1019                                 return;
1020                         }
1021
1022                         //Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
1023                         gridUnclustered[zoom].addObject(layer, markerPoint);
1024                 }
1025
1026                 //Didn't get in anything, add us to the top
1027                 this._topClusterLevel._addChild(layer);
1028                 layer.__parent = this._topClusterLevel;
1029                 return;
1030         },
1031
1032         /**
1033          * Refreshes the icon of all "dirty" visible clusters.
1034          * Non-visible "dirty" clusters will be updated when they are added to the map.
1035          * @private
1036          */
1037         _refreshClustersIcons: function () {
1038                 this._featureGroup.eachLayer(function (c) {
1039                         if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
1040                                 c._updateIcon();
1041                         }
1042                 });
1043         },
1044
1045         //Enqueue code to fire after the marker expand/contract has happened
1046         _enqueue: function (fn) {
1047                 this._queue.push(fn);
1048                 if (!this._queueTimeout) {
1049                         this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300);
1050                 }
1051         },
1052         _processQueue: function () {
1053                 for (var i = 0; i < this._queue.length; i++) {
1054                         this._queue[i].call(this);
1055                 }
1056                 this._queue.length = 0;
1057                 clearTimeout(this._queueTimeout);
1058                 this._queueTimeout = null;
1059         },
1060
1061         //Merge and split any existing clusters that are too big or small
1062         _mergeSplitClusters: function () {
1063                 var mapZoom = Math.round(this._map._zoom);
1064
1065                 //In case we are starting to split before the animation finished
1066                 this._processQueue();
1067
1068                 if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split
1069                         this._animationStart();
1070                         //Remove clusters now off screen
1071                         this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), this._zoom, this._getExpandedVisibleBounds());
1072
1073                         this._animationZoomIn(this._zoom, mapZoom);
1074
1075                 } else if (this._zoom > mapZoom) { //Zoom out, merge
1076                         this._animationStart();
1077
1078                         this._animationZoomOut(this._zoom, mapZoom);
1079                 } else {
1080                         this._moveEnd();
1081                 }
1082         },
1083
1084         //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)
1085         _getExpandedVisibleBounds: function () {
1086                 if (!this.options.removeOutsideVisibleBounds) {
1087                         return this._mapBoundsInfinite;
1088                 } else if (L.Browser.mobile) {
1089                         return this._checkBoundsMaxLat(this._map.getBounds());
1090                 }
1091
1092                 return this._checkBoundsMaxLat(this._map.getBounds().pad(1)); // Padding expands the bounds by its own dimensions but scaled with the given factor.
1093         },
1094
1095         /**
1096          * Expands the latitude to Infinity (or -Infinity) if the input bounds reach the map projection maximum defined latitude
1097          * (in the case of Web/Spherical Mercator, it is 85.0511287798 / see https://en.wikipedia.org/wiki/Web_Mercator#Formulas).
1098          * Otherwise, the removeOutsideVisibleBounds option will remove markers beyond that limit, whereas the same markers without
1099          * this option (or outside MCG) will have their position floored (ceiled) by the projection and rendered at that limit,
1100          * making the user think that MCG "eats" them and never displays them again.
1101          * @param bounds L.LatLngBounds
1102          * @returns {L.LatLngBounds}
1103          * @private
1104          */
1105         _checkBoundsMaxLat: function (bounds) {
1106                 var maxLat = this._maxLat;
1107
1108                 if (maxLat !== undefined) {
1109                         if (bounds.getNorth() >= maxLat) {
1110                                 bounds._northEast.lat = Infinity;
1111                         }
1112                         if (bounds.getSouth() <= -maxLat) {
1113                                 bounds._southWest.lat = -Infinity;
1114                         }
1115                 }
1116
1117                 return bounds;
1118         },
1119
1120         //Shared animation code
1121         _animationAddLayerNonAnimated: function (layer, newCluster) {
1122                 if (newCluster === layer) {
1123                         this._featureGroup.addLayer(layer);
1124                 } else if (newCluster._childCount === 2) {
1125                         newCluster._addToMap();
1126
1127                         var markers = newCluster.getAllChildMarkers();
1128                         this._featureGroup.removeLayer(markers[0]);
1129                         this._featureGroup.removeLayer(markers[1]);
1130                 } else {
1131                         newCluster._updateIcon();
1132                 }
1133         },
1134
1135         /**
1136          * Extracts individual (i.e. non-group) layers from a Layer Group.
1137          * @param group to extract layers from.
1138          * @param output {Array} in which to store the extracted layers.
1139          * @returns {*|Array}
1140          * @private
1141          */
1142         _extractNonGroupLayers: function (group, output) {
1143                 var layers = group.getLayers(),
1144                     i = 0,
1145                     layer;
1146
1147                 output = output || [];
1148
1149                 for (; i < layers.length; i++) {
1150                         layer = layers[i];
1151
1152                         if (layer instanceof L.LayerGroup) {
1153                                 this._extractNonGroupLayers(layer, output);
1154                                 continue;
1155                         }
1156
1157                         output.push(layer);
1158                 }
1159
1160                 return output;
1161         },
1162
1163         /**
1164          * Implements the singleMarkerMode option.
1165          * @param layer Marker to re-style using the Clusters iconCreateFunction.
1166          * @returns {L.Icon} The newly created icon.
1167          * @private
1168          */
1169         _overrideMarkerIcon: function (layer) {
1170                 var icon = layer.options.icon = this.options.iconCreateFunction({
1171                         getChildCount: function () {
1172                                 return 1;
1173                         },
1174                         getAllChildMarkers: function () {
1175                                 return [layer];
1176                         }
1177                 });
1178
1179                 return icon;
1180         }
1181 });
1182
1183 // Constant bounds used in case option "removeOutsideVisibleBounds" is set to false.
1184 L.MarkerClusterGroup.include({
1185         _mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity))
1186 });
1187
1188 L.MarkerClusterGroup.include({
1189         _noAnimation: {
1190                 //Non Animated versions of everything
1191                 _animationStart: function () {
1192                         //Do nothing...
1193                 },
1194                 _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
1195                         this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
1196                         this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
1197
1198                         //We didn't actually animate, but we use this event to mean "clustering animations have finished"
1199                         this.fire('animationend');
1200                 },
1201                 _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
1202                         this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel);
1203                         this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
1204
1205                         //We didn't actually animate, but we use this event to mean "clustering animations have finished"
1206                         this.fire('animationend');
1207                 },
1208                 _animationAddLayer: function (layer, newCluster) {
1209                         this._animationAddLayerNonAnimated(layer, newCluster);
1210                 }
1211         },
1212
1213         _withAnimation: {
1214                 //Animated versions here
1215                 _animationStart: function () {
1216                         this._map._mapPane.className += ' leaflet-cluster-anim';
1217                         this._inZoomAnimation++;
1218                 },
1219
1220                 _animationZoomIn: function (previousZoomLevel, newZoomLevel) {
1221                         var bounds = this._getExpandedVisibleBounds(),
1222                             fg = this._featureGroup,
1223                                 minZoom = Math.floor(this._map.getMinZoom()),
1224                             i;
1225
1226                         this._ignoreMove = true;
1227
1228                         //Add all children of current clusters to map and remove those clusters from map
1229                         this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
1230                                 var startPos = c._latlng,
1231                                     markers  = c._markers,
1232                                     m;
1233
1234                                 if (!bounds.contains(startPos)) {
1235                                         startPos = null;
1236                                 }
1237
1238                                 if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us
1239                                         fg.removeLayer(c);
1240                                         c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
1241                                 } else {
1242                                         //Fade out old cluster
1243                                         c.clusterHide();
1244                                         c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
1245                                 }
1246
1247                                 //Remove all markers that aren't visible any more
1248                                 //TODO: Do we actually need to do this on the higher levels too?
1249                                 for (i = markers.length - 1; i >= 0; i--) {
1250                                         m = markers[i];
1251                                         if (!bounds.contains(m._latlng)) {
1252                                                 fg.removeLayer(m);
1253                                         }
1254                                 }
1255
1256                         });
1257
1258                         this._forceLayout();
1259
1260                         //Update opacities
1261                         this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
1262                         //TODO Maybe? Update markers in _recursivelyBecomeVisible
1263                         fg.eachLayer(function (n) {
1264                                 if (!(n instanceof L.MarkerCluster) && n._icon) {
1265                                         n.clusterShow();
1266                                 }
1267                         });
1268
1269                         //update the positions of the just added clusters/markers
1270                         this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
1271                                 c._recursivelyRestoreChildPositions(newZoomLevel);
1272                         });
1273
1274                         this._ignoreMove = false;
1275
1276                         //Remove the old clusters and close the zoom animation
1277                         this._enqueue(function () {
1278                                 //update the positions of the just added clusters/markers
1279                                 this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
1280                                         fg.removeLayer(c);
1281                                         c.clusterShow();
1282                                 });
1283
1284                                 this._animationEnd();
1285                         });
1286                 },
1287
1288                 _animationZoomOut: function (previousZoomLevel, newZoomLevel) {
1289                         this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel);
1290
1291                         //Need to add markers for those that weren't on the map before but are now
1292                         this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
1293                         //Remove markers that were on the map before but won't be now
1294                         this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, Math.floor(this._map.getMinZoom()), previousZoomLevel, this._getExpandedVisibleBounds());
1295                 },
1296
1297                 _animationAddLayer: function (layer, newCluster) {
1298                         var me = this,
1299                             fg = this._featureGroup;
1300
1301                         fg.addLayer(layer);
1302                         if (newCluster !== layer) {
1303                                 if (newCluster._childCount > 2) { //Was already a cluster
1304
1305                                         newCluster._updateIcon();
1306                                         this._forceLayout();
1307                                         this._animationStart();
1308
1309                                         layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
1310                                         layer.clusterHide();
1311
1312                                         this._enqueue(function () {
1313                                                 fg.removeLayer(layer);
1314                                                 layer.clusterShow();
1315
1316                                                 me._animationEnd();
1317                                         });
1318
1319                                 } else { //Just became a cluster
1320                                         this._forceLayout();
1321
1322                                         me._animationStart();
1323                                         me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom);
1324                                 }
1325                         }
1326                 }
1327         },
1328
1329         // Private methods for animated versions.
1330         _animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
1331                 var bounds = this._getExpandedVisibleBounds(),
1332                         minZoom = Math.floor(this._map.getMinZoom());
1333
1334                 //Animate all of the markers in the clusters to move to their cluster center point
1335                 cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel);
1336
1337                 var me = this;
1338
1339                 //Update the opacity (If we immediately set it they won't animate)
1340                 this._forceLayout();
1341                 cluster._recursivelyBecomeVisible(bounds, newZoomLevel);
1342
1343                 //TODO: Maybe use the transition timing stuff to make this more reliable
1344                 //When the animations are done, tidy up
1345                 this._enqueue(function () {
1346
1347                         //This cluster stopped being a cluster before the timeout fired
1348                         if (cluster._childCount === 1) {
1349                                 var m = cluster._markers[0];
1350                                 //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
1351                                 this._ignoreMove = true;
1352                                 m.setLatLng(m.getLatLng());
1353                                 this._ignoreMove = false;
1354                                 if (m.clusterShow) {
1355                                         m.clusterShow();
1356                                 }
1357                         } else {
1358                                 cluster._recursively(bounds, newZoomLevel, minZoom, function (c) {
1359                                         c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1);
1360                                 });
1361                         }
1362                         me._animationEnd();
1363                 });
1364         },
1365
1366         _animationEnd: function () {
1367                 if (this._map) {
1368                         this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
1369                 }
1370                 this._inZoomAnimation--;
1371                 this.fire('animationend');
1372         },
1373
1374         //Force a browser layout of stuff in the map
1375         // Should apply the current opacity and location to all elements so we can update them again for an animation
1376         _forceLayout: function () {
1377                 //In my testing this works, infact offsetWidth of any element seems to work.
1378                 //Could loop all this._layers and do this for each _icon if it stops working
1379
1380                 L.Util.falseFn(document.body.offsetWidth);
1381         }
1382 });
1383
1384 L.markerClusterGroup = function (options) {
1385         return new L.MarkerClusterGroup(options);
1386 };
1387
1388 var MarkerCluster = L.MarkerCluster = L.Marker.extend({
1389         options: L.Icon.prototype.options,
1390
1391         initialize: function (group, zoom, a, b) {
1392
1393                 L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0),
1394             { icon: this, pane: group.options.clusterPane });
1395
1396                 this._group = group;
1397                 this._zoom = zoom;
1398
1399                 this._markers = [];
1400                 this._childClusters = [];
1401                 this._childCount = 0;
1402                 this._iconNeedsUpdate = true;
1403                 this._boundsNeedUpdate = true;
1404
1405                 this._bounds = new L.LatLngBounds();
1406
1407                 if (a) {
1408                         this._addChild(a);
1409                 }
1410                 if (b) {
1411                         this._addChild(b);
1412                 }
1413         },
1414
1415         //Recursively retrieve all child markers of this cluster
1416         getAllChildMarkers: function (storageArray, ignoreDraggedMarker) {
1417                 storageArray = storageArray || [];
1418
1419                 for (var i = this._childClusters.length - 1; i >= 0; i--) {
1420                         this._childClusters[i].getAllChildMarkers(storageArray);
1421                 }
1422
1423                 for (var j = this._markers.length - 1; j >= 0; j--) {
1424                         if (ignoreDraggedMarker && this._markers[j].__dragStart) {
1425                                 continue;
1426                         }
1427                         storageArray.push(this._markers[j]);
1428                 }
1429
1430                 return storageArray;
1431         },
1432
1433         //Returns the count of how many child markers we have
1434         getChildCount: function () {
1435                 return this._childCount;
1436         },
1437
1438         //Zoom to the minimum of showing all of the child markers, or the extents of this cluster
1439         zoomToBounds: function (fitBoundsOptions) {
1440                 var childClusters = this._childClusters.slice(),
1441                         map = this._group._map,
1442                         boundsZoom = map.getBoundsZoom(this._bounds),
1443                         zoom = this._zoom + 1,
1444                         mapZoom = map.getZoom(),
1445                         i;
1446
1447                 //calculate how far we need to zoom down to see all of the markers
1448                 while (childClusters.length > 0 && boundsZoom > zoom) {
1449                         zoom++;
1450                         var newClusters = [];
1451                         for (i = 0; i < childClusters.length; i++) {
1452                                 newClusters = newClusters.concat(childClusters[i]._childClusters);
1453                         }
1454                         childClusters = newClusters;
1455                 }
1456
1457                 if (boundsZoom > zoom) {
1458                         this._group._map.setView(this._latlng, zoom);
1459                 } else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead
1460                         this._group._map.setView(this._latlng, mapZoom + 1);
1461                 } else {
1462                         this._group._map.fitBounds(this._bounds, fitBoundsOptions);
1463                 }
1464         },
1465
1466         getBounds: function () {
1467                 var bounds = new L.LatLngBounds();
1468                 bounds.extend(this._bounds);
1469                 return bounds;
1470         },
1471
1472         _updateIcon: function () {
1473                 this._iconNeedsUpdate = true;
1474                 if (this._icon) {
1475                         this.setIcon(this);
1476                 }
1477         },
1478
1479         //Cludge for Icon, we pretend to be an icon for performance
1480         createIcon: function () {
1481                 if (this._iconNeedsUpdate) {
1482                         this._iconObj = this._group.options.iconCreateFunction(this);
1483                         this._iconNeedsUpdate = false;
1484                 }
1485                 return this._iconObj.createIcon();
1486         },
1487         createShadow: function () {
1488                 return this._iconObj.createShadow();
1489         },
1490
1491
1492         _addChild: function (new1, isNotificationFromChild) {
1493
1494                 this._iconNeedsUpdate = true;
1495
1496                 this._boundsNeedUpdate = true;
1497                 this._setClusterCenter(new1);
1498
1499                 if (new1 instanceof L.MarkerCluster) {
1500                         if (!isNotificationFromChild) {
1501                                 this._childClusters.push(new1);
1502                                 new1.__parent = this;
1503                         }
1504                         this._childCount += new1._childCount;
1505                 } else {
1506                         if (!isNotificationFromChild) {
1507                                 this._markers.push(new1);
1508                         }
1509                         this._childCount++;
1510                 }
1511
1512                 if (this.__parent) {
1513                         this.__parent._addChild(new1, true);
1514                 }
1515         },
1516
1517         /**
1518          * Makes sure the cluster center is set. If not, uses the child center if it is a cluster, or the marker position.
1519          * @param child L.MarkerCluster|L.Marker that will be used as cluster center if not defined yet.
1520          * @private
1521          */
1522         _setClusterCenter: function (child) {
1523                 if (!this._cLatLng) {
1524                         // when clustering, take position of the first point as the cluster center
1525                         this._cLatLng = child._cLatLng || child._latlng;
1526                 }
1527         },
1528
1529         /**
1530          * Assigns impossible bounding values so that the next extend entirely determines the new bounds.
1531          * This method avoids having to trash the previous L.LatLngBounds object and to create a new one, which is much slower for this class.
1532          * As long as the bounds are not extended, most other methods would probably fail, as they would with bounds initialized but not extended.
1533          * @private
1534          */
1535         _resetBounds: function () {
1536                 var bounds = this._bounds;
1537
1538                 if (bounds._southWest) {
1539                         bounds._southWest.lat = Infinity;
1540                         bounds._southWest.lng = Infinity;
1541                 }
1542                 if (bounds._northEast) {
1543                         bounds._northEast.lat = -Infinity;
1544                         bounds._northEast.lng = -Infinity;
1545                 }
1546         },
1547
1548         _recalculateBounds: function () {
1549                 var markers = this._markers,
1550                     childClusters = this._childClusters,
1551                     latSum = 0,
1552                     lngSum = 0,
1553                     totalCount = this._childCount,
1554                     i, child, childLatLng, childCount;
1555
1556                 // Case where all markers are removed from the map and we are left with just an empty _topClusterLevel.
1557                 if (totalCount === 0) {
1558                         return;
1559                 }
1560
1561                 // Reset rather than creating a new object, for performance.
1562                 this._resetBounds();
1563
1564                 // Child markers.
1565                 for (i = 0; i < markers.length; i++) {
1566                         childLatLng = markers[i]._latlng;
1567
1568                         this._bounds.extend(childLatLng);
1569
1570                         latSum += childLatLng.lat;
1571                         lngSum += childLatLng.lng;
1572                 }
1573
1574                 // Child clusters.
1575                 for (i = 0; i < childClusters.length; i++) {
1576                         child = childClusters[i];
1577
1578                         // Re-compute child bounds and weighted position first if necessary.
1579                         if (child._boundsNeedUpdate) {
1580                                 child._recalculateBounds();
1581                         }
1582
1583                         this._bounds.extend(child._bounds);
1584
1585                         childLatLng = child._wLatLng;
1586                         childCount = child._childCount;
1587
1588                         latSum += childLatLng.lat * childCount;
1589                         lngSum += childLatLng.lng * childCount;
1590                 }
1591
1592                 this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount);
1593
1594                 // Reset dirty flag.
1595                 this._boundsNeedUpdate = false;
1596         },
1597
1598         //Set our markers position as given and add it to the map
1599         _addToMap: function (startPos) {
1600                 if (startPos) {
1601                         this._backupLatlng = this._latlng;
1602                         this.setLatLng(startPos);
1603                 }
1604                 this._group._featureGroup.addLayer(this);
1605         },
1606
1607         _recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
1608                 this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1,
1609                         function (c) {
1610                                 var markers = c._markers,
1611                                         i, m;
1612                                 for (i = markers.length - 1; i >= 0; i--) {
1613                                         m = markers[i];
1614
1615                                         //Only do it if the icon is still on the map
1616                                         if (m._icon) {
1617                                                 m._setPos(center);
1618                                                 m.clusterHide();
1619                                         }
1620                                 }
1621                         },
1622                         function (c) {
1623                                 var childClusters = c._childClusters,
1624                                         j, cm;
1625                                 for (j = childClusters.length - 1; j >= 0; j--) {
1626                                         cm = childClusters[j];
1627                                         if (cm._icon) {
1628                                                 cm._setPos(center);
1629                                                 cm.clusterHide();
1630                                         }
1631                                 }
1632                         }
1633                 );
1634         },
1635
1636         _recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, mapMinZoom, previousZoomLevel, newZoomLevel) {
1637                 this._recursively(bounds, newZoomLevel, mapMinZoom,
1638                         function (c) {
1639                                 c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
1640
1641                                 //TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
1642                                 //As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
1643                                 if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
1644                                         c.clusterShow();
1645                                         c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
1646                                 } else {
1647                                         c.clusterHide();
1648                                 }
1649
1650                                 c._addToMap();
1651                         }
1652                 );
1653         },
1654
1655         _recursivelyBecomeVisible: function (bounds, zoomLevel) {
1656                 this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function (c) {
1657                         c.clusterShow();
1658                 });
1659         },
1660
1661         _recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
1662                 this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel,
1663                         function (c) {
1664                                 if (zoomLevel === c._zoom) {
1665                                         return;
1666                                 }
1667
1668                                 //Add our child markers at startPos (so they can be animated out)
1669                                 for (var i = c._markers.length - 1; i >= 0; i--) {
1670                                         var nm = c._markers[i];
1671
1672                                         if (!bounds.contains(nm._latlng)) {
1673                                                 continue;
1674                                         }
1675
1676                                         if (startPos) {
1677                                                 nm._backupLatlng = nm.getLatLng();
1678
1679                                                 nm.setLatLng(startPos);
1680                                                 if (nm.clusterHide) {
1681                                                         nm.clusterHide();
1682                                                 }
1683                                         }
1684
1685                                         c._group._featureGroup.addLayer(nm);
1686                                 }
1687                         },
1688                         function (c) {
1689                                 c._addToMap(startPos);
1690                         }
1691                 );
1692         },
1693
1694         _recursivelyRestoreChildPositions: function (zoomLevel) {
1695                 //Fix positions of child markers
1696                 for (var i = this._markers.length - 1; i >= 0; i--) {
1697                         var nm = this._markers[i];
1698                         if (nm._backupLatlng) {
1699                                 nm.setLatLng(nm._backupLatlng);
1700                                 delete nm._backupLatlng;
1701                         }
1702                 }
1703
1704                 if (zoomLevel - 1 === this._zoom) {
1705                         //Reposition child clusters
1706                         for (var j = this._childClusters.length - 1; j >= 0; j--) {
1707                                 this._childClusters[j]._restorePosition();
1708                         }
1709                 } else {
1710                         for (var k = this._childClusters.length - 1; k >= 0; k--) {
1711                                 this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel);
1712                         }
1713                 }
1714         },
1715
1716         _restorePosition: function () {
1717                 if (this._backupLatlng) {
1718                         this.setLatLng(this._backupLatlng);
1719                         delete this._backupLatlng;
1720                 }
1721         },
1722
1723         //exceptBounds: If set, don't remove any markers/clusters in it
1724         _recursivelyRemoveChildrenFromMap: function (previousBounds, mapMinZoom, zoomLevel, exceptBounds) {
1725                 var m, i;
1726                 this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1,
1727                         function (c) {
1728                                 //Remove markers at every level
1729                                 for (i = c._markers.length - 1; i >= 0; i--) {
1730                                         m = c._markers[i];
1731                                         if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
1732                                                 c._group._featureGroup.removeLayer(m);
1733                                                 if (m.clusterShow) {
1734                                                         m.clusterShow();
1735                                                 }
1736                                         }
1737                                 }
1738                         },
1739                         function (c) {
1740                                 //Remove child clusters at just the bottom level
1741                                 for (i = c._childClusters.length - 1; i >= 0; i--) {
1742                                         m = c._childClusters[i];
1743                                         if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
1744                                                 c._group._featureGroup.removeLayer(m);
1745                                                 if (m.clusterShow) {
1746                                                         m.clusterShow();
1747                                                 }
1748                                         }
1749                                 }
1750                         }
1751                 );
1752         },
1753
1754         //Run the given functions recursively to this and child clusters
1755         // boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
1756         // zoomLevelToStart: zoom level to start running functions (inclusive)
1757         // zoomLevelToStop: zoom level to stop running functions (inclusive)
1758         // runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
1759         // runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
1760         _recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
1761                 var childClusters = this._childClusters,
1762                     zoom = this._zoom,
1763                     i, c;
1764
1765                 if (zoomLevelToStart <= zoom) {
1766                         if (runAtEveryLevel) {
1767                                 runAtEveryLevel(this);
1768                         }
1769                         if (runAtBottomLevel && zoom === zoomLevelToStop) {
1770                                 runAtBottomLevel(this);
1771                         }
1772                 }
1773
1774                 if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) {
1775                         for (i = childClusters.length - 1; i >= 0; i--) {
1776                                 c = childClusters[i];
1777                                 if (c._boundsNeedUpdate) {
1778                                         c._recalculateBounds();
1779                                 }
1780                                 if (boundsToApplyTo.intersects(c._bounds)) {
1781                                         c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
1782                                 }
1783                         }
1784                 }
1785         },
1786
1787         //Returns true if we are the parent of only one cluster and that cluster is the same as us
1788         _isSingleParent: function () {
1789                 //Don't need to check this._markers as the rest won't work if there are any
1790                 return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount;
1791         }
1792 });
1793
1794 /*
1795 * Extends L.Marker to include two extra methods: clusterHide and clusterShow.
1796
1797 * They work as setOpacity(0) and setOpacity(1) respectively, but
1798 * don't overwrite the options.opacity
1799
1800 */
1801
1802 L.Marker.include({
1803         clusterHide: function () {
1804                 var backup = this.options.opacity;
1805                 this.setOpacity(0);
1806                 this.options.opacity = backup;
1807                 return this;
1808         },
1809         
1810         clusterShow: function () {
1811                 return this.setOpacity(this.options.opacity);
1812         }
1813 });
1814
1815 L.DistanceGrid = function (cellSize) {
1816         this._cellSize = cellSize;
1817         this._sqCellSize = cellSize * cellSize;
1818         this._grid = {};
1819         this._objectPoint = { };
1820 };
1821
1822 L.DistanceGrid.prototype = {
1823
1824         addObject: function (obj, point) {
1825                 var x = this._getCoord(point.x),
1826                     y = this._getCoord(point.y),
1827                     grid = this._grid,
1828                     row = grid[y] = grid[y] || {},
1829                     cell = row[x] = row[x] || [],
1830                     stamp = L.Util.stamp(obj);
1831
1832                 this._objectPoint[stamp] = point;
1833
1834                 cell.push(obj);
1835         },
1836
1837         updateObject: function (obj, point) {
1838                 this.removeObject(obj);
1839                 this.addObject(obj, point);
1840         },
1841
1842         //Returns true if the object was found
1843         removeObject: function (obj, point) {
1844                 var x = this._getCoord(point.x),
1845                     y = this._getCoord(point.y),
1846                     grid = this._grid,
1847                     row = grid[y] = grid[y] || {},
1848                     cell = row[x] = row[x] || [],
1849                     i, len;
1850
1851                 delete this._objectPoint[L.Util.stamp(obj)];
1852
1853                 for (i = 0, len = cell.length; i < len; i++) {
1854                         if (cell[i] === obj) {
1855
1856                                 cell.splice(i, 1);
1857
1858                                 if (len === 1) {
1859                                         delete row[x];
1860                                 }
1861
1862                                 return true;
1863                         }
1864                 }
1865
1866         },
1867
1868         eachObject: function (fn, context) {
1869                 var i, j, k, len, row, cell, removed,
1870                     grid = this._grid;
1871
1872                 for (i in grid) {
1873                         row = grid[i];
1874
1875                         for (j in row) {
1876                                 cell = row[j];
1877
1878                                 for (k = 0, len = cell.length; k < len; k++) {
1879                                         removed = fn.call(context, cell[k]);
1880                                         if (removed) {
1881                                                 k--;
1882                                                 len--;
1883                                         }
1884                                 }
1885                         }
1886                 }
1887         },
1888
1889         getNearObject: function (point) {
1890                 var x = this._getCoord(point.x),
1891                     y = this._getCoord(point.y),
1892                     i, j, k, row, cell, len, obj, dist,
1893                     objectPoint = this._objectPoint,
1894                     closestDistSq = this._sqCellSize,
1895                     closest = null;
1896
1897                 for (i = y - 1; i <= y + 1; i++) {
1898                         row = this._grid[i];
1899                         if (row) {
1900
1901                                 for (j = x - 1; j <= x + 1; j++) {
1902                                         cell = row[j];
1903                                         if (cell) {
1904
1905                                                 for (k = 0, len = cell.length; k < len; k++) {
1906                                                         obj = cell[k];
1907                                                         dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
1908                                                         if (dist < closestDistSq ||
1909                                                                 dist <= closestDistSq && closest === null) {
1910                                                                 closestDistSq = dist;
1911                                                                 closest = obj;
1912                                                         }
1913                                                 }
1914                                         }
1915                                 }
1916                         }
1917                 }
1918                 return closest;
1919         },
1920
1921         _getCoord: function (x) {
1922                 var coord = Math.floor(x / this._cellSize);
1923                 return isFinite(coord) ? coord : x;
1924         },
1925
1926         _sqDist: function (p, p2) {
1927                 var dx = p2.x - p.x,
1928                     dy = p2.y - p.y;
1929                 return dx * dx + dy * dy;
1930         }
1931 };
1932
1933 /* Copyright (c) 2012 the authors listed at the following URL, and/or
1934 the authors of referenced articles or incorporated external code:
1935 http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
1936
1937 Permission is hereby granted, free of charge, to any person obtaining
1938 a copy of this software and associated documentation files (the
1939 "Software"), to deal in the Software without restriction, including
1940 without limitation the rights to use, copy, modify, merge, publish,
1941 distribute, sublicense, and/or sell copies of the Software, and to
1942 permit persons to whom the Software is furnished to do so, subject to
1943 the following conditions:
1944
1945 The above copyright notice and this permission notice shall be
1946 included in all copies or substantial portions of the Software.
1947
1948 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1949 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1950 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
1951 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
1952 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
1953 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1954 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1955
1956 Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
1957 */
1958
1959 (function () {
1960         L.QuickHull = {
1961
1962                 /*
1963                  * @param {Object} cpt a point to be measured from the baseline
1964                  * @param {Array} bl the baseline, as represented by a two-element
1965                  *   array of latlng objects.
1966                  * @returns {Number} an approximate distance measure
1967                  */
1968                 getDistant: function (cpt, bl) {
1969                         var vY = bl[1].lat - bl[0].lat,
1970                                 vX = bl[0].lng - bl[1].lng;
1971                         return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng));
1972                 },
1973
1974                 /*
1975                  * @param {Array} baseLine a two-element array of latlng objects
1976                  *   representing the baseline to project from
1977                  * @param {Array} latLngs an array of latlng objects
1978                  * @returns {Object} the maximum point and all new points to stay
1979                  *   in consideration for the hull.
1980                  */
1981                 findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
1982                         var maxD = 0,
1983                                 maxPt = null,
1984                                 newPoints = [],
1985                                 i, pt, d;
1986
1987                         for (i = latLngs.length - 1; i >= 0; i--) {
1988                                 pt = latLngs[i];
1989                                 d = this.getDistant(pt, baseLine);
1990
1991                                 if (d > 0) {
1992                                         newPoints.push(pt);
1993                                 } else {
1994                                         continue;
1995                                 }
1996
1997                                 if (d > maxD) {
1998                                         maxD = d;
1999                                         maxPt = pt;
2000                                 }
2001                         }
2002
2003                         return { maxPoint: maxPt, newPoints: newPoints };
2004                 },
2005
2006
2007                 /*
2008                  * Given a baseline, compute the convex hull of latLngs as an array
2009                  * of latLngs.
2010                  *
2011                  * @param {Array} latLngs
2012                  * @returns {Array}
2013                  */
2014                 buildConvexHull: function (baseLine, latLngs) {
2015                         var convexHullBaseLines = [],
2016                                 t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
2017
2018                         if (t.maxPoint) { // if there is still a point "outside" the base line
2019                                 convexHullBaseLines =
2020                                         convexHullBaseLines.concat(
2021                                                 this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
2022                                         );
2023                                 convexHullBaseLines =
2024                                         convexHullBaseLines.concat(
2025                                                 this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
2026                                         );
2027                                 return convexHullBaseLines;
2028                         } else {  // if there is no more point "outside" the base line, the current base line is part of the convex hull
2029                                 return [baseLine[0]];
2030                         }
2031                 },
2032
2033                 /*
2034                  * Given an array of latlngs, compute a convex hull as an array
2035                  * of latlngs
2036                  *
2037                  * @param {Array} latLngs
2038                  * @returns {Array}
2039                  */
2040                 getConvexHull: function (latLngs) {
2041                         // find first baseline
2042                         var maxLat = false, minLat = false,
2043                                 maxLng = false, minLng = false,
2044                                 maxLatPt = null, minLatPt = null,
2045                                 maxLngPt = null, minLngPt = null,
2046                                 maxPt = null, minPt = null,
2047                                 i;
2048
2049                         for (i = latLngs.length - 1; i >= 0; i--) {
2050                                 var pt = latLngs[i];
2051                                 if (maxLat === false || pt.lat > maxLat) {
2052                                         maxLatPt = pt;
2053                                         maxLat = pt.lat;
2054                                 }
2055                                 if (minLat === false || pt.lat < minLat) {
2056                                         minLatPt = pt;
2057                                         minLat = pt.lat;
2058                                 }
2059                                 if (maxLng === false || pt.lng > maxLng) {
2060                                         maxLngPt = pt;
2061                                         maxLng = pt.lng;
2062                                 }
2063                                 if (minLng === false || pt.lng < minLng) {
2064                                         minLngPt = pt;
2065                                         minLng = pt.lng;
2066                                 }
2067                         }
2068                         
2069                         if (minLat !== maxLat) {
2070                                 minPt = minLatPt;
2071                                 maxPt = maxLatPt;
2072                         } else {
2073                                 minPt = minLngPt;
2074                                 maxPt = maxLngPt;
2075                         }
2076
2077                         var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
2078                                                                 this.buildConvexHull([maxPt, minPt], latLngs));
2079                         return ch;
2080                 }
2081         };
2082 }());
2083
2084 L.MarkerCluster.include({
2085         getConvexHull: function () {
2086                 var childMarkers = this.getAllChildMarkers(),
2087                         points = [],
2088                         p, i;
2089
2090                 for (i = childMarkers.length - 1; i >= 0; i--) {
2091                         p = childMarkers[i].getLatLng();
2092                         points.push(p);
2093                 }
2094
2095                 return L.QuickHull.getConvexHull(points);
2096         }
2097 });
2098
2099 //This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
2100 //Huge thanks to jawj for implementing it first to make my job easy :-)
2101
2102 L.MarkerCluster.include({
2103
2104         _2PI: Math.PI * 2,
2105         _circleFootSeparation: 25, //related to circumference of circle
2106         _circleStartAngle: 0,
2107
2108         _spiralFootSeparation:  28, //related to size of spiral (experiment!)
2109         _spiralLengthStart: 11,
2110         _spiralLengthFactor: 5,
2111
2112         _circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
2113                                                                 // 0 -> always spiral; Infinity -> always circle
2114
2115         spiderfy: function () {
2116                 if (this._group._spiderfied === this || this._group._inZoomAnimation) {
2117                         return;
2118                 }
2119
2120                 var childMarkers = this.getAllChildMarkers(null, true),
2121                         group = this._group,
2122                         map = group._map,
2123                         center = map.latLngToLayerPoint(this._latlng),
2124                         positions;
2125
2126                 this._group._unspiderfy();
2127                 this._group._spiderfied = this;
2128
2129                 //TODO Maybe: childMarkers order by distance to center
2130
2131                 if (childMarkers.length >= this._circleSpiralSwitchover) {
2132                         positions = this._generatePointsSpiral(childMarkers.length, center);
2133                 } else {
2134                         center.y += 10; // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons.
2135                         positions = this._generatePointsCircle(childMarkers.length, center);
2136                 }
2137
2138                 this._animationSpiderfy(childMarkers, positions);
2139         },
2140
2141         unspiderfy: function (zoomDetails) {
2142                 /// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
2143                 if (this._group._inZoomAnimation) {
2144                         return;
2145                 }
2146                 this._animationUnspiderfy(zoomDetails);
2147
2148                 this._group._spiderfied = null;
2149         },
2150
2151         _generatePointsCircle: function (count, centerPt) {
2152                 var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
2153                         legLength = circumference / this._2PI,  //radius from circumference
2154                         angleStep = this._2PI / count,
2155                         res = [],
2156                         i, angle;
2157
2158                 legLength = Math.max(legLength, 35); // Minimum distance to get outside the cluster icon.
2159
2160                 res.length = count;
2161
2162                 for (i = 0; i < count; i++) { // Clockwise, like spiral.
2163                         angle = this._circleStartAngle + i * angleStep;
2164                         res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
2165                 }
2166
2167                 return res;
2168         },
2169
2170         _generatePointsSpiral: function (count, centerPt) {
2171                 var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier,
2172                         legLength = spiderfyDistanceMultiplier * this._spiralLengthStart,
2173                         separation = spiderfyDistanceMultiplier * this._spiralFootSeparation,
2174                         lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI,
2175                         angle = 0,
2176                         res = [],
2177                         i;
2178
2179                 res.length = count;
2180
2181                 // Higher index, closer position to cluster center.
2182                 for (i = count; i >= 0; i--) {
2183                         // Skip the first position, so that we are already farther from center and we avoid
2184                         // being under the default cluster icon (especially important for Circle Markers).
2185                         if (i < count) {
2186                                 res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
2187                         }
2188                         angle += separation / legLength + i * 0.0005;
2189                         legLength += lengthFactor / angle;
2190                 }
2191                 return res;
2192         },
2193
2194         _noanimationUnspiderfy: function () {
2195                 var group = this._group,
2196                         map = group._map,
2197                         fg = group._featureGroup,
2198                         childMarkers = this.getAllChildMarkers(null, true),
2199                         m, i;
2200
2201                 group._ignoreMove = true;
2202
2203                 this.setOpacity(1);
2204                 for (i = childMarkers.length - 1; i >= 0; i--) {
2205                         m = childMarkers[i];
2206
2207                         fg.removeLayer(m);
2208
2209                         if (m._preSpiderfyLatlng) {
2210                                 m.setLatLng(m._preSpiderfyLatlng);
2211                                 delete m._preSpiderfyLatlng;
2212                         }
2213                         if (m.setZIndexOffset) {
2214                                 m.setZIndexOffset(0);
2215                         }
2216
2217                         if (m._spiderLeg) {
2218                                 map.removeLayer(m._spiderLeg);
2219                                 delete m._spiderLeg;
2220                         }
2221                 }
2222
2223                 group.fire('unspiderfied', {
2224                         cluster: this,
2225                         markers: childMarkers
2226                 });
2227                 group._ignoreMove = false;
2228                 group._spiderfied = null;
2229         }
2230 });
2231
2232 //Non Animated versions of everything
2233 L.MarkerClusterNonAnimated = L.MarkerCluster.extend({
2234         _animationSpiderfy: function (childMarkers, positions) {
2235                 var group = this._group,
2236                         map = group._map,
2237                         fg = group._featureGroup,
2238                         legOptions = this._group.options.spiderLegPolylineOptions,
2239                         i, m, leg, newPos;
2240
2241                 group._ignoreMove = true;
2242
2243                 // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
2244                 // The reverse order trick no longer improves performance on modern browsers.
2245                 for (i = 0; i < childMarkers.length; i++) {
2246                         newPos = map.layerPointToLatLng(positions[i]);
2247                         m = childMarkers[i];
2248
2249                         // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
2250                         leg = new L.Polyline([this._latlng, newPos], legOptions);
2251                         map.addLayer(leg);
2252                         m._spiderLeg = leg;
2253
2254                         // Now add the marker.
2255                         m._preSpiderfyLatlng = m._latlng;
2256                         m.setLatLng(newPos);
2257                         if (m.setZIndexOffset) {
2258                                 m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
2259                         }
2260
2261                         fg.addLayer(m);
2262                 }
2263                 this.setOpacity(0.3);
2264
2265                 group._ignoreMove = false;
2266                 group.fire('spiderfied', {
2267                         cluster: this,
2268                         markers: childMarkers
2269                 });
2270         },
2271
2272         _animationUnspiderfy: function () {
2273                 this._noanimationUnspiderfy();
2274         }
2275 });
2276
2277 //Animated versions here
2278 L.MarkerCluster.include({
2279
2280         _animationSpiderfy: function (childMarkers, positions) {
2281                 var me = this,
2282                         group = this._group,
2283                         map = group._map,
2284                         fg = group._featureGroup,
2285                         thisLayerLatLng = this._latlng,
2286                         thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng),
2287                         svg = L.Path.SVG,
2288                         legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions), // Copy the options so that we can modify them for animation.
2289                         finalLegOpacity = legOptions.opacity,
2290                         i, m, leg, legPath, legLength, newPos;
2291
2292                 if (finalLegOpacity === undefined) {
2293                         finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity;
2294                 }
2295
2296                 if (svg) {
2297                         // If the initial opacity of the spider leg is not 0 then it appears before the animation starts.
2298                         legOptions.opacity = 0;
2299
2300                         // Add the class for CSS transitions.
2301                         legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg';
2302                 } else {
2303                         // Make sure we have a defined opacity.
2304                         legOptions.opacity = finalLegOpacity;
2305                 }
2306
2307                 group._ignoreMove = true;
2308
2309                 // Add markers and spider legs to map, hidden at our center point.
2310                 // Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
2311                 // The reverse order trick no longer improves performance on modern browsers.
2312                 for (i = 0; i < childMarkers.length; i++) {
2313                         m = childMarkers[i];
2314
2315                         newPos = map.layerPointToLatLng(positions[i]);
2316
2317                         // Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
2318                         leg = new L.Polyline([thisLayerLatLng, newPos], legOptions);
2319                         map.addLayer(leg);
2320                         m._spiderLeg = leg;
2321
2322                         // Explanations: https://jakearchibald.com/2013/animated-line-drawing-svg/
2323                         // In our case the transition property is declared in the CSS file.
2324                         if (svg) {
2325                                 legPath = leg._path;
2326                                 legLength = legPath.getTotalLength() + 0.1; // Need a small extra length to avoid remaining dot in Firefox.
2327                                 legPath.style.strokeDasharray = legLength; // Just 1 length is enough, it will be duplicated.
2328                                 legPath.style.strokeDashoffset = legLength;
2329                         }
2330
2331                         // If it is a marker, add it now and we'll animate it out
2332                         if (m.setZIndexOffset) {
2333                                 m.setZIndexOffset(1000000); // Make normal markers appear on top of EVERYTHING
2334                         }
2335                         if (m.clusterHide) {
2336                                 m.clusterHide();
2337                         }
2338                         
2339                         // Vectors just get immediately added
2340                         fg.addLayer(m);
2341
2342                         if (m._setPos) {
2343                                 m._setPos(thisLayerPos);
2344                         }
2345                 }
2346
2347                 group._forceLayout();
2348                 group._animationStart();
2349
2350                 // Reveal markers and spider legs.
2351                 for (i = childMarkers.length - 1; i >= 0; i--) {
2352                         newPos = map.layerPointToLatLng(positions[i]);
2353                         m = childMarkers[i];
2354
2355                         //Move marker to new position
2356                         m._preSpiderfyLatlng = m._latlng;
2357                         m.setLatLng(newPos);
2358                         
2359                         if (m.clusterShow) {
2360                                 m.clusterShow();
2361                         }
2362
2363                         // Animate leg (animation is actually delegated to CSS transition).
2364                         if (svg) {
2365                                 leg = m._spiderLeg;
2366                                 legPath = leg._path;
2367                                 legPath.style.strokeDashoffset = 0;
2368                                 //legPath.style.strokeOpacity = finalLegOpacity;
2369                                 leg.setStyle({opacity: finalLegOpacity});
2370                         }
2371                 }
2372                 this.setOpacity(0.3);
2373
2374                 group._ignoreMove = false;
2375
2376                 setTimeout(function () {
2377                         group._animationEnd();
2378                         group.fire('spiderfied', {
2379                                 cluster: me,
2380                                 markers: childMarkers
2381                         });
2382                 }, 200);
2383         },
2384
2385         _animationUnspiderfy: function (zoomDetails) {
2386                 var me = this,
2387                         group = this._group,
2388                         map = group._map,
2389                         fg = group._featureGroup,
2390                         thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
2391                         childMarkers = this.getAllChildMarkers(null, true),
2392                         svg = L.Path.SVG,
2393                         m, i, leg, legPath, legLength, nonAnimatable;
2394
2395                 group._ignoreMove = true;
2396                 group._animationStart();
2397
2398                 //Make us visible and bring the child markers back in
2399                 this.setOpacity(1);
2400                 for (i = childMarkers.length - 1; i >= 0; i--) {
2401                         m = childMarkers[i];
2402
2403                         //Marker was added to us after we were spiderfied
2404                         if (!m._preSpiderfyLatlng) {
2405                                 continue;
2406                         }
2407
2408                         //Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll
2409                         m.closePopup();
2410
2411                         //Fix up the location to the real one
2412                         m.setLatLng(m._preSpiderfyLatlng);
2413                         delete m._preSpiderfyLatlng;
2414
2415                         //Hack override the location to be our center
2416                         nonAnimatable = true;
2417                         if (m._setPos) {
2418                                 m._setPos(thisLayerPos);
2419                                 nonAnimatable = false;
2420                         }
2421                         if (m.clusterHide) {
2422                                 m.clusterHide();
2423                                 nonAnimatable = false;
2424                         }
2425                         if (nonAnimatable) {
2426                                 fg.removeLayer(m);
2427                         }
2428
2429                         // Animate the spider leg back in (animation is actually delegated to CSS transition).
2430                         if (svg) {
2431                                 leg = m._spiderLeg;
2432                                 legPath = leg._path;
2433                                 legLength = legPath.getTotalLength() + 0.1;
2434                                 legPath.style.strokeDashoffset = legLength;
2435                                 leg.setStyle({opacity: 0});
2436                         }
2437                 }
2438
2439                 group._ignoreMove = false;
2440
2441                 setTimeout(function () {
2442                         //If we have only <= one child left then that marker will be shown on the map so don't remove it!
2443                         var stillThereChildCount = 0;
2444                         for (i = childMarkers.length - 1; i >= 0; i--) {
2445                                 m = childMarkers[i];
2446                                 if (m._spiderLeg) {
2447                                         stillThereChildCount++;
2448                                 }
2449                         }
2450
2451
2452                         for (i = childMarkers.length - 1; i >= 0; i--) {
2453                                 m = childMarkers[i];
2454
2455                                 if (!m._spiderLeg) { //Has already been unspiderfied
2456                                         continue;
2457                                 }
2458
2459                                 if (m.clusterShow) {
2460                                         m.clusterShow();
2461                                 }
2462                                 if (m.setZIndexOffset) {
2463                                         m.setZIndexOffset(0);
2464                                 }
2465
2466                                 if (stillThereChildCount > 1) {
2467                                         fg.removeLayer(m);
2468                                 }
2469
2470                                 map.removeLayer(m._spiderLeg);
2471                                 delete m._spiderLeg;
2472                         }
2473                         group._animationEnd();
2474                         group.fire('unspiderfied', {
2475                                 cluster: me,
2476                                 markers: childMarkers
2477                         });
2478                 }, 200);
2479         }
2480 });
2481
2482
2483 L.MarkerClusterGroup.include({
2484         //The MarkerCluster currently spiderfied (if any)
2485         _spiderfied: null,
2486
2487         unspiderfy: function () {
2488                 this._unspiderfy.apply(this, arguments);
2489         },
2490
2491         _spiderfierOnAdd: function () {
2492                 this._map.on('click', this._unspiderfyWrapper, this);
2493
2494                 if (this._map.options.zoomAnimation) {
2495                         this._map.on('zoomstart', this._unspiderfyZoomStart, this);
2496                 }
2497                 //Browsers without zoomAnimation or a big zoom don't fire zoomstart
2498                 this._map.on('zoomend', this._noanimationUnspiderfy, this);
2499
2500                 if (!L.Browser.touch) {
2501                         this._map.getRenderer(this);
2502                         //Needs to happen in the pageload, not after, or animations don't work in webkit
2503                         //  http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
2504                         //Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
2505                 }
2506         },
2507
2508         _spiderfierOnRemove: function () {
2509                 this._map.off('click', this._unspiderfyWrapper, this);
2510                 this._map.off('zoomstart', this._unspiderfyZoomStart, this);
2511                 this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
2512                 this._map.off('zoomend', this._noanimationUnspiderfy, this);
2513
2514                 //Ensure that markers are back where they should be
2515                 // Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane
2516                 this._noanimationUnspiderfy();
2517         },
2518
2519         //On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
2520         //This means we can define the animation they do rather than Markers doing an animation to their actual location
2521         _unspiderfyZoomStart: function () {
2522                 if (!this._map) { //May have been removed from the map by a zoomEnd handler
2523                         return;
2524                 }
2525
2526                 this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
2527         },
2528
2529         _unspiderfyZoomAnim: function (zoomDetails) {
2530                 //Wait until the first zoomanim after the user has finished touch-zooming before running the animation
2531                 if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
2532                         return;
2533                 }
2534
2535                 this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
2536                 this._unspiderfy(zoomDetails);
2537         },
2538
2539         _unspiderfyWrapper: function () {
2540                 /// <summary>_unspiderfy but passes no arguments</summary>
2541                 this._unspiderfy();
2542         },
2543
2544         _unspiderfy: function (zoomDetails) {
2545                 if (this._spiderfied) {
2546                         this._spiderfied.unspiderfy(zoomDetails);
2547                 }
2548         },
2549
2550         _noanimationUnspiderfy: function () {
2551                 if (this._spiderfied) {
2552                         this._spiderfied._noanimationUnspiderfy();
2553                 }
2554         },
2555
2556         //If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
2557         _unspiderfyLayer: function (layer) {
2558                 if (layer._spiderLeg) {
2559                         this._featureGroup.removeLayer(layer);
2560
2561                         if (layer.clusterShow) {
2562                                 layer.clusterShow();
2563                         }
2564                                 //Position will be fixed up immediately in _animationUnspiderfy
2565                         if (layer.setZIndexOffset) {
2566                                 layer.setZIndexOffset(0);
2567                         }
2568
2569                         this._map.removeLayer(layer._spiderLeg);
2570                         delete layer._spiderLeg;
2571                 }
2572         }
2573 });
2574
2575 /**
2576  * Adds 1 public method to MCG and 1 to L.Marker to facilitate changing
2577  * markers' icon options and refreshing their icon and their parent clusters
2578  * accordingly (case where their iconCreateFunction uses data of childMarkers
2579  * to make up the cluster icon).
2580  */
2581
2582
2583 L.MarkerClusterGroup.include({
2584         /**
2585          * Updates the icon of all clusters which are parents of the given marker(s).
2586          * In singleMarkerMode, also updates the given marker(s) icon.
2587          * @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)|
2588          * L.MarkerCluster|L.Marker (optional) list of markers (or single marker) whose parent
2589          * clusters need to be updated. If not provided, retrieves all child markers of this.
2590          * @returns {L.MarkerClusterGroup}
2591          */
2592         refreshClusters: function (layers) {
2593                 if (!layers) {
2594                         layers = this._topClusterLevel.getAllChildMarkers();
2595                 } else if (layers instanceof L.MarkerClusterGroup) {
2596                         layers = layers._topClusterLevel.getAllChildMarkers();
2597                 } else if (layers instanceof L.LayerGroup) {
2598                         layers = layers._layers;
2599                 } else if (layers instanceof L.MarkerCluster) {
2600                         layers = layers.getAllChildMarkers();
2601                 } else if (layers instanceof L.Marker) {
2602                         layers = [layers];
2603                 } // else: must be an Array(L.Marker)|Map(L.Marker)
2604                 this._flagParentsIconsNeedUpdate(layers);
2605                 this._refreshClustersIcons();
2606
2607                 // In case of singleMarkerMode, also re-draw the markers.
2608                 if (this.options.singleMarkerMode) {
2609                         this._refreshSingleMarkerModeMarkers(layers);
2610                 }
2611
2612                 return this;
2613         },
2614
2615         /**
2616          * Simply flags all parent clusters of the given markers as having a "dirty" icon.
2617          * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
2618          * @private
2619          */
2620         _flagParentsIconsNeedUpdate: function (layers) {
2621                 var id, parent;
2622
2623                 // Assumes layers is an Array or an Object whose prototype is non-enumerable.
2624                 for (id in layers) {
2625                         // Flag parent clusters' icon as "dirty", all the way up.
2626                         // Dumb process that flags multiple times upper parents, but still
2627                         // much more efficient than trying to be smart and make short lists,
2628                         // at least in the case of a hierarchy following a power law:
2629                         // http://jsperf.com/flag-nodes-in-power-hierarchy/2
2630                         parent = layers[id].__parent;
2631                         while (parent) {
2632                                 parent._iconNeedsUpdate = true;
2633                                 parent = parent.__parent;
2634                         }
2635                 }
2636         },
2637
2638         /**
2639          * Re-draws the icon of the supplied markers.
2640          * To be used in singleMarkerMode only.
2641          * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
2642          * @private
2643          */
2644         _refreshSingleMarkerModeMarkers: function (layers) {
2645                 var id, layer;
2646
2647                 for (id in layers) {
2648                         layer = layers[id];
2649
2650                         // Make sure we do not override markers that do not belong to THIS group.
2651                         if (this.hasLayer(layer)) {
2652                                 // Need to re-create the icon first, then re-draw the marker.
2653                                 layer.setIcon(this._overrideMarkerIcon(layer));
2654                         }
2655                 }
2656         }
2657 });
2658
2659 L.Marker.include({
2660         /**
2661          * Updates the given options in the marker's icon and refreshes the marker.
2662          * @param options map object of icon options.
2663          * @param directlyRefreshClusters boolean (optional) true to trigger
2664          * MCG.refreshClustersOf() right away with this single marker.
2665          * @returns {L.Marker}
2666          */
2667         refreshIconOptions: function (options, directlyRefreshClusters) {
2668                 var icon = this.options.icon;
2669
2670                 L.setOptions(icon, options);
2671
2672                 this.setIcon(icon);
2673
2674                 // Shortcut to refresh the associated MCG clusters right away.
2675                 // To be used when refreshing a single marker.
2676                 // Otherwise, better use MCG.refreshClusters() once at the end with
2677                 // the list of modified markers.
2678                 if (directlyRefreshClusters && this.__parent) {
2679                         this.__parent._group.refreshClusters(this);
2680                 }
2681
2682                 return this;
2683         }
2684 });
2685
2686 exports.MarkerClusterGroup = MarkerClusterGroup;
2687 exports.MarkerCluster = MarkerCluster;
2688
2689 })));
2690 //# sourceMappingURL=leaflet.markercluster-src.js.map