PageSourceSearch

https://geerassociation.org/components/com_geer_reports/assets/js/leaflet.markercluster-src.js?702e53

js geerassociation.org collected 2026-10-01 12:13:14 UTC 60,545 bytes, 2,165 lines download raw bytes

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