PageSourceSearch

https://benais.fr/wp-content/themes/cctoval/js/leaflet.markercluster-src.js

js benais.fr collected 2026-10-02 23:58:11 UTC 76,783 bytes, 2,677 lines download raw bytes

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

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