PageSourceSearch

https://sbschool.org/js/maps/leaflet/leaflet.markercluster-src.js

js sbschool.org collected 2026-10-03 21:38:29 UTC 62,681 bytes, 2,163 lines download raw bytes

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