PageSourceSearch

https://www.innovationsociale.ch/_assets/f8eee1518c85a15491b04f0b1…/leaflet.markercluster-src.js?1716288041

js innovationsociale.ch collected 2026-10-07 11:08:58 UTC 52,006 bytes, 1,770 lines download raw bytes

1/*
2 Copyright (c) 2012, Smartrak, David Leaver
3 Leaflet.markercluster is an open-source JavaScript library for Marker Clustering on leaflet powered maps.
4 https://github.com/danzel/Leaflet.markercluster
5*/
6(function (window, undefined) {
7
8
9/*
10 * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
11 */
12
13L.MarkerClusterGroup = L.FeatureGroup.extend({
14
15	options: {
16		maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center
17		iconCreateFunction: null,
18
19		spiderfyOnMaxZoom: true,
20		showCoverageOnHover: true,
21		zoomToBoundsOnClick: true,
22		singleMarkerMode: false,
23
24		disableClusteringAtZoom: null,
25
26		//Whether to animate adding markers after adding the MarkerClusterGroup to the map
27		// If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
28		animateAddingMarkers: false,
29
30		//Options to pass to the L.Polygon constructor
31		polygonOptions: {}
32	},
33
34	initialize: function (options) {
35		L.Util.setOptions(this, options);
36		if (!this.options.iconCreateFunction) {
37			this.options.iconCreateFunction = this._defaultIconCreateFunction;
38		}
39
40		L.FeatureGroup.prototype.initialize.call(this, []);
41
42		this._inZoomAnimation = 0;
43		this._needsClustering = [];
44		//The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
45		this._currentShownBounds = null;
46	},
47
48	addLayer: function (layer) {
49
50		if (layer instanceof L.LayerGroup) {
51			var array = [];
52			for (var i in layer._layers) {
53				if (layer._layers.hasOwnProperty(i)) {
54					array.push(layer._layers[i]);
55				}
56			}
57			return this.addLayers(array);
58		}
59
60		if (this.options.singleMarkerMode) {
61			layer.options.icon = this.options.iconCreateFunction({
62				getChildCount: function () {
63					return 1;
64				},
65				getAllChildMarkers: function () {
66					return [layer];
67				}
68			});
69		}
70
71		if (!this._map) {
72			this._needsClustering.push(layer);
73			return this;
74		}
75
76		if (this.hasLayer(layer)) {
77			return this;
78		}
79
80		//If we have already clustered we'll need to add this one to a cluster
81
82		if (this._unspiderfy) {
83			this._unspiderfy();
84		}
85
86		this._addLayer(layer, this._maxZoom);
87
88		//Work out what is visible
89		var visibleLayer = layer,
90			currentZoom = this._map.getZoom();
91		if (layer.__parent) {
92			while (visibleLayer.__parent._zoom >= currentZoom) {
93				visibleLayer = visibleLayer.__parent;
94			}
95		}
96
97		if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
98			if (this.options.animateAddingMarkers) {
99				this._animationAddLayer(layer, visibleLayer);
100			} else {
101				this._animationAddLayerNonAnimated(layer, visibleLayer);
102			}
103		}
104		return this;
105	},
106
107	removeLayer: function (layer) {
108
109		if (!this._map) {
110			this._arraySplice(this._needsClustering, layer);
111			return this;
112		}
113
114		if (!layer.__parent) {
115			return this;
116		}
117
118		if (this._unspiderfy) {
119			this._unspiderfy();
120			this._unspiderfyLayer(layer);
121		}
122
123		//Remove the marker from clusters
124		this._removeLayer(layer, true);
125
126		if (layer._icon) {
127			L.FeatureGroup.prototype.removeLayer.call(this, layer);
128			layer.setOpacity(1);
129		}
130		return this;
131	},
132
133	//Takes an array of markers and adds them in bulk
134	addLayers: function (layersArray) {
135		if (!this._map) {
136			this._needsClustering = this._needsClustering.concat(layersArray);
137			return this;
138		}
139
140		for (var i = 0, l = layersArray.length; i < l; i++) {
141			var m = layersArray[i];
142			this._addLayer(m, this._maxZoom);
143
144			//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
145			if (m.__parent) {
146				if (m.__parent.getChildCount() === 2) {
147					var markers = m.__parent.getAllChildMarkers(),
148						otherMarker = markers[0] === m ? markers[1] : markers[0];
149					L.FeatureGroup.prototype.removeLayer.call(this, otherMarker);
150				}
151			}
152		}
153		this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
154
155		return this;
156	},
157
158	//Takes an array of markers and removes them in bulk
159	removeLayers: function (layersArray) {
160		var i, l, m;
161
162		if (!this._map) {
163			for (i = 0, l = layersArray.length; i < l; i++) {
164				this._arraySplice(this._needsClustering, layersArray[i]);
165			}
166			return this;
167		}
168
169		for (i = 0, l = layersArray.length; i < l; i++) {
170			m = layersArray[i];
171			this._removeLayer(m, true, true);
172
173			if (m._icon) {
174				L.FeatureGroup.prototype.removeLayer.call(this, m);
175				m.setOpacity(1);
176			}
177		}
178
179		//Fix up the clusters and markers on the map
180		this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
181
182		for (i in this._layers) {
183			if (this._layers.hasOwnProperty(i)) {
184				m = this._layers[i];
185				if (m instanceof L.MarkerCluster) {
186					m._updateIcon();
187				}
188			}
189		}
190
191		return this;
192	},
193
194	//Removes all layers from the MarkerClusterGroup
195	clearLayers: function () {
196		//Need our own special implementation as the LayerGroup one doesn't work for us
197
198		//If we aren't on the map yet, just blow away the markers we know of
199		if (!this._map) {
200			this._needsClustering = [];
201			return this;
202		}
203
204		if (this._unspiderfy) {
205			this._unspiderfy();
206		}
207
208		//Remove all the visible layers
209		for (var i in this._layers) {
210			if (this._layers.hasOwnProperty(i)) {
211				L.FeatureGroup.prototype.removeLayer.call(this, this._layers[i]);
212			}
213		}
214
215		//Reset _topClusterLevel and the DistanceGrids
216		this._generateInitialClusters();
217
218		return this;
219	},
220
221	//Returns true if the given layer is in this MarkerClusterGroup
222	hasLayer: function (layer) {
223		if (this._needsClustering.length > 0) {
224			var anArray = this._needsClustering;
225			for (var i = anArray.length - 1; i >= 0; i--) {
226				if (anArray[i] === layer) {
227					return true;
228				}
229			}
230		}
231
232		return !!(layer.__parent && layer.__parent._group === this);
233	},
234
235	//Zoom down to show the given layer (spiderfying if necessary) then calls the callback
236	zoomToShowLayer: function (layer, callback) {
237
238		var showMarker = function () {
239			if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
240				this._map.off('moveend', showMarker, this);
241				this.off('animationend', showMarker, this);
242
243				if (layer._icon) {
244					callback();
245				} else if (layer.__parent._icon) {
246					var afterSpiderfy = function () {
247						this.off('spiderfied', afterSpiderfy, this);
248						callback();
249					};
250
251					this.on('spiderfied', afterSpiderfy, this);
252					layer.__parent.spiderfy();
253				}
254			}
255		};
256
257		if (layer._icon) {
258			callback();
259		} else if (layer.__parent._zoom < this._map.getZoom()) {
260			//Layer should be visible now but isn't on screen, just pan over to it
261			this._map.on('moveend', showMarker, this);
262			if (!layer._icon) {
263				this._map.panTo(layer.getLatLng());
264			}
265		} else {
266			this._map.on('moveend', showMarker, this);
267			this.on('animationend', showMarker, this);
268			this._map.setView(layer.getLatLng(), layer.__parent._zoom + 1);
269			layer.__parent.zoomToBounds();
270		}
271	},
272
273	//Overrides FeatureGroup.onAdd
274	onAdd: function (map) {
275		L.FeatureGroup.prototype.onAdd.call(this, map);
276
277		if (!this._gridClusters) {
278			this._generateInitialClusters();
279		}
280
281		for (var i = 0, l = this._needsClustering.length; i < l; i++) {
282			this._addLayer(this._needsClustering[i], this._maxZoom);
283		}
284		this._needsClustering = [];
285
286		this._map.on('zoomend', this._zoomEnd, this);
287		this._map.on('moveend', this._moveEnd, this);
288
289		if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
290			this._spiderfierOnAdd();
291		}
292
293		this._bindEvents();
294
295
296		//Actually add our markers to the map:
297
298		//Remember the current zoom level and bounds
299		this._zoom = this._map.getZoom();
300		this._currentShownBounds = this._getExpandedVisibleBounds();
301
302		//Make things appear on the map
303		this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
304	},
305
306	//Overrides FeatureGroup.onRemove
307	onRemove: function (map) {
308		this._map.off('zoomend', this._zoomEnd, this);
309		this._map.off('moveend', this._moveEnd, this);
310
311		//In case we are in a cluster animation
312		this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
313
314		if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
315			this._spiderfierOnRemove();
316		}
317
318		L.FeatureGroup.prototype.onRemove.call(this, map);
319	},
320
321
322	//Remove the given object from the given array
323	_arraySplice: function (anArray, obj) {
324		for (var i = anArray.length - 1; i >= 0; i--) {
325			if (anArray[i] === obj) {
326				anArray.splice(i, 1);
327				return;
328			}
329		}
330	},
331
332	//Internal function for removing a marker from everything.
333	//dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
334	_removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
335		var gridClusters = this._gridClusters,
336			gridUnclustered = this._gridUnclustered,
337			map = this._map;
338
339		//Remove the marker from distance clusters it might be in
340		if (removeFromDistanceGrid) {
341			for (var z = this._maxZoom; z >= 0; z--) {
342				if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
343					break;
344				}
345			}
346		}
347
348		//Work our way up the clusters removing them as we go if required
349		var cluster = marker.__parent,
350			markers = cluster._markers,
351			otherMarker;
352
353		//Remove the marker from the immediate parents marker list
354		this._arraySplice(markers, marker);
355
356		while (cluster) {
357			cluster._childCount--;
358
359			if (cluster._zoom < 0) {
360				//Top level, do nothing
361				break;
362			} else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required
363				//We need to push the other marker up to the parent
364				otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
365
366				//Update distance grid
367				gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
368				gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
369
370				//Move otherMarker up to parent
371				this._arraySplice(cluster.__parent._childClusters, cluster);
372				cluster.__parent._markers.push(otherMarker);
373				otherMarker.__parent = cluster.__parent;
374
375				if (cluster._icon) {
376					//Cluster is currently on the map, need to put the marker on the map instead
377					L.FeatureGroup.prototype.removeLayer.call(this, cluster);
378					if (!dontUpdateMap) {
379						L.FeatureGroup.prototype.addLayer.call(this, otherMarker);
380					}
381				}
382			} else {
383				cluster._recalculateBounds();
384				if (!dontUpdateMap || !cluster._icon) {
385					cluster._updateIcon();
386				}
387			}
388
389			cluster = cluster.__parent;
390		}
391	},
392
393	//Overrides FeatureGroup._propagateEvent
394	_propagateEvent: function (e) {
395		if (e.target instanceof L.MarkerCluster) {
396			e.type = 'cluster' + e.type;
397		}
398		L.FeatureGroup.prototype._propagateEvent.call(this, e);
399	},
400
401	//Default functionality
402	_defaultIconCreateFunction: function (cluster) {
403		var childCount = cluster.getChildCount();
404
405		var c = ' marker-cluster-';
406		if (childCount < 10) {
407			c += 'small';
408		} else if (childCount < 100) {
409			c += 'medium';
410		} else {
411			c += 'large';
412		}
413
414		return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
415	},
416
417	_bindEvents: function () {
418		var shownPolygon = null,
419			map = this._map,
420
421			spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
422			showCoverageOnHover = this.options.showCoverageOnHover,
423			zoomToBoundsOnClick = this.options.zoomToBoundsOnClick;
424
425		//Zoom on cluster click or spiderfy if we are at the lowest level
426		if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
427			this.on('clusterclick', function (a) {
428				if (map.getMaxZoom() === map.getZoom()) {
429					if (spiderfyOnMaxZoom) {
430						a.layer.spiderfy();
431					}
432				} else if (zoomToBoundsOnClick) {
433					a.layer.zoomToBounds();
434				}
435			}, this);
436		}
437
438		//Show convex hull (boundary) polygon on mouse over
439		if (showCoverageOnHover) {
440			this.on('clustermouseover', function (a) {
441				if (this._inZoomAnimation) {
442					return;
443				}
444				if (shownPolygon) {
445					map.removeLayer(shownPolygon);
446				}
447				if (a.layer.getChildCount() > 2) {
448					shownPolygon = new L.Polygon(a.layer.getConvexHull(), this.options.polygonOptions);
449					map.addLayer(shownPolygon);
450				}
451			}, this);
452			this.on('clustermouseout', function () {
453				if (shownPolygon) {
454					map.removeLayer(shownPolygon);
455					shownPolygon = null;
456				}
457			}, this);
458			map.on('zoomend', function () {
459				if (shownPolygon) {
460					map.removeLayer(shownPolygon);
461					shownPolygon = null;
462				}
463			}, this);
464			map.on('layerremove', function (opt) {
465				if (shownPolygon && opt.layer === this) {
466					map.removeLayer(shownPolygon);
467					shownPolygon = null;
468				}
469			}, this);
470		}
471	},
472
473	_zoomEnd: function () {
474		if (!this._map) { //May have been removed from the map by a zoomEnd handler
475			return;
476		}
477		this._mergeSplitClusters();
478
479		this._zoom = this._map._zoom;
480		this._currentShownBounds = this._getExpandedVisibleBounds();
481	},
482
483	_moveEnd: function () {
484		if (this._inZoomAnimation) {
485			return;
486		}
487
488		var newBounds = this._getExpandedVisibleBounds();
489
490		this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, newBounds);
491		this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, newBounds);
492
493		this._currentShownBounds = newBounds;
494		return;
495	},
496
497	_generateInitialClusters: function () {
498		var maxZoom = this._map.getMaxZoom(),
499			radius = this.options.maxClusterRadius;
500
501		if (this.options.disableClusteringAtZoom) {
502			maxZoom = this.options.disableClusteringAtZoom - 1;
503		}
504		this._maxZoom = maxZoom;
505		this._gridClusters = {};
506		this._gridUnclustered = {};
507
508		//Set up DistanceGrids for each zoom
509		for (var zoom = maxZoom; zoom >= 0; zoom--) {
510			this._gridClusters[zoom] = new L.DistanceGrid(radius);
511			this._gridUnclustered[zoom] = new L.DistanceGrid(radius);
512		}
513
514		this._topClusterLevel = new L.MarkerCluster(this, -1);
515	},
516
517	//Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
518	_addLayer: function (layer, zoom) {
519		var gridClusters = this._gridClusters,
520		    gridUnclustered = this._gridUnclustered,
521		    markerPoint, z;
522
523		//Find the lowest zoom level to slot this one in
524		for (; zoom >= 0; zoom--) {
525			markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
526
527			//Try find a cluster close by
528			var closest = gridClusters[zoom].getNearObject(markerPoint);
529			if (closest) {
530				closest._addChild(layer);
531				layer.__parent = closest;
532				return;
533			}
534
535			//Try find a marker close by to form a new cluster with
536			closest = gridUnclustered[zoom].getNearObject(markerPoint);
537			if (closest) {
538				if (closest.__parent) {
539					this._removeLayer(closest, false);
540				}
541				var parent = closest.__parent;
542
543				//Create new cluster with these 2 in it
544
545				var newCluster = new L.MarkerCluster(this, zoom, closest, layer);
546				gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
547				closest.__parent = newCluster;
548				layer.__parent = newCluster;
549
550				//First create any new intermediate parent clusters that don't exist
551				var lastParent = newCluster;
552				for (z = zoom - 1; z > parent._zoom; z--) {
553					lastParent = new L.MarkerCluster(this, z, lastParent);
554					gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
555				}
556				parent._addChild(lastParent);
557
558				//Remove closest from this zoom level and any above that it is in, replace with newCluster
559				for (z = zoom; z >= 0; z--) {
560					if (!gridUnclustered[z].removeObject(closest, this._map.project(closest.getLatLng(), z))) {
561						break;
562					}
563				}
564
565				return;
566			}
567
568			//Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
569			gridUnclustered[zoom].addObject(layer, markerPoint);
570		}
571
572		//Didn't get in anything, add us to the top
573		this._topClusterLevel._addChild(layer);
574		layer.__parent = this._topClusterLevel;
575		return;
576	},
577
578	//Merge and split any existing clusters that are too big or small
579	_mergeSplitClusters: function () {
580		if (this._zoom < this._map._zoom) { //Zoom in, split
581			this._animationStart();
582			//Remove clusters now off screen
583			this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._zoom, this._getExpandedVisibleBounds());
584
585			this._animationZoomIn(this._zoom, this._map._zoom);
586
587		} else if (this._zoom > this._map._zoom) { //Zoom out, merge
588			this._animationStart();
589
590			this._animationZoomOut(this._zoom, this._map._zoom);
591		} else {
592			this._moveEnd();
593		}
594	},
595
596	//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)
597	_getExpandedVisibleBounds: function () {
598		var map = this._map,
599			bounds = map.getPixelBounds(),
600			width =  L.Browser.mobile ? 0 : Math.abs(bounds.max.x - bounds.min.x),
601			height = L.Browser.mobile ? 0 : Math.abs(bounds.max.y - bounds.min.y),
602			sw = map.unproject(new L.Point(bounds.min.x - width, bounds.min.y - height)),
603			ne = map.unproject(new L.Point(bounds.max.x + width, bounds.max.y + height));
604
605		return new L.LatLngBounds(sw, ne);
606	},
607
608	//Shared animation code
609	_animationAddLayerNonAnimated: function (layer, newCluster) {
610		if (newCluster === layer) {
611			L.FeatureGroup.prototype.addLayer.call(this, layer);
612		} else if (newCluster._childCount === 2) {
613			newCluster._addToMap();
614
615			var markers = newCluster.getAllChildMarkers();
616			L.FeatureGroup.prototype.removeLayer.call(this, markers[0]);
617			L.FeatureGroup.prototype.removeLayer.call(this, markers[1]);
618		} else {
619			newCluster._updateIcon();
620		}
621	}
622});
623
624L.MarkerClusterGroup.include(!L.DomUtil.TRANSITION ? {
625
626	//Non Animated versions of everything
627	_animationStart: function () {
628		//Do nothing...
629	},
630	_animationZoomIn: function (previousZoomLevel, newZoomLevel) {
631		this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel);
632		this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
633	},
634	_animationZoomOut: function (previousZoomLevel, newZoomLevel) {
635		this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel);
636		this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
637	},
638	_animationAddLayer: function (layer, newCluster) {
639		this._animationAddLayerNonAnimated(layer, newCluster);
640	}
641} : {
642
643	//Animated versions here
644	_animationStart: function () {
645		this._map._mapPane.className += ' leaflet-cluster-anim';
646		this._inZoomAnimation++;
647	},
648	_animationEnd: function () {
649		if (this._map) {
650			this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
651		}
652		this._inZoomAnimation--;
653		this.fire('animationend');
654	},
655	_animationZoomIn: function (previousZoomLevel, newZoomLevel) {
656		var me = this,
657		    bounds = this._getExpandedVisibleBounds(),
658		    i;
659
660		//Add all children of current clusters to map and remove those clusters from map
661		this._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) {
662			var startPos = c._latlng,
663				markers = c._markers,
664				m;
665
666			if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us
667				L.FeatureGroup.prototype.removeLayer.call(me, c);
668				c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
669			} else {
670				//Fade out old cluster
671				c.setOpacity(0);
672				c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
673			}
674
675			//Remove all markers that aren't visible any more
676			//TODO: Do we actually need to do this on the higher levels too?
677			for (i = markers.length - 1; i >= 0; i--) {
678				m = markers[i];
679				if (!bounds.contains(m._latlng)) {
680					L.FeatureGroup.prototype.removeLayer.call(me, m);
681				}
682			}
683
684		});
685
686		this._forceLayout();
687		var j, n;
688
689		//Update opacities
690		me._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
691		//TODO Maybe? Update markers in _recursivelyBecomeVisible
692		for (j in me._layers) {
693			if (me._layers.hasOwnProperty(j)) {
694				n = me._layers[j];
695
696				if (!(n instanceof L.MarkerCluster) && n._icon) {
697					n.setOpacity(1);
698				}
699			}
700		}
701
702		//update the positions of the just added clusters/markers
703		me._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
704			c._recursivelyRestoreChildPositions(newZoomLevel);
705		});
706
707		//Remove the old clusters and close the zoom animation
708
709		setTimeout(function () {
710			//update the positions of the just added clusters/markers
711			me._topClusterLevel._recursively(bounds, previousZoomLevel, 0, function (c) {
712				L.FeatureGroup.prototype.removeLayer.call(me, c);
713				c.setOpacity(1);
714			});
715
716			me._animationEnd();
717		}, 250);
718	},
719
720	_animationZoomOut: function (previousZoomLevel, newZoomLevel) {
721		this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel);
722
723		//Need to add markers for those that weren't on the map before but are now
724		this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
725		//Remove markers that were on the map before but won't be now
726		this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, previousZoomLevel, this._getExpandedVisibleBounds());
727	},
728	_animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
729		var bounds = this._getExpandedVisibleBounds();
730
731		//Animate all of the markers in the clusters to move to their cluster center point
732		cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, previousZoomLevel + 1, newZoomLevel);
733
734		var me = this;
735
736		//Update the opacity (If we immediately set it they won't animate)
737		this._forceLayout();
738		cluster._recursivelyBecomeVisible(bounds, newZoomLevel);
739
740		//TODO: Maybe use the transition timing stuff to make this more reliable
741		//When the animations are done, tidy up
742		setTimeout(function () {
743
744			//This cluster stopped being a cluster before the timeout fired
745			if (cluster._childCount === 1) {
746				var m = cluster._markers[0];
747				//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
748				m.setLatLng(m.getLatLng());
749				m.setOpacity(1);
750
751				return;
752			}
753
754			cluster._recursively(bounds, newZoomLevel, 0, function (c) {
755				c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel + 1);
756			});
757			me._animationEnd();
758		}, 250);
759	},
760	_animationAddLayer: function (layer, newCluster) {
761		var me = this;
762
763		L.FeatureGroup.prototype.addLayer.call(this, layer);
764		if (newCluster !== layer) {
765			if (newCluster._childCount > 2) { //Was already a cluster
766
767				newCluster._updateIcon();
768				this._forceLayout();
769				this._animationStart();
770
771				layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
772				layer.setOpacity(0);
773
774				setTimeout(function () {
775					L.FeatureGroup.prototype.removeLayer.call(me, layer);
776					layer.setOpacity(1);
777
778					me._animationEnd();
779				}, 250);
780
781			} else { //Just became a cluster
782				this._forceLayout();
783
784				me._animationStart();
785				me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._map.getZoom());
786			}
787		}
788	},
789
790	//Force a browser layout of stuff in the map
791	// Should apply the current opacity and location to all elements so we can update them again for an animation
792	_forceLayout: function () {
793		//In my testing this works, infact offsetWidth of any element seems to work.
794		//Could loop all this._layers and do this for each _icon if it stops working
795
796		L.Util.falseFn(document.body.offsetWidth);
797	}
798});
799
800
801L.MarkerCluster = L.Marker.extend({
802	initialize: function (group, zoom, a, b) {
803
804		L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), { icon: this });
805
806
807		this._group = group;
808		this._zoom = zoom;
809
810		this._markers = [];
811		this._childClusters = [];
812		this._childCount = 0;
813		this._iconNeedsUpdate = true;
814
815		this._bounds = new L.LatLngBounds();
816
817		if (a) {
818			this._addChild(a);
819		}
820		if (b) {
821			this._addChild(b);
822		}
823	},
824
825	//Recursively retrieve all child markers of this cluster
826	getAllChildMarkers: function (storageArray) {
827		storageArray = storageArray || [];
828
829		for (var i = this._childClusters.length - 1; i >= 0; i--) {
830			this._childClusters[i].getAllChildMarkers(storageArray);
831		}
832
833		for (var j = this._markers.length - 1; j >= 0; j--) {
834			storageArray.push(this._markers[j]);
835		}
836
837		return storageArray;
838	},
839
840	//Returns the count of how many child markers we have
841	getChildCount: function () {
842		return this._childCount;
843	},
844
845	//Zoom to the extents of this cluster
846	zoomToBounds: function () {
847		this._group._map.fitBounds(this._bounds);
848	},
849
850
851	_updateIcon: function () {
852		this._iconNeedsUpdate = true;
853		if (this._icon) {
854			this.setIcon(this);
855		}
856	},
857
858	//Cludge for Icon, we pretend to be an icon for performance
859	createIcon: function () {
860		if (this._iconNeedsUpdate) {
861			this._iconObj = this._group.options.iconCreateFunction(this);
862			this._iconNeedsUpdate = false;
863		}
864		return this._iconObj.createIcon();
865	},
866	createShadow: function () {
867		return this._iconObj.createShadow();
868	},
869
870
871	_addChild: function (new1, isNotificationFromChild) {
872
873		this._iconNeedsUpdate = true;
874		this._expandBounds(new1);
875
876		if (new1 instanceof L.MarkerCluster) {
877			if (!isNotificationFromChild) {
878				this._childClusters.push(new1);
879				new1.__parent = this;
880			}
881			this._childCount += new1._childCount;
882		} else {
883			if (!isNotificationFromChild) {
884				this._markers.push(new1);
885			}
886			this._childCount++;
887		}
888
889		if (this.__parent) {
890			this.__parent._addChild(new1, true);
891		}
892	},
893
894	//Expand our bounds and tell our parent to
895	_expandBounds: function (marker) {
896		var addedCount,
897		    addedLatLng = marker._wLatLng || marker._latlng;
898
899		if (marker instanceof L.MarkerCluster) {
900			this._bounds.extend(marker._bounds);
901			addedCount = marker._childCount;
902		} else {
903			this._bounds.extend(addedLatLng);
904			addedCount = 1;
905		}
906
907		if (!this._cLatLng) {
908			// when clustering, take position of the first point as the cluster center
909			this._cLatLng = marker._cLatLng || addedLatLng;
910		}
911
912		// when showing clusters, take weighted average of all points as cluster center
913		var totalCount = this._childCount + addedCount;
914
915		//Calculate weighted latlng for display
916		if (!this._wLatLng) {
917			this._latlng = this._wLatLng = new L.LatLng(addedLatLng.lat, addedLatLng.lng);
918		} else {
919			this._wLatLng.lat = (addedLatLng.lat * addedCount + this._wLatLng.lat * this._childCount) / totalCount;
920			this._wLatLng.lng = (addedLatLng.lng * addedCount + this._wLatLng.lng * this._childCount) / totalCount;
921		}
922	},
923
924	//Set our markers position as given and add it to the map
925	_addToMap: function (startPos) {
926		if (startPos) {
927			this._backupLatlng = this._latlng;
928			this.setLatLng(startPos);
929		}
930		L.FeatureGroup.prototype.addLayer.call(this._group, this);
931	},
932
933	_recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
934		this._recursively(bounds, 0, maxZoom - 1,
935			function (c) {
936				var markers = c._markers,
937					i, m;
938				for (i = markers.length - 1; i >= 0; i--) {
939					m = markers[i];
940
941					//Only do it if the icon is still on the map
942					if (m._icon) {
943						m._setPos(center);
944						m.setOpacity(0);
945					}
946				}
947			},
948			function (c) {
949				var childClusters = c._childClusters,
950					j, cm;
951				for (j = childClusters.length - 1; j >= 0; j--) {
952					cm = childClusters[j];
953					if (cm._icon) {
954						cm._setPos(center);
955						cm.setOpacity(0);
956					}
957				}
958			}
959		);
960	},
961
962	_recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, previousZoomLevel, newZoomLevel) {
963		this._recursively(bounds, newZoomLevel, 0,
964			function (c) {
965				c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
966
967				//TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
968				//As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
969				if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
970					c.setOpacity(1);
971					c._recursivelyRemoveChildrenFromMap(bounds, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
972				} else {
973					c.setOpacity(0);
974				}
975
976				c._addToMap();
977			}
978		);
979	},
980
981	_recursivelyBecomeVisible: function (bounds, zoomLevel) {
982		this._recursively(bounds, 0, zoomLevel, null, function (c) {
983			c.setOpacity(1);
984		});
985	},
986
987	_recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
988		this._recursively(bounds, -1, zoomLevel,
989			function (c) {
990				if (zoomLevel === c._zoom) {
991					return;
992				}
993
994				//Add our child markers at startPos (so they can be animated out)
995				for (var i = c._markers.length - 1; i >= 0; i--) {
996					var nm = c._markers[i];
997
998					if (!bounds.contains(nm._latlng)) {
999						continue;
1000					}
1001
1002					if (startPos) {
1003						nm._backupLatlng = nm.getLatLng();
1004
1005						nm.setLatLng(startPos);
1006						nm.setOpacity(0);
1007					}
1008
1009					L.FeatureGroup.prototype.addLayer.call(c._group, nm);
1010				}
1011			},
1012			function (c) {
1013				c._addToMap(startPos);
1014			}
1015		);
1016	},
1017
1018	_recursivelyRestoreChildPositions: function (zoomLevel) {
1019		//Fix positions of child markers
1020		for (var i = this._markers.length - 1; i >= 0; i--) {
1021			var nm = this._markers[i];
1022			if (nm._backupLatlng) {
1023				nm.setLatLng(nm._backupLatlng);
1024				delete nm._backupLatlng;
1025			}
1026		}
1027
1028		if (zoomLevel - 1 === this._zoom) {
1029			//Reposition child clusters
1030			for (var j = this._childClusters.length - 1; j >= 0; j--) {
1031				this._childClusters[j]._restorePosition();
1032			}
1033		} else {
1034			for (var k = this._childClusters.length - 1; k >= 0; k--) {
1035				this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel);
1036			}
1037		}
1038	},
1039
1040	_restorePosition: function () {
1041		if (this._backupLatlng) {
1042			this.setLatLng(this._backupLatlng);
1043			delete this._backupLatlng;
1044		}
1045	},
1046
1047	//exceptBounds: If set, don't remove any markers/clusters in it
1048	_recursivelyRemoveChildrenFromMap: function (previousBounds, zoomLevel, exceptBounds) {
1049		var m, i;
1050		this._recursively(previousBounds, -1, zoomLevel - 1,
1051			function (c) {
1052				//Remove markers at every level
1053				for (i = c._markers.length - 1; i >= 0; i--) {
1054					m = c._markers[i];
1055					if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
1056						L.FeatureGroup.prototype.removeLayer.call(c._group, m);
1057						m.setOpacity(1);
1058					}
1059				}
1060			},
1061			function (c) {
1062				//Remove child clusters at just the bottom level
1063				for (i = c._childClusters.length - 1; i >= 0; i--) {
1064					m = c._childClusters[i];
1065					if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
1066						L.FeatureGroup.prototype.removeLayer.call(c._group, m);
1067						m.setOpacity(1);
1068					}
1069				}
1070			}
1071		);
1072	},
1073
1074	//Run the given functions recursively to this and child clusters
1075	// boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
1076	// zoomLevelToStart: zoom level to start running functions (inclusive)
1077	// zoomLevelToStop: zoom level to stop running functions (inclusive)
1078	// runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
1079	// runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
1080	_recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
1081		var childClusters = this._childClusters,
1082		    zoom = this._zoom,
1083			i, c;
1084
1085		if (zoomLevelToStart > zoom) { //Still going down to required depth, just recurse to child clusters
1086			for (i = childClusters.length - 1; i >= 0; i--) {
1087				c = childClusters[i];
1088				if (boundsToApplyTo.intersects(c._bounds)) {
1089					c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
1090				}
1091			}
1092		} else { //In required depth
1093
1094			if (runAtEveryLevel) {
1095				runAtEveryLevel(this);
1096			}
1097			if (runAtBottomLevel && this._zoom === zoomLevelToStop) {
1098				runAtBottomLevel(this);
1099			}
1100
1101			//TODO: This loop is almost the same as above
1102			if (zoomLevelToStop > zoom) {
1103				for (i = childClusters.length - 1; i >= 0; i--) {
1104					c = childClusters[i];
1105					if (boundsToApplyTo.intersects(c._bounds)) {
1106						c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
1107					}
1108				}
1109			}
1110		}
1111	},
1112
1113	_recalculateBounds: function () {
1114		var markers = this._markers,
1115			childClusters = this._childClusters,
1116			i;
1117
1118		this._bounds = new L.LatLngBounds();
1119		delete this._wLatLng;
1120
1121		for (i = markers.length - 1; i >= 0; i--) {
1122			this._expandBounds(markers[i]);
1123		}
1124		for (i = childClusters.length - 1; i >= 0; i--) {
1125			this._expandBounds(childClusters[i]);
1126		}
1127	},
1128
1129
1130	//Returns true if we are the parent of only one cluster and that cluster is the same as us
1131	_isSingleParent: function () {
1132		//Don't need to check this._markers as the rest won't work if there are any
1133		return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount;
1134	}
1135});
1136
1137
1138
1139L.DistanceGrid = function (cellSize) {
1140	this._cellSize = cellSize;
1141	this._sqCellSize = cellSize * cellSize;
1142	this._grid = {};
1143	this._objectPoint = { };
1144};
vendor: 5,849 bytes, lines 1145-1368
1145
1146L.DistanceGrid.prototype = {
1147
1148	addObject: function (obj, point) {
1149		var x = this._getCoord(point.x),
1150		    y = this._getCoord(point.y),
1151		    grid = this._grid,
1152		    row = grid[y] = grid[y] || {},
1153		    cell = row[x] = row[x] || [],
1154		    stamp = L.Util.stamp(obj);
1155
1156		this._objectPoint[stamp] = point;
1157
1158		cell.push(obj);
1159	},
1160
1161	updateObject: function (obj, point) {
1162		this.removeObject(obj);
1163		this.addObject(obj, point);
1164	},
1165
1166	//Returns true if the object was found
1167	removeObject: function (obj, point) {
1168		var x = this._getCoord(point.x),
1169		    y = this._getCoord(point.y),
1170		    grid = this._grid,
1171		    row = grid[y] = grid[y] || {},
1172		    cell = row[x] = row[x] || [],
1173		    i, len;
1174
1175		delete this._objectPoint[L.Util.stamp(obj)];
1176
1177		for (i = 0, len = cell.length; i < len; i++) {
1178			if (cell[i] === obj) {
1179
1180				cell.splice(i, 1);
1181
1182				if (len === 1) {
1183					delete row[x];
1184				}
1185
1186				return true;
1187			}
1188		}
1189
1190	},
1191
1192	eachObject: function (fn, context) {
1193		var i, j, k, len, row, cell, removed,
1194		    grid = this._grid;
1195
1196		for (i in grid) {
1197			if (grid.hasOwnProperty(i)) {
1198				row = grid[i];
1199
1200				for (j in row) {
1201					if (row.hasOwnProperty(j)) {
1202						cell = row[j];
1203
1204						for (k = 0, len = cell.length; k < len; k++) {
1205							removed = fn.call(context, cell[k]);
1206							if (removed) {
1207								k--;
1208								len--;
1209							}
1210						}
1211					}
1212				}
1213			}
1214		}
1215	},
1216
1217	getNearObject: function (point) {
1218		var x = this._getCoord(point.x),
1219		    y = this._getCoord(point.y),
1220		    i, j, k, row, cell, len, obj, dist,
1221		    objectPoint = this._objectPoint,
1222		    closestDistSq = this._sqCellSize,
1223		    closest = null;
1224
1225		for (i = y - 1; i <= y + 1; i++) {
1226			row = this._grid[i];
1227			if (row) {
1228
1229				for (j = x - 1; j <= x + 1; j++) {
1230					cell = row[j];
1231					if (cell) {
1232
1233						for (k = 0, len = cell.length; k < len; k++) {
1234							obj = cell[k];
1235							dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
1236							if (dist < closestDistSq) {
1237								closestDistSq = dist;
1238								closest = obj;
1239							}
1240						}
1241					}
1242				}
1243			}
1244		}
1245		return closest;
1246	},
1247
1248	_getCoord: function (x) {
1249		return Math.floor(x / this._cellSize);
1250	},
1251
1252	_sqDist: function (p, p2) {
1253		var dx = p2.x - p.x,
1254		    dy = p2.y - p.y;
1255		return dx * dx + dy * dy;
1256	}
1257};
1258
1259
1260/* Copyright (c) 2012 the authors listed at the following URL, and/or
1261the authors of referenced articles or incorporated external code:
1262http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
1263
1264Permission is hereby granted, free of charge, to any person obtaining
1265a copy of this software and associated documentation files (the
1266"Software"), to deal in the Software without restriction, including
1267without limitation the rights to use, copy, modify, merge, publish,
1268distribute, sublicense, and/or sell copies of the Software, and to
1269permit persons to whom the Software is furnished to do so, subject to
1270the following conditions:
1271
1272The above copyright notice and this permission notice shall be
1273included in all copies or substantial portions of the Software.
1274
1275THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
1276EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
1277MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
1278IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
1279CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
1280TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
1281SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1282
1283Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
1284*/
1285
1286(function () {
1287	L.QuickHull = {
1288		getDistant: function (cpt, bl) {
1289			var vY = bl[1].lat - bl[0].lat,
1290				vX = bl[0].lng - bl[1].lng;
1291			return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng));
1292		},
1293
1294
1295		findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
1296			var maxD = 0,
1297				maxPt = null,
1298				newPoints = [],
1299				i, pt, d;
1300
1301			for (i = latLngs.length - 1; i >= 0; i--) {
1302				pt = latLngs[i];
1303				d = this.getDistant(pt, baseLine);
1304
1305				if (d > 0) {
1306					newPoints.push(pt);
1307				} else {
1308					continue;
1309				}
1310
1311				if (d > maxD) {
1312					maxD = d;
1313					maxPt = pt;
1314				}
1315
1316			}
1317			return { 'maxPoint': maxPt, 'newPoints': newPoints };
1318		},
1319
1320		buildConvexHull: function (baseLine, latLngs) {
1321			var convexHullBaseLines = [],
1322				t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
1323
1324			if (t.maxPoint) { // if there is still a point "outside" the base line
1325				convexHullBaseLines =
1326					convexHullBaseLines.concat(
1327						this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
1328					);
1329				convexHullBaseLines =
1330					convexHullBaseLines.concat(
1331						this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
1332					);
1333				return convexHullBaseLines;
1334			} else {  // if there is no more point "outside" the base line, the current base line is part of the convex hull
1335				return [baseLine];
1336			}
1337		},
1338
1339		getConvexHull: function (latLngs) {
1340			//find first baseline
1341			var maxLat = false, minLat = false,
1342				maxPt = null, minPt = null,
1343				i;
1344
1345			for (i = latLngs.length - 1; i >= 0; i--) {
1346				var pt = latLngs[i];
1347				if (maxLat === false || pt.lat > maxLat) {
1348					maxPt = pt;
1349					maxLat = pt.lat;
1350				}
1351				if (minLat === false || pt.lat < minLat) {
1352					minPt = pt;
1353					minLat = pt.lat;
1354				}
1355			}
1356			var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
1357								this.buildConvexHull([maxPt, minPt], latLngs));
1358			return ch;
1359		}
1360	};
1361}());
1362
1363L.MarkerCluster.include({
1364	getConvexHull: function () {
1365		var childMarkers = this.getAllChildMarkers(),
1366			points = [],
1367			hullLatLng = [],
1368			hull, p, i;
1369
1370		for (i = childMarkers.length - 1; i >= 0; i--) {
1371			p = childMarkers[i].getLatLng();
1372			points.push(p);
1373		}
1374
1375		hull = L.QuickHull.getConvexHull(points);
1376
1377		for (i = hull.length - 1; i >= 0; i--) {
1378			hullLatLng.push(hull[i][0]);
1379		}
1380
1381		return hullLatLng;
1382	}
1383});
1384
1385//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
1386//Huge thanks to jawj for implementing it first to make my job easy :-)
1387
1388L.MarkerCluster.include({
1389
1390	_2PI: Math.PI * 2,
1391	_circleFootSeparation: 25, //related to circumference of circle
1392	_circleStartAngle: Math.PI / 6,
1393
1394	_spiralFootSeparation:  28, //related to size of spiral (experiment!)
1395	_spiralLengthStart: 11,
1396	_spiralLengthFactor: 5,
1397
1398	_circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
1399								// 0 -> always spiral; Infinity -> always circle
1400
1401	spiderfy: function () {
1402		if (this._group._spiderfied === this || this._group._inZoomAnimation) {
1403			return;
1404		}
1405
1406		var childMarkers = this.getAllChildMarkers(),
1407			group = this._group,
1408			map = group._map,
1409			center = map.latLngToLayerPoint(this._latlng),
1410			positions;
1411
1412		this._group._unspiderfy();
1413		this._group._spiderfied = this;
1414
1415		//TODO Maybe: childMarkers order by distance to center
1416
1417		if (childMarkers.length >= this._circleSpiralSwitchover) {
1418			positions = this._generatePointsSpiral(childMarkers.length, center);
1419		} else {
1420			center.y += 10; //Otherwise circles look wrong
1421			positions = this._generatePointsCircle(childMarkers.length, center);
1422		}
1423
1424		this._animationSpiderfy(childMarkers, positions);
1425	},
1426
1427	unspiderfy: function (zoomDetails) {
1428		/// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
1429		if (this._group._inZoomAnimation) {
1430			return;
1431		}
1432		this._animationUnspiderfy(zoomDetails);
1433
1434		this._group._spiderfied = null;
1435	},
1436
1437	_generatePointsCircle: function (count, centerPt) {
1438		var circumference = this._circleFootSeparation * (2 + count),
1439			legLength = circumference / this._2PI,  //radius from circumference
1440			angleStep = this._2PI / count,
1441			res = [],
1442			i, angle;
1443
1444		res.length = count;
1445
1446		for (i = count - 1; i >= 0; i--) {
1447			angle = this._circleStartAngle + i * angleStep;
1448			res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
1449		}
1450
1451		return res;
1452	},
1453
1454	_generatePointsSpiral: function (count, centerPt) {
1455		var legLength = this._spiralLengthStart,
1456			angle = 0,
1457			res = [],
1458			i;
1459
1460		res.length = count;
1461
1462		for (i = count - 1; i >= 0; i--) {
1463			angle += this._spiralFootSeparation / legLength + i * 0.0005;
1464			res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
1465			legLength += this._2PI * this._spiralLengthFactor / angle;
1466		}
1467		return res;
1468	}
1469});
1470
1471L.MarkerCluster.include(!L.DomUtil.TRANSITION ? {
1472	//Non Animated versions of everything
1473	_animationSpiderfy: function (childMarkers, positions) {
1474		var group = this._group,
1475			map = group._map,
1476			i, m, leg, newPos;
1477
1478		for (i = childMarkers.length - 1; i >= 0; i--) {
1479			newPos = map.layerPointToLatLng(positions[i]);
1480			m = childMarkers[i];
1481
1482			m._preSpiderfyLatlng = m._latlng;
1483			m.setLatLng(newPos);
1484			m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
1485
1486			L.FeatureGroup.prototype.addLayer.call(group, m);
1487
1488
1489			leg = new L.Polyline([this._latlng, newPos], { weight: 1.5, color: '#222' });
1490			map.addLayer(leg);
1491			m._spiderLeg = leg;
1492		}
1493		//this.setOpacity(0);
1494		this.setOpacity(0.3);
1495		group.fire('spiderfied');
1496	},
1497
1498	_animationUnspiderfy: function () {
1499		var group = this._group,
1500			map = group._map,
1501			childMarkers = this.getAllChildMarkers(),
1502			m, i;
1503
1504		this.setOpacity(1);
1505		for (i = childMarkers.length - 1; i >= 0; i--) {
1506			m = childMarkers[i];
1507
1508			L.FeatureGroup.prototype.removeLayer.call(group, m);
1509
1510			m.setLatLng(m._preSpiderfyLatlng);
1511			delete m._preSpiderfyLatlng;
1512			m.setZIndexOffset(0);
1513
1514			map.removeLayer(m._spiderLeg);
1515			delete m._spiderLeg;
1516		}
1517	}
1518} : {
1519	//Animated versions here
1520	_animationSpiderfy: function (childMarkers, positions) {
1521		var me = this,
1522			group = this._group,
1523			map = group._map,
1524			thisLayerPos = map.latLngToLayerPoint(this._latlng),
1525			i, m, leg, newPos;
1526
1527		//Add markers to map hidden at our center point
1528		for (i = childMarkers.length - 1; i >= 0; i--) {
1529			m = childMarkers[i];
1530
1531			m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
1532			m.setOpacity(0);
1533
1534			L.FeatureGroup.prototype.addLayer.call(group, m);
1535
1536			m._setPos(thisLayerPos);
1537		}
1538
1539		group._forceLayout();
1540		group._animationStart();
1541
1542		var initialLegOpacity = L.Path.SVG ? 0 : 0.3,
1543			xmlns = L.Path.SVG_NS;
1544
1545
1546		for (i = childMarkers.length - 1; i >= 0; i--) {
1547			newPos = map.layerPointToLatLng(positions[i]);
1548			m = childMarkers[i];
1549
1550			//Move marker to new position
1551			m._preSpiderfyLatlng = m._latlng;
1552			m.setLatLng(newPos);
1553			m.setOpacity(1);
1554
1555
1556			//Add Legs.
1557			leg = new L.Polyline([me._latlng, newPos], { weight: 1.5, color: '#222', opacity: initialLegOpacity });
1558			map.addLayer(leg);
1559			m._spiderLeg = leg;
1560
1561			//Following animations don't work for canvas
1562			if (!L.Path.SVG) {
1563				continue;
1564			}
1565
1566			//How this works:
1567			//http://stackoverflow.com/questions/5924238/how-do-you-animate-an-svg-path-in-ios
1568			//http://dev.opera.com/articles/view/advanced-svg-animation-techniques/
1569
1570			//Animate length
1571			var length = leg._path.getTotalLength();
1572			leg._path.setAttribute("stroke-dasharray", length + "," + length);
1573
1574			var anim = document.createElementNS(xmlns, "animate");
1575			anim.setAttribute("attributeName", "stroke-dashoffset");
1576			anim.setAttribute("begin", "indefinite");
1577			anim.setAttribute("from", length);
1578			anim.setAttribute("to", 0);
1579			anim.setAttribute("dur", 0.25);
1580			leg._path.appendChild(anim);
1581			anim.beginElement();
1582
1583			//Animate opacity
1584			anim = document.createElementNS(xmlns, "animate");
1585			anim.setAttribute("attributeName", "stroke-opacity");
1586			anim.setAttribute("attributeName", "stroke-opacity");
1587			anim.setAttribute("begin", "indefinite");
1588			anim.setAttribute("from", 0);
1589			anim.setAttribute("to", 0.5);
1590			anim.setAttribute("dur", 0.25);
1591			leg._path.appendChild(anim);
1592			anim.beginElement();
1593		}
1594		//me.setOpacity(0);
1595        me.setOpacity(0.3);
1596		//Set the opacity of the spiderLegs back to their correct value
1597		// The animations above override this until they complete.
1598		// If the initial opacity of the spiderlegs isn't 0 then they appear before the animation starts.
1599		if (L.Path.SVG) {
1600			this._group._forceLayout();
1601
1602			for (i = childMarkers.length - 1; i >= 0; i--) {
1603				m = childMarkers[i]._spiderLeg;
1604
1605				m.options.opacity = 0.5;
1606				m._path.setAttribute('stroke-opacity', 0.5);
1607			}
1608		}
1609
1610		setTimeout(function () {
1611			group._animationEnd();
1612			group.fire('spiderfied');
1613		}, 250);
1614	},
1615
1616	_animationUnspiderfy: function (zoomDetails) {
1617		var group = this._group,
1618			map = group._map,
1619			thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
1620			childMarkers = this.getAllChildMarkers(),
1621			svg = L.Path.SVG,
1622			m, i, a;
1623
1624		group._animationStart();
1625
1626		//Make us visible and bring the child markers back in
1627		this.setOpacity(1);
1628		for (i = childMarkers.length - 1; i >= 0; i--) {
1629			m = childMarkers[i];
1630
1631			//Fix up the location to the real one
1632			m.setLatLng(m._preSpiderfyLatlng);
1633			delete m._preSpiderfyLatlng;
1634			//Hack override the location to be our center
1635			m._setPos(thisLayerPos);
1636
1637			m.setOpacity(0);
1638
1639			//Animate the spider legs back in
1640			if (svg) {
1641				a = m._spiderLeg._path.childNodes[0];
1642				a.setAttribute('to', a.getAttribute('from'));
1643				a.setAttribute('from', 0);
1644				a.beginElement();
1645
1646				a = m._spiderLeg._path.childNodes[1];
1647				a.setAttribute('from', 0.5);
1648				a.setAttribute('to', 0);
1649				a.setAttribute('stroke-opacity', 0);
1650				a.beginElement();
1651
1652				m._spiderLeg._path.setAttribute('stroke-opacity', 0);
1653			}
1654		}
1655
1656		setTimeout(function () {
1657			//If we have only <= one child left then that marker will be shown on the map so don't remove it!
1658			var stillThereChildCount = 0;
1659			for (i = childMarkers.length - 1; i >= 0; i--) {
1660				m = childMarkers[i];
1661				if (m._spiderLeg) {
1662					stillThereChildCount++;
1663				}
1664			}
1665
1666
1667			for (i = childMarkers.length - 1; i >= 0; i--) {
1668				m = childMarkers[i];
1669
1670				if (!m._spiderLeg) { //Has already been unspiderfied
1671					continue;
1672				}
1673
1674
1675				m.setOpacity(1);
1676				m.setZIndexOffset(0);
1677
1678				if (stillThereChildCount > 1) {
1679					L.FeatureGroup.prototype.removeLayer.call(group, m);
1680				}
1681
1682				map.removeLayer(m._spiderLeg);
1683				delete m._spiderLeg;
1684			}
1685			group._animationEnd();
1686		}, 250);
1687	}
1688});
1689
1690
1691L.MarkerClusterGroup.include({
1692	//The MarkerCluster currently spiderfied (if any)
1693	_spiderfied: null,
1694
1695	_spiderfierOnAdd: function () {
1696		this._map.on('click', this._unspiderfyWrapper, this);
1697
1698		if (this._map.options.zoomAnimation) {
1699			this._map.on('zoomstart', this._unspiderfyZoomStart, this);
1700		} else {
1701			//Browsers without zoomAnimation don't fire zoomstart
1702			this._map.on('zoomend', this._unspiderfyWrapper, this);
1703		}
1704
1705		if (L.Path.SVG && !L.Browser.touch) {
1706			this._map._initPathRoot();
1707			//Needs to happen in the pageload, not after, or animations don't work in webkit
1708			//  http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
1709			//Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
1710		}
1711	},
1712
1713	_spiderfierOnRemove: function () {
1714		this._map.off('click', this._unspiderfyWrapper, this);
1715		this._map.off('zoomstart', this._unspiderfyZoomStart, this);
1716		this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
1717
1718		this._unspiderfy(); //Ensure that markers are back where they should be
1719	},
1720
1721
1722	//On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
1723	//This means we can define the animation they do rather than Markers doing an animation to their actual location
1724	_unspiderfyZoomStart: function () {
1725		if (!this._map) { //May have been removed from the map by a zoomEnd handler
1726			return;
1727		}
1728
1729		this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
1730	},
1731	_unspiderfyZoomAnim: function (zoomDetails) {
1732		//Wait until the first zoomanim after the user has finished touch-zooming before running the animation
1733		if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
1734			return;
1735		}
1736
1737		this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
1738		this._unspiderfy(zoomDetails);
1739	},
1740
1741
1742	_unspiderfyWrapper: function () {
1743		/// <summary>_unspiderfy but passes no arguments</summary>
1744		this._unspiderfy();
1745	},
1746
1747	_unspiderfy: function (zoomDetails) {
1748		if (this._spiderfied) {
1749			this._spiderfied.unspiderfy(zoomDetails);
1750		}
1751	},
1752
1753	//If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
1754	_unspiderfyLayer: function (layer) {
1755		if (layer._spiderLeg) {
1756			L.FeatureGroup.prototype.removeLayer.call(this, layer);
1757
1758			layer.setOpacity(1);
1759			//Position will be fixed up immediately in _animationUnspiderfy
1760			layer.setZIndexOffset(0);
1761
1762			this._map.removeLayer(layer._spiderLeg);
1763			delete layer._spiderLeg;
1764		}
1765	}
1766});
1767
1768
1769
1770}(this));

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.