PageSourceSearch

https://www.ensta.org/global/javascript/Leaflet.markercluster/dist/leaflet.markercluster-src.js

js ensta.org collected 2026-09-25 07:52:03 UTC 74,833 bytes, 2,632 lines download raw bytes

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