PageSourceSearch

https://tourisme-terrestouloises.com/wp-content/plugins/Illico-Ges…/js/leaflet/leaflet.markercluster-src.js

js tourisme-terrestouloises.com collected 2026-09-25 06:04:04 UTC 80,458 bytes, 2,690 lines download raw bytes

vendor: 80,458 bytes, lines 1-2690
1/*
2 * Leaflet.markercluster 1.4.1+master.94f9815,
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
17var 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.
1184L.MarkerClusterGroup.include({
1185	_mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity))
1186});
1187
1188L.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
1384L.markerClusterGroup = function (options) {
1385	return new L.MarkerClusterGroup(options);
1386};
1387
1388var 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
1802L.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
1815L.DistanceGrid = function (cellSize) {
1816	this._cellSize = cellSize;
1817	this._sqCellSize = cellSize * cellSize;
1818	this._grid = {};
1819	this._objectPoint = { };
1820};
1821
1822L.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
1934the authors of referenced articles or incorporated external code:
1935http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
1936
1937Permission is hereby granted, free of charge, to any person obtaining
1938a copy of this software and associated documentation files (the
1939"Software"), to deal in the Software without restriction, including
1940without limitation the rights to use, copy, modify, merge, publish,
1941distribute, sublicense, and/or sell copies of the Software, and to
1942permit persons to whom the Software is furnished to do so, subject to
1943the following conditions:
1944
1945The above copyright notice and this permission notice shall be
1946included in all copies or substantial portions of the Software.
1947
1948THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1949EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1950MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
1951IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
1952CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
1953TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1954SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1955
1956Retrieved 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
2084L.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
2102L.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
2233L.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
2278L.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
2483L.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
2583L.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
2659L.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
2686exports.MarkerClusterGroup = MarkerClusterGroup;
2687exports.MarkerCluster = MarkerCluster;
2688
2689})));
2690//# sourceMappingURL=leaflet.markercluster-src.js.map

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.