PageSourceSearch

https://saii.kr/assets/leaflet.markercluster-20a6e492.js

js saii.kr collected 2026-10-06 03:43:54 UTC 81,235 bytes, 2,720 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.