PageSourceSearch

https://fibmarket.ru/wp-content/themes/classiera/js/classiera-map.js?ver=5.2.21

js fibmarket.ru collected 2026-10-08 09:48:44 UTC 456,095 bytes, 16,159 lines download raw bytes

1/*
2 Leaflet 1.0.1, a JS library for interactive maps. http://leafletjs.com
3 (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4*/
5(function (window, document, undefined) {
6var L = {
7	version: "1.0.1"
8};
9
10function expose() {
11	var oldL = window.L;
12
13	L.noConflict = function () {
14		window.L = oldL;
15		return this;
16	};
17
18	window.L = L;
19}
20
21// define Leaflet for Node module pattern loaders, including Browserify
22if (typeof module === 'object' && typeof module.exports === 'object') {
23	module.exports = L;
24
25// define Leaflet as an AMD module
26} else if (typeof define === 'function' && define.amd) {
27	define(L);
28}
29
30// define Leaflet as a global L variable, saving the original L to restore later if needed
31if (typeof window !== 'undefined') {
32	expose();
33}
34
35
36
37/*
38 * @namespace Util
39 *
40 * Various utility functions, used by Leaflet internally.
41 */
42
43L.Util = {
44
45	// @function extend(dest: Object, src?: Object): Object
46	// Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
47	extend: function (dest) {
48		var i, j, len, src;
49
50		for (j = 1, len = arguments.length; j < len; j++) {
51			src = arguments[j];
52			for (i in src) {
53				dest[i] = src[i];
54			}
55		}
56		return dest;
57	},
58
59	// @function create(proto: Object, properties?: Object): Object
60	// Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
61	create: Object.create || (function () {
62		function F() {}
63		return function (proto) {
64			F.prototype = proto;
65			return new F();
66		};
67	})(),
68
69	// @function bind(fn: Function, …): Function
70	// Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
71	// Has a `L.bind()` shortcut.
72	bind: function (fn, obj) {
73		var slice = Array.prototype.slice;
74
75		if (fn.bind) {
76			return fn.bind.apply(fn, slice.call(arguments, 1));
77		}
78
79		var args = slice.call(arguments, 2);
80
81		return function () {
82			return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
83		};
84	},
85
86	// @function stamp(obj: Object): Number
87	// Returns the unique ID of an object, assiging it one if it doesn't have it.
88	stamp: function (obj) {
89		/*eslint-disable */
90		obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
91		return obj._leaflet_id;
92		/*eslint-enable */
93	},
94
95	// @property lastId: Number
96	// Last unique ID used by [`stamp()`](#util-stamp)
97	lastId: 0,
98
99	// @function throttle(fn: Function, time: Number, context: Object): Function
100	// Returns a function which executes function `fn` with the given scope `context`
101	// (so that the `this` keyword refers to `context` inside `fn`'s code). The function
102	// `fn` will be called no more than one time per given amount of `time`. The arguments
103	// received by the bound function will be any arguments passed when binding the
104	// function, followed by any arguments passed when invoking the bound function.
105	// Has an `L.bind` shortcut.
106	throttle: function (fn, time, context) {
107		var lock, args, wrapperFn, later;
108
109		later = function () {
110			// reset lock and call if queued
111			lock = false;
112			if (args) {
113				wrapperFn.apply(context, args);
114				args = false;
115			}
116		};
117
118		wrapperFn = function () {
119			if (lock) {
120				// called too soon, queue to call later
121				args = arguments;
122
123			} else {
124				// call and lock until later
125				fn.apply(context, arguments);
126				setTimeout(later, time);
127				lock = true;
128			}
129		};
130
131		return wrapperFn;
132	},
133
134	// @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
135	// Returns the number `num` modulo `range` in such a way so it lies within
136	// `range[0]` and `range[1]`. The returned value will be always smaller than
137	// `range[1]` unless `includeMax` is set to `true`.
138	wrapNum: function (x, range, includeMax) {
139		var max = range[1],
140		    min = range[0],
141		    d = max - min;
142		return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
143	},
144
145	// @function falseFn(): Function
146	// Returns a function which always returns `false`.
147	falseFn: function () { return false; },
148
149	// @function formatNum(num: Number, digits?: Number): Number
150	// Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
151	formatNum: function (num, digits) {
152		var pow = Math.pow(10, digits || 5);
153		return Math.round(num * pow) / pow;
154	},
155
156	// @function trim(str: String): String
157	// Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
158	trim: function (str) {
159		return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
160	},
161
162	// @function splitWords(str: String): String[]
163	// Trims and splits the string on whitespace and returns the array of parts.
164	splitWords: function (str) {
165		return L.Util.trim(str).split(/\s+/);
166	},
167
168	// @function setOptions(obj: Object, options: Object): Object
169	// Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
170	setOptions: function (obj, options) {
171		if (!obj.hasOwnProperty('options')) {
172			obj.options = obj.options ? L.Util.create(obj.options) : {};
173		}
174		for (var i in options) {
175			obj.options[i] = options[i];
176		}
177		return obj.options;
178	},
179
180	// @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
181	// Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
182	// translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
183	// be appended at the end. If `uppercase` is `true`, the parameter names will
184	// be uppercased (e.g. `'?A=foo&B=bar'`)
185	getParamString: function (obj, existingUrl, uppercase) {
186		var params = [];
187		for (var i in obj) {
188			params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
189		}
190		return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
191	},
192
193	// @function template(str: String, data: Object): String
194	// Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
195	// and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
196	// `('Hello foo, bar')`. You can also specify functions instead of strings for
197	// data values — they will be evaluated passing `data` as an argument.
198	template: function (str, data) {
199		return str.replace(L.Util.templateRe, function (str, key) {
200			var value = data[key];
201
202			if (value === undefined) {
203				throw new Error('No value provided for variable ' + str);
204
205			} else if (typeof value === 'function') {
206				value = value(data);
207			}
208			return value;
209		});
210	},
211
212	templateRe: /\{ *([\w_\-]+) *\}/g,
213
214	// @function isArray(obj): Boolean
215	// Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
216	isArray: Array.isArray || function (obj) {
217		return (Object.prototype.toString.call(obj) === '[object Array]');
218	},
219
220	// @function indexOf(array: Array, el: Object): Number
221	// Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
222	indexOf: function (array, el) {
223		for (var i = 0; i < array.length; i++) {
224			if (array[i] === el) { return i; }
225		}
226		return -1;
227	},
228
229	// @property emptyImageUrl: String
230	// Data URI string containing a base64-encoded empty GIF image.
231	// Used as a hack to free memory from unused images on WebKit-powered
232	// mobile devices (by setting image `src` to this string).
233	emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
234};
235
236(function () {
237	// inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
238
239	function getPrefixed(name) {
240		return window['webkit' + name] || window['moz' + name] || window['ms' + name];
241	}
242
243	var lastTime = 0;
244
245	// fallback for IE 7-8
246	function timeoutDefer(fn) {
247		var time = +new Date(),
248		    timeToCall = Math.max(0, 16 - (time - lastTime));
249
250		lastTime = time + timeToCall;
251		return window.setTimeout(fn, timeToCall);
252	}
253
254	var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255	    cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
256	               getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
257
258
259	// @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
260	// Schedules `fn` to be executed when the browser repaints. `fn` is bound to
261	// `context` if given. When `immediate` is set, `fn` is called immediately if
262	// the browser doesn't have native support for
263	// [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
264	// otherwise it's delayed. Returns a request ID that can be used to cancel the request.
265	L.Util.requestAnimFrame = function (fn, context, immediate) {
266		if (immediate && requestFn === timeoutDefer) {
267			fn.call(context);
268		} else {
269			return requestFn.call(window, L.bind(fn, context));
270		}
271	};
272
273	// @function cancelAnimFrame(id: Number): undefined
274	// Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
275	L.Util.cancelAnimFrame = function (id) {
276		if (id) {
277			cancelFn.call(window, id);
278		}
279	};
280})();
281
282// shortcuts for most used utility functions
283L.extend = L.Util.extend;
284L.bind = L.Util.bind;
285L.stamp = L.Util.stamp;
286L.setOptions = L.Util.setOptions;
287
288
289
290
291// @class Class
292// @aka L.Class
293
294// @section
295// @uninheritable
296
297// Thanks to John Resig and Dean Edwards for inspiration!
298
299L.Class = function () {};
300
301L.Class.extend = function (props) {
302
303	// @function extend(props: Object): Function
304	// [Extends the current class](#class-inheritance) given the properties to be included.
305	// Returns a Javascript function that is a class constructor (to be called with `new`).
306	var NewClass = function () {
307
308		// call the constructor
309		if (this.initialize) {
310			this.initialize.apply(this, arguments);
311		}
312
313		// call all constructor hooks
314		this.callInitHooks();
315	};
316
317	var parentProto = NewClass.__super__ = this.prototype;
318
319	var proto = L.Util.create(parentProto);
320	proto.constructor = NewClass;
321
322	NewClass.prototype = proto;
323
324	// inherit parent's statics
325	for (var i in this) {
326		if (this.hasOwnProperty(i) && i !== 'prototype') {
327			NewClass[i] = this[i];
328		}
329	}
330
331	// mix static properties into the class
332	if (props.statics) {
333		L.extend(NewClass, props.statics);
334		delete props.statics;
335	}
336
337	// mix includes into the prototype
338	if (props.includes) {
339		L.Util.extend.apply(null, [proto].concat(props.includes));
340		delete props.includes;
341	}
342
343	// merge options
344	if (proto.options) {
345		props.options = L.Util.extend(L.Util.create(proto.options), props.options);
346	}
347
348	// mix given properties into the prototype
349	L.extend(proto, props);
350
351	proto._initHooks = [];
352
353	// add method for calling all hooks
354	proto.callInitHooks = function () {
355
356		if (this._initHooksCalled) { return; }
357
358		if (parentProto.callInitHooks) {
359			parentProto.callInitHooks.call(this);
360		}
361
362		this._initHooksCalled = true;
363
364		for (var i = 0, len = proto._initHooks.length; i < len; i++) {
365			proto._initHooks[i].call(this);
366		}
367	};
368
369	return NewClass;
370};
371
372
373// @function include(properties: Object): this
374// [Includes a mixin](#class-includes) into the current class.
375L.Class.include = function (props) {
376	L.extend(this.prototype, props);
377	return this;
378};
379
380// @function mergeOptions(options: Object): this
381// [Merges `options`](#class-options) into the defaults of the class.
382L.Class.mergeOptions = function (options) {
383	L.extend(this.prototype.options, options);
384	return this;
385};
386
387// @function addInitHook(fn: Function): this
388// Adds a [constructor hook](#class-constructor-hooks) to the class.
389L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
390	var args = Array.prototype.slice.call(arguments, 1);
391
392	var init = typeof fn === 'function' ? fn : function () {
393		this[fn].apply(this, args);
394	};
395
396	this.prototype._initHooks = this.prototype._initHooks || [];
397	this.prototype._initHooks.push(init);
398	return this;
399};
400
401
402
403/*
404 * @class Evented
405 * @aka L.Evented
406 * @inherits Class
407 *
408 * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
409 *
410 * @example
411 *
412 * ```js
413 * map.on('click', function(e) {
414 * 	alert(e.latlng);
415 * } );
416 * ```
417 *
418 * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
419 *
420 * ```js
421 * function onClick(e) { ... }
422 *
423 * map.on('click', onClick);
424 * map.off('click', onClick);
425 * ```
426 */
427
428
429L.Evented = L.Class.extend({
430
431	/* @method on(type: String, fn: Function, context?: Object): this
432	 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
433	 *
434	 * @alternative
435	 * @method on(eventMap: Object): this
436	 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
437	 */
438	on: function (types, fn, context) {
439
440		// types can be a map of types/handlers
441		if (typeof types === 'object') {
442			for (var type in types) {
443				// we don't process space-separated events here for performance;
444				// it's a hot path since Layer uses the on(obj) syntax
445				this._on(type, types[type], fn);
446			}
447
448		} else {
449			// types can be a string of space-separated words
450			types = L.Util.splitWords(types);
451
452			for (var i = 0, len = types.length; i < len; i++) {
453				this._on(types[i], fn, context);
454			}
455		}
456
457		return this;
458	},
459
460	/* @method off(type: String, fn?: Function, context?: Object): this
461	 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
462	 *
463	 * @alternative
464	 * @method off(eventMap: Object): this
465	 * Removes a set of type/listener pairs.
466	 *
467	 * @alternative
468	 * @method off: this
469	 * Removes all listeners to all events on the object.
470	 */
471	off: function (types, fn, context) {
472
473		if (!types) {
474			// clear all listeners if called without arguments
475			delete this._events;
476
477		} else if (typeof types === 'object') {
478			for (var type in types) {
479				this._off(type, types[type], fn);
480			}
481
482		} else {
483			types = L.Util.splitWords(types);
484
485			for (var i = 0, len = types.length; i < len; i++) {
486				this._off(types[i], fn, context);
487			}
488		}
489
490		return this;
491	},
492
493	// attach listener (without syntactic sugar now)
494	_on: function (type, fn, context) {
495		this._events = this._events || {};
496
497		/* get/init listeners for type */
498		var typeListeners = this._events[type];
499		if (!typeListeners) {
500			typeListeners = [];
501			this._events[type] = typeListeners;
502		}
503
504		if (context === this) {
505			// Less memory footprint.
506			context = undefined;
507		}
508		var newListener = {fn: fn, ctx: context},
509		    listeners = typeListeners;
510
511		// check if fn already there
512		for (var i = 0, len = listeners.length; i < len; i++) {
513			if (listeners[i].fn === fn && listeners[i].ctx === context) {
514				return;
515			}
516		}
517
518		listeners.push(newListener);
519		typeListeners.count++;
520	},
521
522	_off: function (type, fn, context) {
523		var listeners,
524		    i,
525		    len;
526
527		if (!this._events) { return; }
528
529		listeners = this._events[type];
530
531		if (!listeners) {
532			return;
533		}
534
535		if (!fn) {
536			// Set all removed listeners to noop so they are not called if remove happens in fire
537			for (i = 0, len = listeners.length; i < len; i++) {
538				listeners[i].fn = L.Util.falseFn;
539			}
540			// clear all listeners for a type if function isn't specified
541			delete this._events[type];
542			return;
543		}
544
545		if (context === this) {
546			context = undefined;
547		}
548
549		if (listeners) {
550
551			// find fn and remove it
552			for (i = 0, len = listeners.length; i < len; i++) {
553				var l = listeners[i];
554				if (l.ctx !== context) { continue; }
555				if (l.fn === fn) {
556
557					// set the removed listener to noop so that's not called if remove happens in fire
558					l.fn = L.Util.falseFn;
559
560					if (this._firingCount) {
561						/* copy array in case events are being fired */
562						this._events[type] = listeners = listeners.slice();
563					}
564					listeners.splice(i, 1);
565
566					return;
567				}
568			}
569		}
570	},
571
572	// @method fire(type: String, data?: Object, propagate?: Boolean): this
573	// Fires an event of the specified type. You can optionally provide an data
574	// object — the first argument of the listener function will contain its
575	// properties. The event might can optionally be propagated to event parents.
576	fire: function (type, data, propagate) {
577		if (!this.listens(type, propagate)) { return this; }
578
579		var event = L.Util.extend({}, data, {type: type, target: this});
580
581		if (this._events) {
582			var listeners = this._events[type];
583
584			if (listeners) {
585				this._firingCount = (this._firingCount + 1) || 1;
586				for (var i = 0, len = listeners.length; i < len; i++) {
587					var l = listeners[i];
588					l.fn.call(l.ctx || this, event);
589				}
590
591				this._firingCount--;
592			}
593		}
594
595		if (propagate) {
596			// propagate the event to parents (set with addEventParent)
597			this._propagateEvent(event);
598		}
599
600		return this;
601	},
602
603	// @method listens(type: String): Boolean
604	// Returns `true` if a particular event type has any listeners attached to it.
605	listens: function (type, propagate) {
606		var listeners = this._events && this._events[type];
607		if (listeners && listeners.length) { return true; }
608
609		if (propagate) {
610			// also check parents for listeners if event propagates
611			for (var id in this._eventParents) {
612				if (this._eventParents[id].listens(type, propagate)) { return true; }
613			}
614		}
615		return false;
616	},
617
618	// @method once(…): this
619	// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620	once: function (types, fn, context) {
621
622		if (typeof types === 'object') {
623			for (var type in types) {
624				this.once(type, types[type], fn);
625			}
626			return this;
627		}
628
629		var handler = L.bind(function () {
630			this
631			    .off(types, fn, context)
632			    .off(types, handler, context);
633		}, this);
634
635		// add a listener that's executed once and removed after that
636		return this
637		    .on(types, fn, context)
638		    .on(types, handler, context);
639	},
640
641	// @method addEventParent(obj: Evented): this
642	// Adds an event parent - an `Evented` that will receive propagated events
643	addEventParent: function (obj) {
644		this._eventParents = this._eventParents || {};
645		this._eventParents[L.stamp(obj)] = obj;
646		return this;
647	},
648
649	// @method removeEventParent(obj: Evented): this
650	// Removes an event parent, so it will stop receiving propagated events
651	removeEventParent: function (obj) {
652		if (this._eventParents) {
653			delete this._eventParents[L.stamp(obj)];
654		}
655		return this;
656	},
657
658	_propagateEvent: function (e) {
659		for (var id in this._eventParents) {
660			this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
661		}
662	}
663});
664
665var proto = L.Evented.prototype;
666
667// aliases; we should ditch those eventually
668
669// @method addEventListener(…): this
670// Alias to [`on(…)`](#evented-on)
671proto.addEventListener = proto.on;
672
673// @method removeEventListener(…): this
674// Alias to [`off(…)`](#evented-off)
675
676// @method clearAllEventListeners(…): this
677// Alias to [`off()`](#evented-off)
678proto.removeEventListener = proto.clearAllEventListeners = proto.off;
679
680// @method addOneTimeEventListener(…): this
681// Alias to [`once(…)`](#evented-once)
682proto.addOneTimeEventListener = proto.once;
683
684// @method fireEvent(…): this
685// Alias to [`fire(…)`](#evented-fire)
686proto.fireEvent = proto.fire;
687
688// @method hasEventListeners(…): Boolean
689// Alias to [`listens(…)`](#evented-listens)
690proto.hasEventListeners = proto.listens;
691
692L.Mixin = {Events: proto};
693
694
695
696/*
697 * @namespace Browser
698 * @aka L.Browser
699 *
700 * A namespace with static properties for browser/feature detection used by Leaflet internally.
701 *
702 * @example
703 *
704 * ```js
705 * if (L.Browser.ielt9) {
706 *   alert('Upgrade your browser, dude!');
707 * }
708 * ```
709 */
710
711(function () {
712
713	var ua = navigator.userAgent.toLowerCase(),
714	    doc = document.documentElement,
715
716	    ie = 'ActiveXObject' in window,
717
718	    webkit    = ua.indexOf('webkit') !== -1,
719	    phantomjs = ua.indexOf('phantom') !== -1,
720	    android23 = ua.search('android [23]') !== -1,
721	    chrome    = ua.indexOf('chrome') !== -1,
722	    gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,
723
724	    win = navigator.platform.indexOf('Win') === 0,
725
726	    mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
727	    msPointer = !window.PointerEvent && window.MSPointerEvent,
728	    pointer = window.PointerEvent || msPointer,
729
730	    ie3d = ie && ('transition' in doc.style),
731	    webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
732	    gecko3d = 'MozPerspective' in doc.style,
733	    opera12 = 'OTransition' in doc.style;
734
735
736	var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
737			(window.DocumentTouch && document instanceof window.DocumentTouch));
738
739	L.Browser = {
740
741		// @property ie: Boolean
742		// `true` for all Internet Explorer versions (not Edge).
743		ie: ie,
744
745		// @property ielt9: Boolean
746		// `true` for Internet Explorer versions less than 9.
747		ielt9: ie && !document.addEventListener,
748
749		// @property edge: Boolean
750		// `true` for the Edge web browser.
751		edge: 'msLaunchUri' in navigator && !('documentMode' in document),
752
753		// @property webkit: Boolean
754		// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
755		webkit: webkit,
756
757		// @property gecko: Boolean
758		// `true` for gecko-based browsers like Firefox.
759		gecko: gecko,
760
761		// @property android: Boolean
762		// `true` for any browser running on an Android platform.
763		android: ua.indexOf('android') !== -1,
764
765		// @property android23: Boolean
766		// `true` for browsers running on Android 2 or Android 3.
767		android23: android23,
768
769		// @property chrome: Boolean
770		// `true` for the Chrome browser.
771		chrome: chrome,
772
773		// @property safari: Boolean
774		// `true` for the Safari browser.
775		safari: !chrome && ua.indexOf('safari') !== -1,
776
777
778		// @property win: Boolean
779		// `true` when the browser is running in a Windows platform
780		win: win,
781
782
783		// @property ie3d: Boolean
784		// `true` for all Internet Explorer versions supporting CSS transforms.
785		ie3d: ie3d,
786
787		// @property webkit3d: Boolean
788		// `true` for webkit-based browsers supporting CSS transforms.
789		webkit3d: webkit3d,
790
791		// @property gecko3d: Boolean
792		// `true` for gecko-based browsers supporting CSS transforms.
793		gecko3d: gecko3d,
794
795		// @property opera12: Boolean
796		// `true` for the Opera browser supporting CSS transforms (version 12 or later).
797		opera12: opera12,
798
799		// @property any3d: Boolean
800		// `true` for all browsers supporting CSS transforms.
801		any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
802
803
804		// @property mobile: Boolean
805		// `true` for all browsers running in a mobile device.
806		mobile: mobile,
807
808		// @property mobileWebkit: Boolean
809		// `true` for all webkit-based browsers in a mobile device.
810		mobileWebkit: mobile && webkit,
811
812		// @property mobileWebkit3d: Boolean
813		// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
814		mobileWebkit3d: mobile && webkit3d,
815
816		// @property mobileOpera: Boolean
817		// `true` for the Opera browser in a mobile device.
818		mobileOpera: mobile && window.opera,
819
820		// @property mobileGecko: Boolean
821		// `true` for gecko-based browsers running in a mobile device.
822		mobileGecko: mobile && gecko,
823
824
825		// @property touch: Boolean
826		// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
827		touch: !!touch,
828
829		// @property msPointer: Boolean
830		// `true` for browsers implementing the Microsoft touch events model (notably IE10).
831		msPointer: !!msPointer,
832
833		// @property pointer: Boolean
834		// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
835		pointer: !!pointer,
836
837
838		// @property retina: Boolean
839		// `true` for browsers on a high-resolution "retina" screen.
840		retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
841	};
842
843}());
844
845
846
847/*
848 * @class Point
849 * @aka L.Point
850 *
851 * Represents a point with `x` and `y` coordinates in pixels.
852 *
853 * @example
854 *
855 * ```js
856 * var point = L.point(200, 300);
857 * ```
858 *
859 * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
860 *
861 * ```js
862 * map.panBy([200, 300]);
863 * map.panBy(L.point(200, 300));
864 * ```
865 */
866
867L.Point = function (x, y, round) {
868	this.x = (round ? Math.round(x) : x);
869	this.y = (round ? Math.round(y) : y);
870};
871
872L.Point.prototype = {
873
874	// @method clone(): Point
875	// Returns a copy of the current point.
876	clone: function () {
877		return new L.Point(this.x, this.y);
878	},
879
880	// @method add(otherPoint: Point): Point
881	// Returns the result of addition of the current and the given points.
882	add: function (point) {
883		// non-destructive, returns a new point
884		return this.clone()._add(L.point(point));
885	},
886
887	_add: function (point) {
888		// destructive, used directly for performance in situations where it's safe to modify existing point
889		this.x += point.x;
890		this.y += point.y;
891		return this;
892	},
893
894	// @method subtract(otherPoint: Point): Point
895	// Returns the result of subtraction of the given point from the current.
896	subtract: function (point) {
897		return this.clone()._subtract(L.point(point));
898	},
899
900	_subtract: function (point) {
901		this.x -= point.x;
902		this.y -= point.y;
903		return this;
904	},
905
906	// @method divideBy(num: Number): Point
907	// Returns the result of division of the current point by the given number.
908	divideBy: function (num) {
909		return this.clone()._divideBy(num);
910	},
911
912	_divideBy: function (num) {
913		this.x /= num;
914		this.y /= num;
915		return this;
916	},
917
918	// @method multiplyBy(num: Number): Point
919	// Returns the result of multiplication of the current point by the given number.
920	multiplyBy: function (num) {
921		return this.clone()._multiplyBy(num);
922	},
923
924	_multiplyBy: function (num) {
925		this.x *= num;
926		this.y *= num;
927		return this;
928	},
929
930	// @method scaleBy(scale: Point): Point
931	// Multiply each coordinate of the current point by each coordinate of
932	// `scale`. In linear algebra terms, multiply the point by the
933	// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
934	// defined by `scale`.
935	scaleBy: function (point) {
936		return new L.Point(this.x * point.x, this.y * point.y);
937	},
938
939	// @method unscaleBy(scale: Point): Point
940	// Inverse of `scaleBy`. Divide each coordinate of the current point by
941	// each coordinate of `scale`.
942	unscaleBy: function (point) {
943		return new L.Point(this.x / point.x, this.y / point.y);
944	},
945
946	// @method round(): Point
947	// Returns a copy of the current point with rounded coordinates.
948	round: function () {
949		return this.clone()._round();
950	},
951
952	_round: function () {
953		this.x = Math.round(this.x);
954		this.y = Math.round(this.y);
955		return this;
956	},
957
958	// @method floor(): Point
959	// Returns a copy of the current point with floored coordinates (rounded down).
960	floor: function () {
961		return this.clone()._floor();
962	},
963
964	_floor: function () {
965		this.x = Math.floor(this.x);
966		this.y = Math.floor(this.y);
967		return this;
968	},
969
970	// @method ceil(): Point
971	// Returns a copy of the current point with ceiled coordinates (rounded up).
972	ceil: function () {
973		return this.clone()._ceil();
974	},
975
976	_ceil: function () {
977		this.x = Math.ceil(this.x);
978		this.y = Math.ceil(this.y);
979		return this;
980	},
981
982	// @method distanceTo(otherPoint: Point): Number
983	// Returns the cartesian distance between the current and the given points.
984	distanceTo: function (point) {
985		point = L.point(point);
986
987		var x = point.x - this.x,
988		    y = point.y - this.y;
989
990		return Math.sqrt(x * x + y * y);
991	},
992
993	// @method equals(otherPoint: Point): Boolean
994	// Returns `true` if the given point has the same coordinates.
995	equals: function (point) {
996		point = L.point(point);
997
998		return point.x === this.x &&
999		       point.y === this.y;
1000	},
1001
1002	// @method contains(otherPoint: Point): Boolean
1003	// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1004	contains: function (point) {
1005		point = L.point(point);
1006
1007		return Math.abs(point.x) <= Math.abs(this.x) &&
1008		       Math.abs(point.y) <= Math.abs(this.y);
1009	},
1010
1011	// @method toString(): String
1012	// Returns a string representation of the point for debugging purposes.
1013	toString: function () {
1014		return 'Point(' +
1015		        L.Util.formatNum(this.x) + ', ' +
1016		        L.Util.formatNum(this.y) + ')';
1017	}
1018};
1019
1020// @factory L.point(x: Number, y: Number, round?: Boolean)
1021// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1022
1023// @alternative
1024// @factory L.point(coords: Number[])
1025// Expects an array of the form `[x, y]` instead.
1026
1027// @alternative
1028// @factory L.point(coords: Object)
1029// Expects a plain object of the form `{x: Number, y: Number}` instead.
1030L.point = function (x, y, round) {
1031	if (x instanceof L.Point) {
1032		return x;
1033	}
1034	if (L.Util.isArray(x)) {
1035		return new L.Point(x[0], x[1]);
1036	}
1037	if (x === undefined || x === null) {
1038		return x;
1039	}
1040	if (typeof x === 'object' && 'x' in x && 'y' in x) {
1041		return new L.Point(x.x, x.y);
1042	}
1043	return new L.Point(x, y, round);
1044};
1045
1046
1047
1048/*
1049 * @class Bounds
1050 * @aka L.Bounds
1051 *
1052 * Represents a rectangular area in pixel coordinates.
1053 *
1054 * @example
1055 *
1056 * ```js
1057 * var p1 = L.point(10, 10),
1058 * p2 = L.point(40, 60),
1059 * bounds = L.bounds(p1, p2);
1060 * ```
1061 *
1062 * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1063 *
1064 * ```js
1065 * otherBounds.intersects([[10, 10], [40, 60]]);
1066 * ```
1067 */
1068
1069L.Bounds = function (a, b) {
1070	if (!a) { return; }
1071
1072	var points = b ? [a, b] : a;
1073
1074	for (var i = 0, len = points.length; i < len; i++) {
1075		this.extend(points[i]);
1076	}
1077};
1078
1079L.Bounds.prototype = {
1080	// @method extend(point: Point): this
1081	// Extends the bounds to contain the given point.
1082	extend: function (point) { // (Point)
1083		point = L.point(point);
1084
1085		// @property min: Point
1086		// The top left corner of the rectangle.
1087		// @property max: Point
1088		// The bottom right corner of the rectangle.
1089		if (!this.min && !this.max) {
1090			this.min = point.clone();
1091			this.max = point.clone();
1092		} else {
1093			this.min.x = Math.min(point.x, this.min.x);
1094			this.max.x = Math.max(point.x, this.max.x);
1095			this.min.y = Math.min(point.y, this.min.y);
1096			this.max.y = Math.max(point.y, this.max.y);
1097		}
1098		return this;
1099	},
1100
1101	// @method getCenter(round?: Boolean): Point
1102	// Returns the center point of the bounds.
1103	getCenter: function (round) {
1104		return new L.Point(
1105		        (this.min.x + this.max.x) / 2,
1106		        (this.min.y + this.max.y) / 2, round);
1107	},
1108
1109	// @method getBottomLeft(): Point
1110	// Returns the bottom-left point of the bounds.
1111	getBottomLeft: function () {
1112		return new L.Point(this.min.x, this.max.y);
1113	},
1114
1115	// @method getTopRight(): Point
1116	// Returns the top-right point of the bounds.
1117	getTopRight: function () { // -> Point
1118		return new L.Point(this.max.x, this.min.y);
1119	},
1120
1121	// @method getSize(): Point
1122	// Returns the size of the given bounds
1123	getSize: function () {
1124		return this.max.subtract(this.min);
1125	},
1126
1127	// @method contains(otherBounds: Bounds): Boolean
1128	// Returns `true` if the rectangle contains the given one.
1129	// @alternative
1130	// @method contains(point: Point): Boolean
1131	// Returns `true` if the rectangle contains the given point.
1132	contains: function (obj) {
1133		var min, max;
1134
1135		if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1136			obj = L.point(obj);
1137		} else {
1138			obj = L.bounds(obj);
1139		}
1140
1141		if (obj instanceof L.Bounds) {
1142			min = obj.min;
1143			max = obj.max;
1144		} else {
1145			min = max = obj;
1146		}
1147
1148		return (min.x >= this.min.x) &&
1149		       (max.x <= this.max.x) &&
1150		       (min.y >= this.min.y) &&
1151		       (max.y <= this.max.y);
1152	},
1153
1154	// @method intersects(otherBounds: Bounds): Boolean
1155	// Returns `true` if the rectangle intersects the given bounds. Two bounds
1156	// intersect if they have at least one point in common.
1157	intersects: function (bounds) { // (Bounds) -> Boolean
1158		bounds = L.bounds(bounds);
1159
1160		var min = this.min,
1161		    max = this.max,
1162		    min2 = bounds.min,
1163		    max2 = bounds.max,
1164		    xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1165		    yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1166
1167		return xIntersects && yIntersects;
1168	},
1169
1170	// @method overlaps(otherBounds: Bounds): Boolean
1171	// Returns `true` if the rectangle overlaps the given bounds. Two bounds
1172	// overlap if their intersection is an area.
1173	overlaps: function (bounds) { // (Bounds) -> Boolean
1174		bounds = L.bounds(bounds);
1175
1176		var min = this.min,
1177		    max = this.max,
1178		    min2 = bounds.min,
1179		    max2 = bounds.max,
1180		    xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1181		    yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1182
1183		return xOverlaps && yOverlaps;
1184	},
1185
1186	isValid: function () {
1187		return !!(this.min && this.max);
1188	}
1189};
1190
1191
1192// @factory L.bounds(topLeft: Point, bottomRight: Point)
1193// Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1194// @alternative
1195// @factory L.bounds(points: Point[])
1196// Creates a Bounds object from the points it contains
1197L.bounds = function (a, b) {
1198	if (!a || a instanceof L.Bounds) {
1199		return a;
1200	}
1201	return new L.Bounds(a, b);
1202};
1203
1204
1205
1206/*
1207 * @class Transformation
1208 * @aka L.Transformation
1209 *
1210 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1211 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1212 * the reverse. Used by Leaflet in its projections code.
1213 *
1214 * @example
1215 *
1216 * ```js
1217 * var transformation = new L.Transformation(2, 5, -1, 10),
1218 * 	p = L.point(1, 2),
1219 * 	p2 = transformation.transform(p), //  L.point(7, 8)
1220 * 	p3 = transformation.untransform(p2); //  L.point(1, 2)
1221 * ```
1222 */
1223
1224
1225// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1226// Creates a `Transformation` object with the given coefficients.
1227L.Transformation = function (a, b, c, d) {
1228	this._a = a;
1229	this._b = b;
1230	this._c = c;
1231	this._d = d;
1232};
1233
1234L.Transformation.prototype = {
1235	// @method transform(point: Point, scale?: Number): Point
1236	// Returns a transformed point, optionally multiplied by the given scale.
1237	// Only accepts real `L.Point` instances, not arrays.
1238	transform: function (point, scale) { // (Point, Number) -> Point
1239		return this._transform(point.clone(), scale);
1240	},
1241
1242	// destructive transform (faster)
1243	_transform: function (point, scale) {
1244		scale = scale || 1;
1245		point.x = scale * (this._a * point.x + this._b);
1246		point.y = scale * (this._c * point.y + this._d);
1247		return point;
1248	},
1249
1250	// @method untransform(point: Point, scale?: Number): Point
1251	// Returns the reverse transformation of the given point, optionally divided
1252	// by the given scale. Only accepts real `L.Point` instances, not arrays.
1253	untransform: function (point, scale) {
1254		scale = scale || 1;
1255		return new L.Point(
1256		        (point.x / scale - this._b) / this._a,
1257		        (point.y / scale - this._d) / this._c);
1258	}
1259};
1260
1261
1262
1263/*
1264 * @namespace DomUtil
1265 *
1266 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1267 * tree, used by Leaflet internally.
1268 *
1269 * Most functions expecting or returning a `HTMLElement` also work for
1270 * SVG elements. The only difference is that classes refer to CSS classes
1271 * in HTML and SVG classes in SVG.
1272 */
1273
1274L.DomUtil = {
1275
1276	// @function get(id: String|HTMLElement): HTMLElement
1277	// Returns an element given its DOM id, or returns the element itself
1278	// if it was passed directly.
1279	get: function (id) {
1280		return typeof id === 'string' ? document.getElementById(id) : id;
1281	},
1282
1283	// @function getStyle(el: HTMLElement, styleAttrib: String): String
1284	// Returns the value for a certain style attribute on an element,
1285	// including computed values or values set through CSS.
1286	getStyle: function (el, style) {
1287
1288		var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1289
1290		if ((!value || value === 'auto') && document.defaultView) {
1291			var css = document.defaultView.getComputedStyle(el, null);
1292			value = css ? css[style] : null;
1293		}
1294
1295		return value === 'auto' ? null : value;
1296	},
1297
1298	// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1299	// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1300	create: function (tagName, className, container) {
1301
1302		var el = document.createElement(tagName);
1303		el.className = className || '';
1304
1305		if (container) {
1306			container.appendChild(el);
1307		}
1308
1309		return el;
1310	},
1311
1312	// @function remove(el: HTMLElement)
1313	// Removes `el` from its parent element
1314	remove: function (el) {
1315		var parent = el.parentNode;
1316		if (parent) {
1317			parent.removeChild(el);
1318		}
1319	},
1320
1321	// @function empty(el: HTMLElement)
1322	// Removes all of `el`'s children elements from `el`
1323	empty: function (el) {
1324		while (el.firstChild) {
1325			el.removeChild(el.firstChild);
1326		}
1327	},
1328
1329	// @function toFront(el: HTMLElement)
1330	// Makes `el` the last children of its parent, so it renders in front of the other children.
1331	toFront: function (el) {
1332		el.parentNode.appendChild(el);
1333	},
1334
1335	// @function toBack(el: HTMLElement)
1336	// Makes `el` the first children of its parent, so it renders back from the other children.
1337	toBack: function (el) {
1338		var parent = el.parentNode;
1339		parent.insertBefore(el, parent.firstChild);
1340	},
1341
1342	// @function hasClass(el: HTMLElement, name: String): Boolean
1343	// Returns `true` if the element's class attribute contains `name`.
1344	hasClass: function (el, name) {
1345		if (el.classList !== undefined) {
1346			return el.classList.contains(name);
1347		}
1348		var className = L.DomUtil.getClass(el);
1349		return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1350	},
1351
1352	// @function addClass(el: HTMLElement, name: String)
1353	// Adds `name` to the element's class attribute.
1354	addClass: function (el, name) {
1355		if (el.classList !== undefined) {
1356			var classes = L.Util.splitWords(name);
1357			for (var i = 0, len = classes.length; i < len; i++) {
1358				el.classList.add(classes[i]);
1359			}
1360		} else if (!L.DomUtil.hasClass(el, name)) {
1361			var className = L.DomUtil.getClass(el);
1362			L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1363		}
1364	},
1365
1366	// @function removeClass(el: HTMLElement, name: String)
1367	// Removes `name` from the element's class attribute.
1368	removeClass: function (el, name) {
1369		if (el.classList !== undefined) {
1370			el.classList.remove(name);
1371		} else {
1372			L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1373		}
1374	},
1375
1376	// @function setClass(el: HTMLElement, name: String)
1377	// Sets the element's class.
1378	setClass: function (el, name) {
1379		if (el.className.baseVal === undefined) {
1380			el.className = name;
1381		} else {
1382			// in case of SVG element
1383			el.className.baseVal = name;
1384		}
1385	},
1386
1387	// @function getClass(el: HTMLElement): String
1388	// Returns the element's class.
1389	getClass: function (el) {
1390		return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1391	},
1392
1393	// @function setOpacity(el: HTMLElement, opacity: Number)
1394	// Set the opacity of an element (including old IE support).
1395	// `opacity` must be a number from `0` to `1`.
1396	setOpacity: function (el, value) {
1397
1398		if ('opacity' in el.style) {
1399			el.style.opacity = value;
1400
1401		} else if ('filter' in el.style) {
1402			L.DomUtil._setOpacityIE(el, value);
1403		}
1404	},
1405
1406	_setOpacityIE: function (el, value) {
1407		var filter = false,
1408		    filterName = 'DXImageTransform.Microsoft.Alpha';
1409
1410		// filters collection throws an error if we try to retrieve a filter that doesn't exist
1411		try {
1412			filter = el.filters.item(filterName);
1413		} catch (e) {
1414			// don't set opacity to 1 if we haven't already set an opacity,
1415			// it isn't needed and breaks transparent pngs.
1416			if (value === 1) { return; }
1417		}
1418
1419		value = Math.round(value * 100);
1420
1421		if (filter) {
1422			filter.Enabled = (value !== 100);
1423			filter.Opacity = value;
1424		} else {
1425			el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1426		}
1427	},
1428
1429	// @function testProp(props: String[]): String|false
1430	// Goes through the array of style names and returns the first name
1431	// that is a valid style name for an element. If no such name is found,
1432	// it returns false. Useful for vendor-prefixed styles like `transform`.
1433	testProp: function (props) {
1434
1435		var style = document.documentElement.style;
1436
1437		for (var i = 0; i < props.length; i++) {
1438			if (props[i] in style) {
1439				return props[i];
1440			}
1441		}
1442		return false;
1443	},
1444
1445	// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1446	// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1447	// and optionally scaled by `scale`. Does not have an effect if the
1448	// browser doesn't support 3D CSS transforms.
1449	setTransform: function (el, offset, scale) {
1450		var pos = offset || new L.Point(0, 0);
1451
1452		el.style[L.DomUtil.TRANSFORM] =
1453			(L.Browser.ie3d ?
1454				'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1455				'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1456			(scale ? ' scale(' + scale + ')' : '');
1457	},
1458
1459	// @function setPosition(el: HTMLElement, position: Point)
1460	// Sets the position of `el` to coordinates specified by `position`,
1461	// using CSS translate or top/left positioning depending on the browser
1462	// (used by Leaflet internally to position its layers).
1463	setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1464
1465		/*eslint-disable */
1466		el._leaflet_pos = point;
1467		/*eslint-enable */
1468
1469		if (L.Browser.any3d) {
1470			L.DomUtil.setTransform(el, point);
1471		} else {
1472			el.style.left = point.x + 'px';
1473			el.style.top = point.y + 'px';
1474		}
1475	},
1476
1477	// @function getPosition(el: HTMLElement): Point
1478	// Returns the coordinates of an element previously positioned with setPosition.
1479	getPosition: function (el) {
1480		// this method is only used for elements previously positioned using setPosition,
1481		// so it's safe to cache the position for performance
1482
1483		return el._leaflet_pos || new L.Point(0, 0);
1484	}
1485};
1486
1487
1488(function () {
1489	// prefix style property names
1490
1491	// @property TRANSFORM: String
1492	// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1493	L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1494			['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1495
1496
1497	// webkitTransition comes first because some browser versions that drop vendor prefix don't do
1498	// the same for the transitionend event, in particular the Android 4.1 stock browser
1499
1500	// @property TRANSITION: String
1501	// Vendor-prefixed transform style name.
1502	var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1503			['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1504
1505	L.DomUtil.TRANSITION_END =
1506			transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1507
1508	// @function disableTextSelection()
1509	// Prevents the user from generating `selectstart` DOM events, usually generated
1510	// when the user drags the mouse through a page with text. Used internally
1511	// by Leaflet to override the behaviour of any click-and-drag interaction on
1512	// the map. Affects drag interactions on the whole document.
1513
1514	// @function enableTextSelection()
1515	// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1516	if ('onselectstart' in document) {
1517		L.DomUtil.disableTextSelection = function () {
1518			L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1519		};
1520		L.DomUtil.enableTextSelection = function () {
1521			L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1522		};
1523
1524	} else {
1525		var userSelectProperty = L.DomUtil.testProp(
1526			['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1527
1528		L.DomUtil.disableTextSelection = function () {
1529			if (userSelectProperty) {
1530				var style = document.documentElement.style;
1531				this._userSelect = style[userSelectProperty];
1532				style[userSelectProperty] = 'none';
1533			}
1534		};
1535		L.DomUtil.enableTextSelection = function () {
1536			if (userSelectProperty) {
1537				document.documentElement.style[userSelectProperty] = this._userSelect;
1538				delete this._userSelect;
1539			}
1540		};
1541	}
1542
1543	// @function disableImageDrag()
1544	// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1545	// for `dragstart` DOM events, usually generated when the user drags an image.
1546	L.DomUtil.disableImageDrag = function () {
1547		L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1548	};
1549
1550	// @function enableImageDrag()
1551	// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1552	L.DomUtil.enableImageDrag = function () {
1553		L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1554	};
1555
1556	// @function preventOutline(el: HTMLElement)
1557	// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1558	// of the element `el` invisible. Used internally by Leaflet to prevent
1559	// focusable elements from displaying an outline when the user performs a
1560	// drag interaction on them.
1561	L.DomUtil.preventOutline = function (element) {
1562		while (element.tabIndex === -1) {
1563			element = element.parentNode;
1564		}
1565		if (!element || !element.style) { return; }
1566		L.DomUtil.restoreOutline();
1567		this._outlineElement = element;
1568		this._outlineStyle = element.style.outline;
1569		element.style.outline = 'none';
1570		L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1571	};
1572
1573	// @function restoreOutline()
1574	// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1575	L.DomUtil.restoreOutline = function () {
1576		if (!this._outlineElement) { return; }
1577		this._outlineElement.style.outline = this._outlineStyle;
1578		delete this._outlineElement;
1579		delete this._outlineStyle;
1580		L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1581	};
1582})();
1583
1584
1585
1586/* @class LatLng
1587 * @aka L.LatLng
1588 *
1589 * Represents a geographical point with a certain latitude and longitude.
1590 *
1591 * @example
1592 *
1593 * ```
1594 * var latlng = L.latLng(50.5, 30.5);
1595 * ```
1596 *
1597 * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
1598 *
1599 * ```
1600 * map.panTo([50, 30]);
1601 * map.panTo({lon: 30, lat: 50});
1602 * map.panTo({lat: 50, lng: 30});
1603 * map.panTo(L.latLng(50, 30));
1604 * ```
1605 */
1606
1607L.LatLng = function (lat, lng, alt) {
1608	if (isNaN(lat) || isNaN(lng)) {
1609		throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1610	}
1611
1612	// @property lat: Number
1613	// Latitude in degrees
1614	this.lat = +lat;
1615
1616	// @property lng: Number
1617	// Longitude in degrees
1618	this.lng = +lng;
1619
1620	// @property alt: Number
1621	// Altitude in meters (optional)
1622	if (alt !== undefined) {
1623		this.alt = +alt;
1624	}
1625};
1626
1627L.LatLng.prototype = {
1628	// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1629	// Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
1630	equals: function (obj, maxMargin) {
1631		if (!obj) { return false; }
1632
1633		obj = L.latLng(obj);
1634
1635		var margin = Math.max(
1636		        Math.abs(this.lat - obj.lat),
1637		        Math.abs(this.lng - obj.lng));
1638
1639		return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1640	},
1641
1642	// @method toString(): String
1643	// Returns a string representation of the point (for debugging purposes).
1644	toString: function (precision) {
1645		return 'LatLng(' +
1646		        L.Util.formatNum(this.lat, precision) + ', ' +
1647		        L.Util.formatNum(this.lng, precision) + ')';
1648	},
1649
1650	// @method distanceTo(otherLatLng: LatLng): Number
1651	// Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1652	distanceTo: function (other) {
1653		return L.CRS.Earth.distance(this, L.latLng(other));
1654	},
1655
1656	// @method wrap(): LatLng
1657	// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1658	wrap: function () {
1659		return L.CRS.Earth.wrapLatLng(this);
1660	},
1661
1662	// @method toBounds(sizeInMeters: Number): LatLngBounds
1663	// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`.
1664	toBounds: function (sizeInMeters) {
1665		var latAccuracy = 180 * sizeInMeters / 40075017,
1666		    lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1667
1668		return L.latLngBounds(
1669		        [this.lat - latAccuracy, this.lng - lngAccuracy],
1670		        [this.lat + latAccuracy, this.lng + lngAccuracy]);
1671	},
1672
1673	clone: function () {
1674		return new L.LatLng(this.lat, this.lng, this.alt);
1675	}
1676};
1677
1678
1679
1680// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1681// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1682
1683// @alternative
1684// @factory L.latLng(coords: Array): LatLng
1685// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1686
1687// @alternative
1688// @factory L.latLng(coords: Object): LatLng
1689// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1690
1691L.latLng = function (a, b, c) {
1692	if (a instanceof L.LatLng) {
1693		return a;
1694	}
1695	if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1696		if (a.length === 3) {
1697			return new L.LatLng(a[0], a[1], a[2]);
1698		}
1699		if (a.length === 2) {
1700			return new L.LatLng(a[0], a[1]);
1701		}
1702		return null;
1703	}
1704	if (a === undefined || a === null) {
1705		return a;
1706	}
1707	if (typeof a === 'object' && 'lat' in a) {
1708		return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1709	}
1710	if (b === undefined) {
1711		return null;
1712	}
1713	return new L.LatLng(a, b, c);
1714};
1715
1716
1717
1718/*
1719 * @class LatLngBounds
1720 * @aka L.LatLngBounds
1721 *
1722 * Represents a rectangular geographical area on a map.
1723 *
1724 * @example
1725 *
1726 * ```js
1727 * var southWest = L.latLng(40.712, -74.227),
1728 * northEast = L.latLng(40.774, -74.125),
1729 * bounds = L.latLngBounds(southWest, northEast);
1730 * ```
1731 *
1732 * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1733 *
1734 * ```js
1735 * map.fitBounds([
1736 * 	[40.712, -74.227],
1737 * 	[40.774, -74.125]
1738 * ]);
1739 * ```
1740 */
1741
1742L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
1743	if (!southWest) { return; }
1744
1745	var latlngs = northEast ? [southWest, northEast] : southWest;
1746
1747	for (var i = 0, len = latlngs.length; i < len; i++) {
1748		this.extend(latlngs[i]);
1749	}
1750};
1751
1752L.LatLngBounds.prototype = {
1753
1754	// @method extend(latlng: LatLng): this
1755	// Extend the bounds to contain the given point
1756
1757	// @alternative
1758	// @method extend(otherBounds: LatLngBounds): this
1759	// Extend the bounds to contain the given bounds
1760	extend: function (obj) {
1761		var sw = this._southWest,
1762		    ne = this._northEast,
1763		    sw2, ne2;
1764
1765		if (obj instanceof L.LatLng) {
1766			sw2 = obj;
1767			ne2 = obj;
1768
1769		} else if (obj instanceof L.LatLngBounds) {
1770			sw2 = obj._southWest;
1771			ne2 = obj._northEast;
1772
1773			if (!sw2 || !ne2) { return this; }
1774
1775		} else {
1776			return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1777		}
1778
1779		if (!sw && !ne) {
1780			this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1781			this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1782		} else {
1783			sw.lat = Math.min(sw2.lat, sw.lat);
1784			sw.lng = Math.min(sw2.lng, sw.lng);
1785			ne.lat = Math.max(ne2.lat, ne.lat);
1786			ne.lng = Math.max(ne2.lng, ne.lng);
1787		}
1788
1789		return this;
1790	},
1791
1792	// @method pad(bufferRatio: Number): LatLngBounds
1793	// Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1794	pad: function (bufferRatio) {
1795		var sw = this._southWest,
1796		    ne = this._northEast,
1797		    heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1798		    widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1799
1800		return new L.LatLngBounds(
1801		        new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1802		        new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1803	},
1804
1805	// @method getCenter(): LatLng
1806	// Returns the center point of the bounds.
1807	getCenter: function () {
1808		return new L.LatLng(
1809		        (this._southWest.lat + this._northEast.lat) / 2,
1810		        (this._southWest.lng + this._northEast.lng) / 2);
1811	},
1812
1813	// @method getSouthWest(): LatLng
1814	// Returns the south-west point of the bounds.
1815	getSouthWest: function () {
1816		return this._southWest;
1817	},
1818
1819	// @method getNorthEast(): LatLng
1820	// Returns the north-east point of the bounds.
1821	getNorthEast: function () {
1822		return this._northEast;
1823	},
1824
1825	// @method getNorthWest(): LatLng
1826	// Returns the north-west point of the bounds.
1827	getNorthWest: function () {
1828		return new L.LatLng(this.getNorth(), this.getWest());
1829	},
1830
1831	// @method getSouthEast(): LatLng
1832	// Returns the south-east point of the bounds.
1833	getSouthEast: function () {
1834		return new L.LatLng(this.getSouth(), this.getEast());
1835	},
1836
1837	// @method getWest(): Number
1838	// Returns the west longitude of the bounds
1839	getWest: function () {
1840		return this._southWest.lng;
1841	},
1842
1843	// @method getSouth(): Number
1844	// Returns the south latitude of the bounds
1845	getSouth: function () {
1846		return this._southWest.lat;
1847	},
1848
1849	// @method getEast(): Number
1850	// Returns the east longitude of the bounds
1851	getEast: function () {
1852		return this._northEast.lng;
1853	},
1854
1855	// @method getNorth(): Number
1856	// Returns the north latitude of the bounds
1857	getNorth: function () {
1858		return this._northEast.lat;
1859	},
1860
1861	// @method contains(otherBounds: LatLngBounds): Boolean
1862	// Returns `true` if the rectangle contains the given one.
1863
1864	// @alternative
1865	// @method contains (latlng: LatLng): Boolean
1866	// Returns `true` if the rectangle contains the given point.
1867	contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1868		if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1869			obj = L.latLng(obj);
1870		} else {
1871			obj = L.latLngBounds(obj);
1872		}
1873
1874		var sw = this._southWest,
1875		    ne = this._northEast,
1876		    sw2, ne2;
1877
1878		if (obj instanceof L.LatLngBounds) {
vendor: 1,060 bytes, lines 1879-1911
1879			sw2 = obj.getSouthWest();
1880			ne2 = obj.getNorthEast();
1881		} else {
1882			sw2 = ne2 = obj;
1883		}
1884
1885		return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1886		       (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1887	},
1888
1889	// @method intersects(otherBounds: LatLngBounds): Boolean
1890	// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1891	intersects: function (bounds) {
1892		bounds = L.latLngBounds(bounds);
1893
1894		var sw = this._southWest,
1895		    ne = this._northEast,
1896		    sw2 = bounds.getSouthWest(),
1897		    ne2 = bounds.getNorthEast(),
1898
1899		    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1900		    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1901
1902		return latIntersects && lngIntersects;
1903	},
1904
1905	// @method overlaps(otherBounds: Bounds): Boolean
1906	// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1907	overlaps: function (bounds) {
1908		bounds = L.latLngBounds(bounds);
1909
1910		var sw = this._southWest,
1911		    ne = this._northEast,
1912		    sw2 = bounds.getSouthWest(),
1913		    ne2 = bounds.getNorthEast(),
1914
1915		    latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1916		    lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1917
1918		return latOverlaps && lngOverlaps;
1919	},
1920
1921	// @method toBBoxString(): String
1922	// Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
1923	toBBoxString: function () {
1924		return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1925	},
1926
1927	// @method equals(otherBounds: LatLngBounds): Boolean
1928	// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1929	equals: function (bounds) {
1930		if (!bounds) { return false; }
1931
1932		bounds = L.latLngBounds(bounds);
1933
1934		return this._southWest.equals(bounds.getSouthWest()) &&
1935		       this._northEast.equals(bounds.getNorthEast());
1936	},
1937
1938	// @method isValid(): Boolean
1939	// Returns `true` if the bounds are properly initialized.
1940	isValid: function () {
1941		return !!(this._southWest && this._northEast);
1942	}
1943};
1944
1945// TODO International date line?
1946
1947// @factory L.latLngBounds(southWest: LatLng, northEast: LatLng)
1948// Creates a `LatLngBounds` object by defining south-west and north-east corners of the rectangle.
1949
1950// @alternative
1951// @factory L.latLngBounds(latlngs: LatLng[])
1952// Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
1953L.latLngBounds = function (a, b) {
1954	if (a instanceof L.LatLngBounds) {
1955		return a;
1956	}
1957	return new L.LatLngBounds(a, b);
1958};
1959
1960
1961
1962/*
1963 * @namespace Projection
1964 * @section
1965 * Leaflet comes with a set of already defined Projections out of the box:
1966 *
1967 * @projection L.Projection.LonLat
1968 *
1969 * Equirectangular, or Plate Carree projection — the most simple projection,
1970 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1971 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1972 * `EPSG:3395` and `Simple` CRS.
1973 */
1974
1975L.Projection = {};
1976
1977L.Projection.LonLat = {
1978	project: function (latlng) {
1979		return new L.Point(latlng.lng, latlng.lat);
1980	},
1981
1982	unproject: function (point) {
1983		return new L.LatLng(point.y, point.x);
1984	},
1985
1986	bounds: L.bounds([-180, -90], [180, 90])
1987};
1988
1989
1990
1991/*
1992 * @namespace Projection
1993 * @projection L.Projection.SphericalMercator
1994 *
1995 * Spherical Mercator projection — the most common projection for online maps,
1996 * used by almost all free and commercial tile providers. Assumes that Earth is
1997 * a sphere. Used by the `EPSG:3857` CRS.
1998 */
1999
2000L.Projection.SphericalMercator = {
2001
2002	R: 6378137,
2003	MAX_LATITUDE: 85.0511287798,
2004
2005	project: function (latlng) {
2006		var d = Math.PI / 180,
2007		    max = this.MAX_LATITUDE,
2008		    lat = Math.max(Math.min(max, latlng.lat), -max),
2009		    sin = Math.sin(lat * d);
2010
2011		return new L.Point(
2012				this.R * latlng.lng * d,
2013				this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2014	},
2015
2016	unproject: function (point) {
2017		var d = 180 / Math.PI;
2018
2019		return new L.LatLng(
2020			(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2021			point.x * d / this.R);
2022	},
2023
2024	bounds: (function () {
2025		var d = 6378137 * Math.PI;
2026		return L.bounds([-d, -d], [d, d]);
2027	})()
2028};
2029
2030
2031
2032/*
2033 * @class CRS
2034 * @aka L.CRS
2035 * Abstract class that defines coordinate reference systems for projecting
2036 * geographical points into pixel (screen) coordinates and back (and to
2037 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2038 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2039 *
2040 * Leaflet defines the most usual CRSs by default. If you want to use a
2041 * CRS not defined by default, take a look at the
2042 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2043 */
2044
2045L.CRS = {
2046	// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2047	// Projects geographical coordinates into pixel coordinates for a given zoom.
2048	latLngToPoint: function (latlng, zoom) {
2049		var projectedPoint = this.projection.project(latlng),
2050		    scale = this.scale(zoom);
2051
2052		return this.transformation._transform(projectedPoint, scale);
2053	},
2054
2055	// @method pointToLatLng(point: Point, zoom: Number): LatLng
2056	// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2057	// zoom into geographical coordinates.
2058	pointToLatLng: function (point, zoom) {
2059		var scale = this.scale(zoom),
2060		    untransformedPoint = this.transformation.untransform(point, scale);
2061
2062		return this.projection.unproject(untransformedPoint);
2063	},
2064
2065	// @method project(latlng: LatLng): Point
2066	// Projects geographical coordinates into coordinates in units accepted for
2067	// this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2068	project: function (latlng) {
2069		return this.projection.project(latlng);
2070	},
2071
2072	// @method unproject(point: Point): LatLng
2073	// Given a projected coordinate returns the corresponding LatLng.
2074	// The inverse of `project`.
2075	unproject: function (point) {
2076		return this.projection.unproject(point);
2077	},
2078
2079	// @method scale(zoom: Number): Number
2080	// Returns the scale used when transforming projected coordinates into
2081	// pixel coordinates for a particular zoom. For example, it returns
2082	// `256 * 2^zoom` for Mercator-based CRS.
2083	scale: function (zoom) {
2084		return 256 * Math.pow(2, zoom);
2085	},
2086
2087	// @method zoom(scale: Number): Number
2088	// Inverse of `scale()`, returns the zoom level corresponding to a scale
2089	// factor of `scale`.
2090	zoom: function (scale) {
2091		return Math.log(scale / 256) / Math.LN2;
2092	},
2093
2094	// @method getProjectedBounds(zoom: Number): Bounds
2095	// Returns the projection's bounds scaled and transformed for the provided `zoom`.
2096	getProjectedBounds: function (zoom) {
2097		if (this.infinite) { return null; }
2098
2099		var b = this.projection.bounds,
2100		    s = this.scale(zoom),
2101		    min = this.transformation.transform(b.min, s),
2102		    max = this.transformation.transform(b.max, s);
2103
2104		return L.bounds(min, max);
2105	},
2106
2107	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
2108	// Returns the distance between two geographical coordinates.
2109
2110	// @property code: String
2111	// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2112	//
2113	// @property wrapLng: Number[]
2114	// An array of two numbers defining whether the longitude (horizontal) coordinate
2115	// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2116	// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2117	//
2118	// @property wrapLat: Number[]
2119	// Like `wrapLng`, but for the latitude (vertical) axis.
2120
2121	// wrapLng: [min, max],
2122	// wrapLat: [min, max],
2123
2124	// @property infinite: Boolean
2125	// If true, the coordinate space will be unbounded (infinite in both axes)
2126	infinite: false,
2127
2128	// @method wrapLatLng(latlng: LatLng): LatLng
2129	// Returns a `LatLng` where lat and lng has been wrapped according to the
2130	// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2131	wrapLatLng: function (latlng) {
2132		var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2133		    lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2134		    alt = latlng.alt;
2135
2136		return L.latLng(lat, lng, alt);
2137	}
2138};
2139
2140
2141
2142/*
2143 * @namespace CRS
2144 * @crs L.CRS.Simple
2145 *
2146 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2147 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2148 * axis should still be inverted (going from bottom to top). `distance()` returns
2149 * simple euclidean distance.
2150 */
2151
2152L.CRS.Simple = L.extend({}, L.CRS, {
2153	projection: L.Projection.LonLat,
2154	transformation: new L.Transformation(1, 0, -1, 0),
2155
2156	scale: function (zoom) {
2157		return Math.pow(2, zoom);
2158	},
2159
2160	zoom: function (scale) {
2161		return Math.log(scale) / Math.LN2;
2162	},
2163
2164	distance: function (latlng1, latlng2) {
2165		var dx = latlng2.lng - latlng1.lng,
2166		    dy = latlng2.lat - latlng1.lat;
2167
2168		return Math.sqrt(dx * dx + dy * dy);
2169	},
2170
2171	infinite: true
2172});
2173
2174
2175
2176/*
2177 * @namespace CRS
2178 * @crs L.CRS.Earth
2179 *
2180 * Serves as the base for CRS that are global such that they cover the earth.
2181 * Can only be used as the base for other CRS and cannot be used directly,
2182 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2183 * meters.
2184 */
2185
2186L.CRS.Earth = L.extend({}, L.CRS, {
2187	wrapLng: [-180, 180],
2188
2189	// Mean Earth Radius, as recommended for use by
2190	// the International Union of Geodesy and Geophysics,
2191	// see http://rosettacode.org/wiki/Haversine_formula
2192	R: 6371000,
2193
2194	// distance between two geographical points using spherical law of cosines approximation
2195	distance: function (latlng1, latlng2) {
2196		var rad = Math.PI / 180,
2197		    lat1 = latlng1.lat * rad,
2198		    lat2 = latlng2.lat * rad,
2199		    a = Math.sin(lat1) * Math.sin(lat2) +
2200		        Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2201
2202		return this.R * Math.acos(Math.min(a, 1));
2203	}
2204});
2205
2206
2207
2208/*
2209 * @namespace CRS
2210 * @crs L.CRS.EPSG3857
2211 *
2212 * The most common CRS for online maps, used by almost all free and commercial
2213 * tile providers. Uses Spherical Mercator projection. Set in by default in
2214 * Map's `crs` option.
2215 */
2216
2217L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2218	code: 'EPSG:3857',
2219	projection: L.Projection.SphericalMercator,
2220
2221	transformation: (function () {
2222		var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2223		return new L.Transformation(scale, 0.5, -scale, 0.5);
2224	}())
2225});
2226
2227L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2228	code: 'EPSG:900913'
2229});
2230
2231
2232
2233/*
2234 * @namespace CRS
2235 * @crs L.CRS.EPSG4326
2236 *
2237 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2238 */
2239
2240L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2241	code: 'EPSG:4326',
2242	projection: L.Projection.LonLat,
2243	transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2244});
2245
2246
2247
2248/*
2249 * @class Map
2250 * @aka L.Map
2251 * @inherits Evented
2252 *
2253 * The central class of the API — it is used to create a map on a page and manipulate it.
2254 *
2255 * @example
2256 *
2257 * ```js
2258 * // initialize the map on the "map" div with a given center and zoom
2259 * var map = L.map('map', {
2260 * 	center: [51.505, -0.09],
2261 * 	zoom: 13
2262 * });
2263 * ```
2264 *
2265 */
2266
2267L.Map = L.Evented.extend({
2268
2269	options: {
2270		// @section Map State Options
2271		// @option crs: CRS = L.CRS.EPSG3857
2272		// The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2273		// sure what it means.
2274		crs: L.CRS.EPSG3857,
2275
2276		// @option center: LatLng = undefined
2277		// Initial geographic center of the map
2278		center: undefined,
2279
2280		// @option zoom: Number = undefined
2281		// Initial map zoom level
2282		zoom: undefined,
2283
2284		// @option minZoom: Number = undefined
2285		// Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2286		minZoom: undefined,
2287
2288		// @option maxZoom: Number = undefined
2289		// Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2290		maxZoom: undefined,
2291
2292		// @option layers: Layer[] = []
2293		// Array of layers that will be added to the map initially
2294		layers: [],
2295
2296		// @option maxBounds: LatLngBounds = null
2297		// When this option is set, the map restricts the view to the given
2298		// geographical bounds, bouncing the user back when he tries to pan
2299		// outside the view. To set the restriction dynamically, use
2300		// [`setMaxBounds`](#map-setmaxbounds) method.
2301		maxBounds: undefined,
2302
2303		// @option renderer: Renderer = *
2304		// The default method for drawing vector layers on the map. `L.SVG`
2305		// or `L.Canvas` by default depending on browser support.
2306		renderer: undefined,
2307
2308
2309		// @section Animation Options
2310		// @option fadeAnimation: Boolean = true
2311		// Whether the tile fade animation is enabled. By default it's enabled
2312		// in all browsers that support CSS3 Transitions except Android.
2313		fadeAnimation: true,
2314
2315		// @option markerZoomAnimation: Boolean = true
2316		// Whether markers animate their zoom with the zoom animation, if disabled
2317		// they will disappear for the length of the animation. By default it's
2318		// enabled in all browsers that support CSS3 Transitions except Android.
2319		markerZoomAnimation: true,
2320
2321		// @option transform3DLimit: Number = 2^23
2322		// Defines the maximum size of a CSS translation transform. The default
2323		// value should not be changed unless a web browser positions layers in
2324		// the wrong place after doing a large `panBy`.
2325		transform3DLimit: 8388608, // Precision limit of a 32-bit float
2326
2327		// @section Interaction Options
2328		// @option zoomSnap: Number = 1
2329		// Forces the map's zoom level to always be a multiple of this, particularly
2330		// right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2331		// By default, the zoom level snaps to the nearest integer; lower values
2332		// (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2333		// means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2334		zoomSnap: 1,
2335
2336		// @option zoomDelta: Number = 1
2337		// Controls how much the map's zoom level will change after a
2338		// [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2339		// or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2340		// Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2341		zoomDelta: 1,
2342
2343		// @option trackResize: Boolean = true
2344		// Whether the map automatically handles browser window resize to update itself.
2345		trackResize: true
2346	},
2347
2348	initialize: function (id, options) { // (HTMLElement or String, Object)
2349		options = L.setOptions(this, options);
2350
2351		this._initContainer(id);
2352		this._initLayout();
2353
2354		// hack for https://github.com/Leaflet/Leaflet/issues/1980
2355		this._onResize = L.bind(this._onResize, this);
2356
2357		this._initEvents();
2358
2359		if (options.maxBounds) {
2360			this.setMaxBounds(options.maxBounds);
2361		}
2362
2363		if (options.zoom !== undefined) {
2364			this._zoom = this._limitZoom(options.zoom);
2365		}
2366
2367		if (options.center && options.zoom !== undefined) {
2368			this.setView(L.latLng(options.center), options.zoom, {reset: true});
2369		}
2370
2371		this._handlers = [];
2372		this._layers = {};
2373		this._zoomBoundLayers = {};
2374		this._sizeChanged = true;
2375
2376		this.callInitHooks();
2377
2378		this._addLayers(this.options.layers);
2379	},
2380
2381
2382	// @section Methods for modifying map state
2383
2384	// @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2385	// Sets the view of the map (geographical center and zoom) with the given
2386	// animation options.
2387	setView: function (center, zoom) {
2388		// replaced by animation-powered implementation in Map.PanAnimation.js
2389		zoom = zoom === undefined ? this.getZoom() : zoom;
2390		this._resetView(L.latLng(center), zoom);
2391		return this;
2392	},
2393
2394	// @method setZoom(zoom: Number, options: Zoom/pan options): this
2395	// Sets the zoom of the map.
2396	setZoom: function (zoom, options) {
2397		if (!this._loaded) {
2398			this._zoom = zoom;
2399			return this;
2400		}
2401		return this.setView(this.getCenter(), zoom, {zoom: options});
2402	},
2403
2404	// @method zoomIn(delta?: Number, options?: Zoom options): this
2405	// Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2406	zoomIn: function (delta, options) {
2407		delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2408		return this.setZoom(this._zoom + delta, options);
2409	},
2410
2411	// @method zoomOut(delta?: Number, options?: Zoom options): this
2412	// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2413	zoomOut: function (delta, options) {
2414		delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2415		return this.setZoom(this._zoom - delta, options);
2416	},
2417
2418	// @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2419	// Zooms the map while keeping a specified geographical point on the map
2420	// stationary (e.g. used internally for scroll zoom and double-click zoom).
2421	// @alternative
2422	// @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2423	// Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2424	setZoomAround: function (latlng, zoom, options) {
2425		var scale = this.getZoomScale(zoom),
2426		    viewHalf = this.getSize().divideBy(2),
2427		    containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2428
2429		    centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2430		    newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2431
2432		return this.setView(newCenter, zoom, {zoom: options});
2433	},
2434
2435	_getBoundsCenterZoom: function (bounds, options) {
2436
2437		options = options || {};
2438		bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2439
2440		var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2441		    paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2442
2443		    zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2444
2445		zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2446
2447		var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2448
2449		    swPoint = this.project(bounds.getSouthWest(), zoom),
2450		    nePoint = this.project(bounds.getNorthEast(), zoom),
2451		    center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2452
2453		return {
2454			center: center,
2455			zoom: zoom
2456		};
2457	},
2458
2459	// @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this
2460	// Sets a map view that contains the given geographical bounds with the
2461	// maximum zoom level possible.
2462	fitBounds: function (bounds, options) {
2463
2464		bounds = L.latLngBounds(bounds);
2465
2466		if (!bounds.isValid()) {
2467			throw new Error('Bounds are not valid.');
2468		}
2469
2470		var target = this._getBoundsCenterZoom(bounds, options);
2471		return this.setView(target.center, target.zoom, options);
2472	},
2473
2474	// @method fitWorld(options?: fitBounds options): this
2475	// Sets a map view that mostly contains the whole world with the maximum
2476	// zoom level possible.
2477	fitWorld: function (options) {
2478		return this.fitBounds([[-90, -180], [90, 180]], options);
2479	},
2480
2481	// @method panTo(latlng: LatLng, options?: Pan options): this
2482	// Pans the map to a given center.
2483	panTo: function (center, options) { // (LatLng)
2484		return this.setView(center, this._zoom, {pan: options});
2485	},
2486
2487	// @method panBy(offset: Point): this
2488	// Pans the map by a given number of pixels (animated).
2489	panBy: function (offset) { // (Point)
2490		// replaced with animated panBy in Map.PanAnimation.js
2491		this.fire('movestart');
2492
2493		this._rawPanBy(L.point(offset));
2494
2495		this.fire('move');
2496		return this.fire('moveend');
2497	},
2498
2499	// @method setMaxBounds(bounds: Bounds): this
2500	// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2501	setMaxBounds: function (bounds) {
2502		bounds = L.latLngBounds(bounds);
2503
2504		if (!bounds.isValid()) {
2505			this.options.maxBounds = null;
2506			return this.off('moveend', this._panInsideMaxBounds);
2507		} else if (this.options.maxBounds) {
2508			this.off('moveend', this._panInsideMaxBounds);
2509		}
2510
2511		this.options.maxBounds = bounds;
2512
2513		if (this._loaded) {
2514			this._panInsideMaxBounds();
2515		}
2516
2517		return this.on('moveend', this._panInsideMaxBounds);
2518	},
2519
2520	// @method setMinZoom(zoom: Number): this
2521	// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2522	setMinZoom: function (zoom) {
2523		this.options.minZoom = zoom;
2524
2525		if (this._loaded && this.getZoom() < this.options.minZoom) {
2526			return this.setZoom(zoom);
2527		}
2528
2529		return this;
2530	},
2531
2532	// @method setMaxZoom(zoom: Number): this
2533	// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2534	setMaxZoom: function (zoom) {
2535		this.options.maxZoom = zoom;
2536
2537		if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2538			return this.setZoom(zoom);
2539		}
2540
2541		return this;
2542	},
2543
2544	// @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2545	// Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
2546	panInsideBounds: function (bounds, options) {
2547		this._enforcingBounds = true;
2548		var center = this.getCenter(),
2549		    newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2550
2551		if (!center.equals(newCenter)) {
2552			this.panTo(newCenter, options);
2553		}
2554
2555		this._enforcingBounds = false;
2556		return this;
2557	},
2558
2559	// @method invalidateSize(options: Zoom/Pan options): this
2560	// Checks if the map container size changed and updates the map if so —
2561	// call it after you've changed the map size dynamically, also animating
2562	// pan by default. If `options.pan` is `false`, panning will not occur.
2563	// If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2564	// that it doesn't happen often even if the method is called many
2565	// times in a row.
2566
2567	// @alternative
2568	// @method invalidateSize(animate: Boolean): this
2569	// Checks if the map container size changed and updates the map if so —
2570	// call it after you've changed the map size dynamically, also animating
2571	// pan by default.
2572	invalidateSize: function (options) {
2573		if (!this._loaded) { return this; }
2574
2575		options = L.extend({
2576			animate: false,
2577			pan: true
2578		}, options === true ? {animate: true} : options);
2579
2580		var oldSize = this.getSize();
2581		this._sizeChanged = true;
2582		this._lastCenter = null;
2583
2584		var newSize = this.getSize(),
2585		    oldCenter = oldSize.divideBy(2).round(),
2586		    newCenter = newSize.divideBy(2).round(),
2587		    offset = oldCenter.subtract(newCenter);
2588
2589		if (!offset.x && !offset.y) { return this; }
2590
2591		if (options.animate && options.pan) {
2592			this.panBy(offset);
2593
2594		} else {
2595			if (options.pan) {
2596				this._rawPanBy(offset);
2597			}
2598
2599			this.fire('move');
2600
2601			if (options.debounceMoveend) {
2602				clearTimeout(this._sizeTimer);
2603				this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2604			} else {
2605				this.fire('moveend');
2606			}
2607		}
2608
2609		// @section Map state change events
2610		// @event resize: ResizeEvent
2611		// Fired when the map is resized.
2612		return this.fire('resize', {
2613			oldSize: oldSize,
2614			newSize: newSize
2615		});
2616	},
2617
2618	// @section Methods for modifying map state
2619	// @method stop(): this
2620	// Stops the currently running `panTo` or `flyTo` animation, if any.
2621	stop: function () {
2622		this.setZoom(this._limitZoom(this._zoom));
2623		if (!this.options.zoomSnap) {
2624			this.fire('viewreset');
2625		}
2626		return this._stop();
2627	},
2628
2629
2630	// TODO handler.addTo
2631	// TODO Appropiate docs section?
2632	// @section Other Methods
2633	// @method addHandler(name: String, HandlerClass: Function): this
2634	// Adds a new `Handler` to the map, given its name and constructor function.
2635	addHandler: function (name, HandlerClass) {
2636		if (!HandlerClass) { return this; }
2637
2638		var handler = this[name] = new HandlerClass(this);
2639
2640		this._handlers.push(handler);
2641
2642		if (this.options[name]) {
2643			handler.enable();
2644		}
2645
2646		return this;
2647	},
2648
2649	// @method remove(): this
2650	// Destroys the map and clears all related event listeners.
2651	remove: function () {
2652
2653		this._initEvents(true);
2654
2655		if (this._containerId !== this._container._leaflet_id) {
2656			throw new Error('Map container is being reused by another instance');
2657		}
2658
2659		try {
2660			// throws error in IE6-8
2661			delete this._container._leaflet_id;
2662			delete this._containerId;
2663		} catch (e) {
2664			/*eslint-disable */
2665			this._container._leaflet_id = undefined;
2666			/*eslint-enable */
2667			this._containerId = undefined;
2668		}
2669
2670		L.DomUtil.remove(this._mapPane);
2671
2672		if (this._clearControlPos) {
2673			this._clearControlPos();
2674		}
2675
2676		this._clearHandlers();
2677
2678		if (this._loaded) {
2679			// @section Map state change events
2680			// @event unload: Event
2681			// Fired when the map is destroyed with [remove](#map-remove) method.
2682			this.fire('unload');
2683		}
2684
2685		for (var i in this._layers) {
2686			this._layers[i].remove();
2687		}
2688
2689		return this;
2690	},
2691
2692	// @section Other Methods
2693	// @method createPane(name: String, container?: HTMLElement): HTMLElement
2694	// Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2695	// then returns it. The pane is created as a children of `container`, or
2696	// as a children of the main map pane if not set.
2697	createPane: function (name, container) {
2698		var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
2699		    pane = L.DomUtil.create('div', className, container || this._mapPane);
2700
2701		if (name) {
2702			this._panes[name] = pane;
2703		}
2704		return pane;
2705	},
2706
2707	// @section Methods for Getting Map State
2708
2709	// @method getCenter(): LatLng
2710	// Returns the geographical center of the map view
2711	getCenter: function () {
2712		this._checkIfLoaded();
2713
2714		if (this._lastCenter && !this._moved()) {
2715			return this._lastCenter;
2716		}
2717		return this.layerPointToLatLng(this._getCenterLayerPoint());
2718	},
2719
2720	// @method getZoom(): Number
2721	// Returns the current zoom level of the map view
2722	getZoom: function () {
2723		return this._zoom;
2724	},
2725
2726	// @method getBounds(): LatLngBounds
2727	// Returns the geographical bounds visible in the current map view
2728	getBounds: function () {
2729		var bounds = this.getPixelBounds(),
2730		    sw = this.unproject(bounds.getBottomLeft()),
2731		    ne = this.unproject(bounds.getTopRight());
2732
2733		return new L.LatLngBounds(sw, ne);
2734	},
2735
2736	// @method getMinZoom(): Number
2737	// Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
2738	getMinZoom: function () {
2739		return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
2740	},
2741
2742	// @method getMaxZoom(): Number
2743	// Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
2744	getMaxZoom: function () {
2745		return this.options.maxZoom === undefined ?
2746			(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
2747			this.options.maxZoom;
2748	},
2749
2750	// @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
2751	// Returns the maximum zoom level on which the given bounds fit to the map
2752	// view in its entirety. If `inside` (optional) is set to `true`, the method
2753	// instead returns the minimum zoom level on which the map view fits into
2754	// the given bounds in its entirety.
2755	getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
2756		bounds = L.latLngBounds(bounds);
2757		padding = L.point(padding || [0, 0]);
2758
2759		var zoom = this.getZoom() || 0,
2760		    min = this.getMinZoom(),
2761		    max = this.getMaxZoom(),
2762		    nw = bounds.getNorthWest(),
2763		    se = bounds.getSouthEast(),
2764		    size = this.getSize().subtract(padding),
2765		    boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)),
2766		    snap = L.Browser.any3d ? this.options.zoomSnap : 1;
2767
2768		var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
2769		zoom = this.getScaleZoom(scale, zoom);
2770
2771		if (snap) {
2772			zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
2773			zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
2774		}
2775
2776		return Math.max(min, Math.min(max, zoom));
2777	},
2778
2779	// @method getSize(): Point
2780	// Returns the current size of the map container (in pixels).
2781	getSize: function () {
2782		if (!this._size || this._sizeChanged) {
2783			this._size = new L.Point(
2784				this._container.clientWidth,
2785				this._container.clientHeight);
2786
2787			this._sizeChanged = false;
2788		}
2789		return this._size.clone();
2790	},
2791
2792	// @method getPixelBounds(): Bounds
2793	// Returns the bounds of the current map view in projected pixel
2794	// coordinates (sometimes useful in layer and overlay implementations).
2795	getPixelBounds: function (center, zoom) {
2796		var topLeftPoint = this._getTopLeftPoint(center, zoom);
2797		return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
2798	},
2799
2800	// TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
2801	// the map pane? "left point of the map layer" can be confusing, specially
2802	// since there can be negative offsets.
2803	// @method getPixelOrigin(): Point
2804	// Returns the projected pixel coordinates of the top left point of
2805	// the map layer (useful in custom layer and overlay implementations).
2806	getPixelOrigin: function () {
2807		this._checkIfLoaded();
2808		return this._pixelOrigin;
2809	},
2810
2811	// @method getPixelWorldBounds(zoom?: Number): Bounds
2812	// Returns the world's bounds in pixel coordinates for zoom level `zoom`.
2813	// If `zoom` is omitted, the map's current zoom level is used.
2814	getPixelWorldBounds: function (zoom) {
2815		return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
2816	},
2817
2818	// @section Other Methods
2819
2820	// @method getPane(pane: String|HTMLElement): HTMLElement
2821	// Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
2822	getPane: function (pane) {
2823		return typeof pane === 'string' ? this._panes[pane] : pane;
2824	},
2825
2826	// @method getPanes(): Object
2827	// Returns a plain object containing the names of all [panes](#map-pane) as keys and
2828	// the panes as values.
2829	getPanes: function () {
2830		return this._panes;
2831	},
2832
2833	// @method getContainer: HTMLElement
2834	// Returns the HTML element that contains the map.
2835	getContainer: function () {
2836		return this._container;
2837	},
2838
2839
2840	// @section Conversion Methods
2841
2842	// @method getZoomScale(toZoom: Number, fromZoom: Number): Number
2843	// Returns the scale factor to be applied to a map transition from zoom level
2844	// `fromZoom` to `toZoom`. Used internally to help with zoom animations.
2845	getZoomScale: function (toZoom, fromZoom) {
2846		// TODO replace with universal implementation after refactoring projections
2847		var crs = this.options.crs;
2848		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2849		return crs.scale(toZoom) / crs.scale(fromZoom);
2850	},
2851
2852	// @method getScaleZoom(scale: Number, fromZoom: Number): Number
2853	// Returns the zoom level that the map would end up at, if it is at `fromZoom`
2854	// level and everything is scaled by a factor of `scale`. Inverse of
2855	// [`getZoomScale`](#map-getZoomScale).
2856	getScaleZoom: function (scale, fromZoom) {
2857		var crs = this.options.crs;
2858		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2859		var zoom = crs.zoom(scale * crs.scale(fromZoom));
2860		return isNaN(zoom) ? Infinity : zoom;
2861	},
2862
2863	// @method project(latlng: LatLng, zoom: Number): Point
2864	// Projects a geographical coordinate `LatLng` according to the projection
2865	// of the map's CRS, then scales it according to `zoom` and the CRS's
2866	// `Transformation`. The result is pixel coordinate relative to
2867	// the CRS origin.
2868	project: function (latlng, zoom) {
2869		zoom = zoom === undefined ? this._zoom : zoom;
2870		return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
2871	},
2872
2873	// @method unproject(point: Point, zoom: Number): LatLng
2874	// Inverse of [`project`](#map-project).
2875	unproject: function (point, zoom) {
2876		zoom = zoom === undefined ? this._zoom : zoom;
2877		return this.options.crs.pointToLatLng(L.point(point), zoom);
2878	},
2879
2880	// @method layerPointToLatLng(point: Point): LatLng
2881	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2882	// returns the corresponding geographical coordinate (for the current zoom level).
2883	layerPointToLatLng: function (point) {
2884		var projectedPoint = L.point(point).add(this.getPixelOrigin());
2885		return this.unproject(projectedPoint);
2886	},
2887
2888	// @method latLngToLayerPoint(latlng: LatLng): Point
2889	// Given a geographical coordinate, returns the corresponding pixel coordinate
2890	// relative to the [origin pixel](#map-getpixelorigin).
2891	latLngToLayerPoint: function (latlng) {
2892		var projectedPoint = this.project(L.latLng(latlng))._round();
2893		return projectedPoint._subtract(this.getPixelOrigin());
2894	},
2895
2896	// @method wrapLatLng(latlng: LatLng): LatLng
2897	// Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
2898	// map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
2899	// CRS's bounds.
2900	// By default this means longitude is wrapped around the dateline so its
2901	// value is between -180 and +180 degrees.
2902	wrapLatLng: function (latlng) {
2903		return this.options.crs.wrapLatLng(L.latLng(latlng));
2904	},
2905
2906	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
2907	// Returns the distance between two geographical coordinates according to
2908	// the map's CRS. By default this measures distance in meters.
2909	distance: function (latlng1, latlng2) {
2910		return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
2911	},
2912
2913	// @method containerPointToLayerPoint(point: Point): Point
2914	// Given a pixel coordinate relative to the map container, returns the corresponding
2915	// pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
2916	containerPointToLayerPoint: function (point) { // (Point)
2917		return L.point(point).subtract(this._getMapPanePos());
2918	},
2919
2920	// @method layerPointToContainerPoint(point: Point): Point
2921	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2922	// returns the corresponding pixel coordinate relative to the map container.
2923	layerPointToContainerPoint: function (point) { // (Point)
2924		return L.point(point).add(this._getMapPanePos());
2925	},
2926
2927	// @method containerPointToLatLng(point: Point): Point
2928	// Given a pixel coordinate relative to the map container, returns
2929	// the corresponding geographical coordinate (for the current zoom level).
2930	containerPointToLatLng: function (point) {
2931		var layerPoint = this.containerPointToLayerPoint(L.point(point));
2932		return this.layerPointToLatLng(layerPoint);
2933	},
2934
2935	// @method latLngToContainerPoint(latlng: LatLng): Point
2936	// Given a geographical coordinate, returns the corresponding pixel coordinate
2937	// relative to the map container.
2938	latLngToContainerPoint: function (latlng) {
2939		return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
2940	},
2941
2942	// @method mouseEventToContainerPoint(ev: MouseEvent): Point
2943	// Given a MouseEvent object, returns the pixel coordinate relative to the
2944	// map container where the event took place.
2945	mouseEventToContainerPoint: function (e) {
2946		return L.DomEvent.getMousePosition(e, this._container);
2947	},
2948
2949	// @method mouseEventToLayerPoint(ev: MouseEvent): Point
2950	// Given a MouseEvent object, returns the pixel coordinate relative to
2951	// the [origin pixel](#map-getpixelorigin) where the event took place.
2952	mouseEventToLayerPoint: function (e) {
2953		return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
2954	},
2955
2956	// @method mouseEventToLatLng(ev: MouseEvent): LatLng
2957	// Given a MouseEvent object, returns geographical coordinate where the
2958	// event took place.
2959	mouseEventToLatLng: function (e) { // (MouseEvent)
2960		return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
2961	},
2962
2963
2964	// map initialization methods
2965
2966	_initContainer: function (id) {
2967		var container = this._container = L.DomUtil.get(id);
2968
2969		if (!container) {
2970			throw new Error('Map container not found.');
2971		} else if (container._leaflet_id) {
2972			throw new Error('Map container is already initialized.');
2973		}
2974
2975		L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
2976		this._containerId = L.Util.stamp(container);
2977	},
2978
2979	_initLayout: function () {
2980		var container = this._container;
2981
2982		this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
2983
2984		L.DomUtil.addClass(container, 'leaflet-container' +
2985			(L.Browser.touch ? ' leaflet-touch' : '') +
2986			(L.Browser.retina ? ' leaflet-retina' : '') +
2987			(L.Browser.ielt9 ? ' leaflet-oldie' : '') +
2988			(L.Browser.safari ? ' leaflet-safari' : '') +
2989			(this._fadeAnimated ? ' leaflet-fade-anim' : ''));
2990
2991		var position = L.DomUtil.getStyle(container, 'position');
2992
2993		if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
2994			container.style.position = 'relative';
2995		}
2996
2997		this._initPanes();
2998
2999		if (this._initControlPos) {
3000			this._initControlPos();
3001		}
3002	},
3003
3004	_initPanes: function () {
3005		var panes = this._panes = {};
3006		this._paneRenderers = {};
3007
3008		// @section
3009		//
3010		// Panes are DOM elements used to control the ordering of layers on the map. You
3011		// can access panes with [`map.getPane`](#map-getpane) or
3012		// [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3013		// [`map.createPane`](#map-createpane) method.
3014		//
3015		// Every map has the following default panes that differ only in zIndex.
3016		//
3017		// @pane mapPane: HTMLElement = 'auto'
3018		// Pane that contains all other map panes
3019
3020		this._mapPane = this.createPane('mapPane', this._container);
3021		L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3022
3023		// @pane tilePane: HTMLElement = 200
3024		// Pane for `GridLayer`s and `TileLayer`s
3025		this.createPane('tilePane');
3026		// @pane overlayPane: HTMLElement = 400
3027		// Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3028		this.createPane('shadowPane');
3029		// @pane shadowPane: HTMLElement = 500
3030		// Pane for overlay shadows (e.g. `Marker` shadows)
3031		this.createPane('overlayPane');
3032		// @pane markerPane: HTMLElement = 600
3033		// Pane for `Icon`s of `Marker`s
3034		this.createPane('markerPane');
3035		// @pane tooltipPane: HTMLElement = 650
3036		// Pane for tooltip.
3037		this.createPane('tooltipPane');
3038		// @pane popupPane: HTMLElement = 700
3039		// Pane for `Popup`s.
3040		this.createPane('popupPane');
3041
3042		if (!this.options.markerZoomAnimation) {
3043			L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3044			L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3045		}
3046	},
3047
3048
3049	// private methods that modify map state
3050
3051	// @section Map state change events
3052	_resetView: function (center, zoom) {
3053		L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3054
3055		var loading = !this._loaded;
3056		this._loaded = true;
3057		zoom = this._limitZoom(zoom);
3058
3059		this.fire('viewprereset');
3060
3061		var zoomChanged = this._zoom !== zoom;
3062		this
3063			._moveStart(zoomChanged)
3064			._move(center, zoom)
3065			._moveEnd(zoomChanged);
3066
3067		// @event viewreset: Event
3068		// Fired when the map needs to redraw its content (this usually happens
3069		// on map zoom or load). Very useful for creating custom overlays.
3070		this.fire('viewreset');
3071
3072		// @event load: Event
3073		// Fired when the map is initialized (when its center and zoom are set
3074		// for the first time).
3075		if (loading) {
3076			this.fire('load');
3077		}
3078	},
3079
3080	_moveStart: function (zoomChanged) {
3081		// @event zoomstart: Event
3082		// Fired when the map zoom is about to change (e.g. before zoom animation).
3083		// @event movestart: Event
3084		// Fired when the view of the map starts changing (e.g. user starts dragging the map).
3085		if (zoomChanged) {
3086			this.fire('zoomstart');
3087		}
3088		return this.fire('movestart');
3089	},
3090
3091	_move: function (center, zoom, data) {
3092		if (zoom === undefined) {
3093			zoom = this._zoom;
3094		}
3095		var zoomChanged = this._zoom !== zoom;
3096
3097		this._zoom = zoom;
3098		this._lastCenter = center;
3099		this._pixelOrigin = this._getNewPixelOrigin(center);
3100
3101		// @event zoom: Event
3102		// Fired repeatedly during any change in zoom level, including zoom
3103		// and fly animations.
3104		if (zoomChanged || (data && data.pinch)) {	// Always fire 'zoom' if pinching because #3530
3105			this.fire('zoom', data);
3106		}
3107
3108		// @event move: Event
3109		// Fired repeatedly during any movement of the map, including pan and
3110		// fly animations.
3111		return this.fire('move', data);
3112	},
3113
3114	_moveEnd: function (zoomChanged) {
3115		// @event zoomend: Event
3116		// Fired when the map has changed, after any animations.
3117		if (zoomChanged) {
3118			this.fire('zoomend');
3119		}
3120
3121		// @event moveend: Event
3122		// Fired when the center of the map stops changing (e.g. user stopped
3123		// dragging the map).
3124		return this.fire('moveend');
3125	},
3126
3127	_stop: function () {
3128		L.Util.cancelAnimFrame(this._flyToFrame);
3129		if (this._panAnim) {
3130			this._panAnim.stop();
3131		}
3132		return this;
3133	},
3134
3135	_rawPanBy: function (offset) {
3136		L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3137	},
3138
3139	_getZoomSpan: function () {
3140		return this.getMaxZoom() - this.getMinZoom();
3141	},
3142
3143	_panInsideMaxBounds: function () {
3144		if (!this._enforcingBounds) {
3145			this.panInsideBounds(this.options.maxBounds);
3146		}
3147	},
3148
3149	_checkIfLoaded: function () {
3150		if (!this._loaded) {
3151			throw new Error('Set map center and zoom first.');
3152		}
3153	},
3154
3155	// DOM event handling
3156
3157	// @section Interaction events
3158	_initEvents: function (remove) {
3159		if (!L.DomEvent) { return; }
3160
3161		this._targets = {};
3162		this._targets[L.stamp(this._container)] = this;
3163
3164		var onOff = remove ? 'off' : 'on';
3165
3166		// @event click: MouseEvent
3167		// Fired when the user clicks (or taps) the map.
3168		// @event dblclick: MouseEvent
3169		// Fired when the user double-clicks (or double-taps) the map.
3170		// @event mousedown: MouseEvent
3171		// Fired when the user pushes the mouse button on the map.
3172		// @event mouseup: MouseEvent
3173		// Fired when the user releases the mouse button on the map.
3174		// @event mouseover: MouseEvent
3175		// Fired when the mouse enters the map.
3176		// @event mouseout: MouseEvent
3177		// Fired when the mouse leaves the map.
3178		// @event mousemove: MouseEvent
3179		// Fired while the mouse moves over the map.
3180		// @event contextmenu: MouseEvent
3181		// Fired when the user pushes the right mouse button on the map, prevents
3182		// default browser context menu from showing if there are listeners on
3183		// this event. Also fired on mobile when the user holds a single touch
3184		// for a second (also called long press).
3185		// @event keypress: KeyboardEvent
3186		// Fired when the user presses a key from the keyboard while the map is focused.
3187		L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3188			'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3189
3190		if (this.options.trackResize) {
3191			L.DomEvent[onOff](window, 'resize', this._onResize, this);
3192		}
3193
3194		if (L.Browser.any3d && this.options.transform3DLimit) {
3195			this[onOff]('moveend', this._onMoveEnd);
3196		}
3197	},
3198
3199	_onResize: function () {
3200		L.Util.cancelAnimFrame(this._resizeRequest);
3201		this._resizeRequest = L.Util.requestAnimFrame(
3202		        function () { this.invalidateSize({debounceMoveend: true}); }, this);
3203	},
3204
3205	_onScroll: function () {
3206		this._container.scrollTop  = 0;
3207		this._container.scrollLeft = 0;
3208	},
3209
3210	_onMoveEnd: function () {
3211		var pos = this._getMapPanePos();
3212		if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3213			// https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3214			// a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3215			this._resetView(this.getCenter(), this.getZoom());
3216		}
3217	},
3218
3219	_findEventTargets: function (e, type) {
3220		var targets = [],
3221		    target,
3222		    isHover = type === 'mouseout' || type === 'mouseover',
3223		    src = e.target || e.srcElement,
3224		    dragging = false;
3225
3226		while (src) {
3227			target = this._targets[L.stamp(src)];
3228			if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3229				// Prevent firing click after you just dragged an object.
3230				dragging = true;
3231				break;
3232			}
3233			if (target && target.listens(type, true)) {
3234				if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3235				targets.push(target);
3236				if (isHover) { break; }
3237			}
3238			if (src === this._container) { break; }
3239			src = src.parentNode;
3240		}
3241		if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3242			targets = [this];
3243		}
3244		return targets;
3245	},
3246
3247	_handleDOMEvent: function (e) {
3248		if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3249
3250		var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3251
3252		if (type === 'mousedown') {
3253			// prevents outline when clicking on keyboard-focusable element
3254			L.DomUtil.preventOutline(e.target || e.srcElement);
3255		}
3256
3257		this._fireDOMEvent(e, type);
3258	},
3259
3260	_fireDOMEvent: function (e, type, targets) {
3261
3262		if (e.type === 'click') {
3263			// Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3264			// @event preclick: MouseEvent
3265			// Fired before mouse click on the map (sometimes useful when you
3266			// want something to happen on click before any existing click
3267			// handlers start running).
3268			var synth = L.Util.extend({}, e);
3269			synth.type = 'preclick';
3270			this._fireDOMEvent(synth, synth.type, targets);
3271		}
3272
3273		if (e._stopped) { return; }
3274
3275		// Find the layer the event is propagating from and its parents.
3276		targets = (targets || []).concat(this._findEventTargets(e, type));
3277
3278		if (!targets.length) { return; }
3279
3280		var target = targets[0];
3281		if (type === 'contextmenu' && target.listens(type, true)) {
3282			L.DomEvent.preventDefault(e);
3283		}
3284
3285		var data = {
3286			originalEvent: e
3287		};
3288
3289		if (e.type !== 'keypress') {
3290			var isMarker = target instanceof L.Marker;
3291			data.containerPoint = isMarker ?
3292					this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3293			data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3294			data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3295		}
3296
3297		for (var i = 0; i < targets.length; i++) {
3298			targets[i].fire(type, data, true);
3299			if (data.originalEvent._stopped ||
3300				(targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3301		}
3302	},
3303
3304	_draggableMoved: function (obj) {
3305		obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3306		return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3307	},
3308
3309	_clearHandlers: function () {
3310		for (var i = 0, len = this._handlers.length; i < len; i++) {
3311			this._handlers[i].disable();
3312		}
3313	},
3314
3315	// @section Other Methods
3316
3317	// @method whenReady(fn: Function, context?: Object): this
3318	// Runs the given function `fn` when the map gets initialized with
3319	// a view (center and zoom) and at least one layer, or immediately
3320	// if it's already initialized, optionally passing a function context.
3321	whenReady: function (callback, context) {
3322		if (this._loaded) {
3323			callback.call(context || this, {target: this});
3324		} else {
3325			this.on('load', callback, context);
3326		}
3327		return this;
3328	},
3329
3330
3331	// private methods for getting map state
3332
3333	_getMapPanePos: function () {
3334		return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3335	},
3336
3337	_moved: function () {
3338		var pos = this._getMapPanePos();
3339		return pos && !pos.equals([0, 0]);
3340	},
3341
3342	_getTopLeftPoint: function (center, zoom) {
3343		var pixelOrigin = center && zoom !== undefined ?
3344			this._getNewPixelOrigin(center, zoom) :
3345			this.getPixelOrigin();
3346		return pixelOrigin.subtract(this._getMapPanePos());
3347	},
3348
3349	_getNewPixelOrigin: function (center, zoom) {
3350		var viewHalf = this.getSize()._divideBy(2);
3351		return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3352	},
3353
3354	_latLngToNewLayerPoint: function (latlng, zoom, center) {
3355		var topLeft = this._getNewPixelOrigin(center, zoom);
3356		return this.project(latlng, zoom)._subtract(topLeft);
3357	},
3358
3359	// layer point of the current center
3360	_getCenterLayerPoint: function () {
3361		return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3362	},
3363
3364	// offset of the specified place to the current center in pixels
3365	_getCenterOffset: function (latlng) {
3366		return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3367	},
3368
3369	// adjust center for view to get inside bounds
3370	_limitCenter: function (center, zoom, bounds) {
3371
3372		if (!bounds) { return center; }
3373
3374		var centerPoint = this.project(center, zoom),
3375		    viewHalf = this.getSize().divideBy(2),
3376		    viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3377		    offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3378
3379		// If offset is less than a pixel, ignore.
3380		// This prevents unstable projections from getting into
3381		// an infinite loop of tiny offsets.
3382		if (offset.round().equals([0, 0])) {
3383			return center;
3384		}
3385
3386		return this.unproject(centerPoint.add(offset), zoom);
3387	},
3388
3389	// adjust offset for view to get inside bounds
3390	_limitOffset: function (offset, bounds) {
3391		if (!bounds) { return offset; }
3392
3393		var viewBounds = this.getPixelBounds(),
3394		    newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3395
3396		return offset.add(this._getBoundsOffset(newBounds, bounds));
3397	},
3398
3399	// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3400	_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3401		var projectedMaxBounds = L.bounds(
3402		        this.project(maxBounds.getNorthEast(), zoom),
3403		        this.project(maxBounds.getSouthWest(), zoom)
3404		    ),
3405		    minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3406		    maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3407
3408		    dx = this._rebound(minOffset.x, -maxOffset.x),
3409		    dy = this._rebound(minOffset.y, -maxOffset.y);
3410
3411		return new L.Point(dx, dy);
3412	},
3413
3414	_rebound: function (left, right) {
3415		return left + right > 0 ?
3416			Math.round(left - right) / 2 :
3417			Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3418	},
3419
3420	_limitZoom: function (zoom) {
3421		var min = this.getMinZoom(),
3422		    max = this.getMaxZoom(),
3423		    snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3424		if (snap) {
3425			zoom = Math.round(zoom / snap) * snap;
3426		}
3427		return Math.max(min, Math.min(max, zoom));
3428	}
3429});
3430
3431// @section
3432
3433// @factory L.map(id: String, options?: Map options)
3434// Instantiates a map object given the DOM ID of a `<div>` element
3435// and optionally an object literal with `Map options`.
3436//
3437// @alternative
3438// @factory L.map(el: HTMLElement, options?: Map options)
3439// Instantiates a map object given an instance of a `<div>` HTML element
3440// and optionally an object literal with `Map options`.
3441L.map = function (id, options) {
3442	return new L.Map(id, options);
3443};
3444
3445
3446
3447
3448/*
3449 * @class Layer
3450 * @inherits Evented
3451 * @aka L.Layer
3452 * @aka ILayer
3453 *
3454 * A set of methods from the Layer base class that all Leaflet layers use.
3455 * Inherits all methods, options and events from `L.Evented`.
3456 *
3457 * @example
3458 *
3459 * ```js
3460 * var layer = L.Marker(latlng).addTo(map);
3461 * layer.addTo(map);
3462 * layer.remove();
3463 * ```
3464 *
3465 * @event add: Event
3466 * Fired after the layer is added to a map
3467 *
3468 * @event remove: Event
3469 * Fired after the layer is removed from a map
3470 */
3471
3472
3473L.Layer = L.Evented.extend({
3474
3475	// Classes extending `L.Layer` will inherit the following options:
3476	options: {
3477		// @option pane: String = 'overlayPane'
3478		// By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
3479		pane: 'overlayPane',
3480		nonBubblingEvents: []  // Array of events that should not be bubbled to DOM parents (like the map)
3481	},
3482
3483	/* @section
3484	 * Classes extending `L.Layer` will inherit the following methods:
3485	 *
3486	 * @method addTo(map: Map): this
3487	 * Adds the layer to the given map
3488	 */
3489	addTo: function (map) {
3490		map.addLayer(this);
3491		return this;
3492	},
3493
3494	// @method remove: this
3495	// Removes the layer from the map it is currently active on.
3496	remove: function () {
3497		return this.removeFrom(this._map || this._mapToAdd);
3498	},
3499
3500	// @method removeFrom(map: Map): this
3501	// Removes the layer from the given map
3502	removeFrom: function (obj) {
3503		if (obj) {
3504			obj.removeLayer(this);
3505		}
3506		return this;
3507	},
3508
3509	// @method getPane(name? : String): HTMLElement
3510	// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3511	getPane: function (name) {
3512		return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3513	},
3514
3515	addInteractiveTarget: function (targetEl) {
3516		this._map._targets[L.stamp(targetEl)] = this;
3517		return this;
3518	},
3519
3520	removeInteractiveTarget: function (targetEl) {
3521		delete this._map._targets[L.stamp(targetEl)];
3522		return this;
3523	},
3524
3525	_layerAdd: function (e) {
3526		var map = e.target;
3527
3528		// check in case layer gets added and then removed before the map is ready
3529		if (!map.hasLayer(this)) { return; }
3530
3531		this._map = map;
3532		this._zoomAnimated = map._zoomAnimated;
3533
3534		if (this.getEvents) {
3535			var events = this.getEvents();
3536			map.on(events, this);
3537			this.once('remove', function () {
3538				map.off(events, this);
3539			}, this);
3540		}
3541
3542		this.onAdd(map);
3543
3544		if (this.getAttribution && this._map.attributionControl) {
3545			this._map.attributionControl.addAttribution(this.getAttribution());
3546		}
3547
3548		this.fire('add');
3549		map.fire('layeradd', {layer: this});
3550	}
3551});
3552
3553/* @section Extension methods
3554 * @uninheritable
3555 *
3556 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
3557 *
3558 * @method onAdd(map: Map): this
3559 * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
3560 *
3561 * @method onRemove(map: Map): this
3562 * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
3563 *
3564 * @method getEvents(): Object
3565 * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
3566 *
3567 * @method getAttribution(): String
3568 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
3569 *
3570 * @method beforeAdd(map: Map): this
3571 * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
3572 */
3573
3574
3575/* @namespace Map
3576 * @section Layer events
3577 *
3578 * @event layeradd: LayerEvent
3579 * Fired when a new layer is added to the map.
3580 *
3581 * @event layerremove: LayerEvent
3582 * Fired when some layer is removed from the map
3583 *
3584 * @section Methods for Layers and Controls
3585 */
3586L.Map.include({
3587	// @method addLayer(layer: Layer): this
3588	// Adds the given layer to the map
3589	addLayer: function (layer) {
3590		var id = L.stamp(layer);
3591		if (this._layers[id]) { return this; }
3592		this._layers[id] = layer;
3593
3594		layer._mapToAdd = this;
3595
3596		if (layer.beforeAdd) {
3597			layer.beforeAdd(this);
3598		}
3599
3600		this.whenReady(layer._layerAdd, layer);
3601
3602		return this;
3603	},
3604
3605	// @method removeLayer(layer: Layer): this
3606	// Removes the given layer from the map.
3607	removeLayer: function (layer) {
3608		var id = L.stamp(layer);
3609
3610		if (!this._layers[id]) { return this; }
3611
3612		if (this._loaded) {
3613			layer.onRemove(this);
3614		}
3615
3616		if (layer.getAttribution && this.attributionControl) {
3617			this.attributionControl.removeAttribution(layer.getAttribution());
3618		}
3619
3620		delete this._layers[id];
3621
3622		if (this._loaded) {
3623			this.fire('layerremove', {layer: layer});
3624			layer.fire('remove');
3625		}
3626
3627		layer._map = layer._mapToAdd = null;
3628
3629		return this;
3630	},
3631
3632	// @method hasLayer(layer: Layer): Boolean
3633	// Returns `true` if the given layer is currently added to the map
3634	hasLayer: function (layer) {
3635		return !!layer && (L.stamp(layer) in this._layers);
3636	},
3637
3638	/* @method eachLayer(fn: Function, context?: Object): this
3639	 * Iterates over the layers of the map, optionally specifying context of the iterator function.
3640	 * ```
3641	 * map.eachLayer(function(layer){
3642	 *     layer.bindPopup('Hello');
3643	 * });
3644	 * ```
3645	 */
3646	eachLayer: function (method, context) {
3647		for (var i in this._layers) {
3648			method.call(context, this._layers[i]);
3649		}
3650		return this;
3651	},
3652
3653	_addLayers: function (layers) {
3654		layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
3655
3656		for (var i = 0, len = layers.length; i < len; i++) {
3657			this.addLayer(layers[i]);
3658		}
3659	},
3660
3661	_addZoomLimit: function (layer) {
3662		if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
3663			this._zoomBoundLayers[L.stamp(layer)] = layer;
3664			this._updateZoomLevels();
3665		}
3666	},
3667
3668	_removeZoomLimit: function (layer) {
3669		var id = L.stamp(layer);
3670
3671		if (this._zoomBoundLayers[id]) {
3672			delete this._zoomBoundLayers[id];
3673			this._updateZoomLevels();
3674		}
3675	},
3676
3677	_updateZoomLevels: function () {
3678		var minZoom = Infinity,
3679		    maxZoom = -Infinity,
3680		    oldZoomSpan = this._getZoomSpan();
3681
3682		for (var i in this._zoomBoundLayers) {
3683			var options = this._zoomBoundLayers[i].options;
3684
3685			minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
3686			maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
3687		}
3688
3689		this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
3690		this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
3691
3692		// @section Map state change events
3693		// @event zoomlevelschange: Event
3694		// Fired when the number of zoomlevels on the map is changed due
3695		// to adding or removing a layer.
3696		if (oldZoomSpan !== this._getZoomSpan()) {
3697			this.fire('zoomlevelschange');
3698		}
3699	}
3700});
3701
3702
3703
3704/*
3705 * @namespace Projection
3706 * @projection L.Projection.Mercator
3707 *
3708 * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
3709 */
3710
3711L.Projection.Mercator = {
3712	R: 6378137,
3713	R_MINOR: 6356752.314245179,
3714
3715	bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
3716
3717	project: function (latlng) {
3718		var d = Math.PI / 180,
3719		    r = this.R,
3720		    y = latlng.lat * d,
3721		    tmp = this.R_MINOR / r,
3722		    e = Math.sqrt(1 - tmp * tmp),
3723		    con = e * Math.sin(y);
3724
3725		var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
3726		y = -r * Math.log(Math.max(ts, 1E-10));
3727
3728		return new L.Point(latlng.lng * d * r, y);
3729	},
3730
3731	unproject: function (point) {
3732		var d = 180 / Math.PI,
3733		    r = this.R,
3734		    tmp = this.R_MINOR / r,
3735		    e = Math.sqrt(1 - tmp * tmp),
3736		    ts = Math.exp(-point.y / r),
3737		    phi = Math.PI / 2 - 2 * Math.atan(ts);
3738
3739		for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
3740			con = e * Math.sin(phi);
3741			con = Math.pow((1 - con) / (1 + con), e / 2);
3742			dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
3743			phi += dphi;
3744		}
3745
3746		return new L.LatLng(phi * d, point.x * d / r);
3747	}
3748};
3749
3750
3751
3752/*
3753 * @namespace CRS
3754 * @crs L.CRS.EPSG3395
3755 *
3756 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
3757 */
3758
3759L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
3760	code: 'EPSG:3395',
3761	projection: L.Projection.Mercator,
3762
3763	transformation: (function () {
3764		var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
3765		return new L.Transformation(scale, 0.5, -scale, 0.5);
3766	}())
3767});
3768
3769
3770
3771/*
3772 * @class GridLayer
3773 * @inherits Layer
3774 * @aka L.GridLayer
3775 *
3776 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
3777 * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
3778 *
3779 *
3780 * @section Synchronous usage
3781 * @example
3782 *
3783 * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
3784 *
3785 * ```js
3786 * var CanvasLayer = L.GridLayer.extend({
3787 *     createTile: function(coords){
3788 *         // create a <canvas> element for drawing
3789 *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3790 *
3791 *         // setup tile width and height according to the options
3792 *         var size = this.getTileSize();
3793 *         tile.width = size.x;
3794 *         tile.height = size.y;
3795 *
3796 *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
3797 *         var ctx = tile.getContext('2d');
3798 *
3799 *         // return the tile so it can be rendered on screen
3800 *         return tile;
3801 *     }
3802 * });
3803 * ```
3804 *
3805 * @section Asynchronous usage
3806 * @example
3807 *
3808 * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
3809 *
3810 * ```js
3811 * var CanvasLayer = L.GridLayer.extend({
3812 *     createTile: function(coords, done){
3813 *         var error;
3814 *
3815 *         // create a <canvas> element for drawing
3816 *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3817 *
3818 *         // setup tile width and height according to the options
3819 *         var size = this.getTileSize();
3820 *         tile.width = size.x;
3821 *         tile.height = size.y;
3822 *
3823 *         // draw something asynchronously and pass the tile to the done() callback
3824 *         setTimeout(function() {
3825 *             done(error, tile);
3826 *         }, 1000);
3827 *
3828 *         return tile;
3829 *     }
3830 * });
3831 * ```
3832 *
3833 * @section
3834 */
3835
3836
3837L.GridLayer = L.Layer.extend({
3838
3839	// @section
3840	// @aka GridLayer options
3841	options: {
vendor: 19,086 bytes, lines 3842-4534
3842		// @option tileSize: Number|Point = 256
3843		// Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
3844		tileSize: 256,
3845
3846		// @option opacity: Number = 1.0
3847		// Opacity of the tiles. Can be used in the `createTile()` function.
3848		opacity: 1,
3849
3850		// @option updateWhenIdle: Boolean = depends
3851		// If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
3852		updateWhenIdle: L.Browser.mobile,
3853
3854		// @option updateWhenZooming: Boolean = true
3855		// By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
3856		updateWhenZooming: true,
3857
3858		// @option updateInterval: Number = 200
3859		// Tiles will not update more than once every `updateInterval` milliseconds when panning.
3860		updateInterval: 200,
3861
3862		// @option attribution: String = null
3863		// String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3864		attribution: null,
3865
3866		// @option zIndex: Number = 1
3867		// The explicit zIndex of the tile layer.
3868		zIndex: 1,
3869
3870		// @option bounds: LatLngBounds = undefined
3871		// If set, tiles will only be loaded inside the set `LatLngBounds`.
3872		bounds: null,
3873
3874		// @option minZoom: Number = 0
3875		// The minimum zoom level that tiles will be loaded at. By default the entire map.
3876		minZoom: 0,
3877
3878		// @option maxZoom: Number = undefined
3879		// The maximum zoom level that tiles will be loaded at.
3880		maxZoom: undefined,
3881
3882		// @option noWrap: Boolean = false
3883		// Whether the layer is wrapped around the antimeridian. If `true`, the
3884		// GridLayer will only be displayed once at low zoom levels. Has no
3885		// effect when the [map CRS](#map-crs) doesn't wrap around.
3886		noWrap: false,
3887
3888		// @option pane: String = 'tilePane'
3889		// `Map pane` where the grid layer will be added.
3890		pane: 'tilePane',
3891
3892		// @option className: String = ''
3893		// A custom class name to assign to the tile layer. Empty by default.
3894		className: '',
3895
3896		// @option keepBuffer: Number = 2
3897		// When panning the map, keep this many rows and columns of tiles before unloading them.
3898		keepBuffer: 2
3899	},
3900
3901	initialize: function (options) {
3902		L.setOptions(this, options);
3903	},
3904
3905	onAdd: function () {
3906		this._initContainer();
3907
3908		this._levels = {};
3909		this._tiles = {};
3910
3911		this._resetView();
3912		this._update();
3913	},
3914
3915	beforeAdd: function (map) {
3916		map._addZoomLimit(this);
3917	},
3918
3919	onRemove: function (map) {
3920		this._removeAllTiles();
3921		L.DomUtil.remove(this._container);
3922		map._removeZoomLimit(this);
3923		this._container = null;
3924		this._tileZoom = null;
3925	},
3926
3927	// @method bringToFront: this
3928	// Brings the tile layer to the top of all tile layers.
3929	bringToFront: function () {
3930		if (this._map) {
3931			L.DomUtil.toFront(this._container);
3932			this._setAutoZIndex(Math.max);
3933		}
3934		return this;
3935	},
3936
3937	// @method bringToBack: this
3938	// Brings the tile layer to the bottom of all tile layers.
3939	bringToBack: function () {
3940		if (this._map) {
3941			L.DomUtil.toBack(this._container);
3942			this._setAutoZIndex(Math.min);
3943		}
3944		return this;
3945	},
3946
3947	// @method getAttribution: String
3948	// Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3949	getAttribution: function () {
3950		return this.options.attribution;
3951	},
3952
3953	// @method getContainer: HTMLElement
3954	// Returns the HTML element that contains the tiles for this layer.
3955	getContainer: function () {
3956		return this._container;
3957	},
3958
3959	// @method setOpacity(opacity: Number): this
3960	// Changes the [opacity](#gridlayer-opacity) of the grid layer.
3961	setOpacity: function (opacity) {
3962		this.options.opacity = opacity;
3963		this._updateOpacity();
3964		return this;
3965	},
3966
3967	// @method setZIndex(zIndex: Number): this
3968	// Changes the [zIndex](#gridlayer-zindex) of the grid layer.
3969	setZIndex: function (zIndex) {
3970		this.options.zIndex = zIndex;
3971		this._updateZIndex();
3972
3973		return this;
3974	},
3975
3976	// @method isLoading: Boolean
3977	// Returns `true` if any tile in the grid layer has not finished loading.
3978	isLoading: function () {
3979		return this._loading;
3980	},
3981
3982	// @method redraw: this
3983	// Causes the layer to clear all the tiles and request them again.
3984	redraw: function () {
3985		if (this._map) {
3986			this._removeAllTiles();
3987			this._update();
3988		}
3989		return this;
3990	},
3991
3992	getEvents: function () {
3993		var events = {
3994			viewprereset: this._invalidateAll,
3995			viewreset: this._resetView,
3996			zoom: this._resetView,
3997			moveend: this._onMoveEnd
3998		};
3999
4000		if (!this.options.updateWhenIdle) {
4001			// update tiles on move, but not more often than once per given interval
4002			if (!this._onMove) {
4003				this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4004			}
4005
4006			events.move = this._onMove;
4007		}
4008
4009		if (this._zoomAnimated) {
4010			events.zoomanim = this._animateZoom;
4011		}
4012
4013		return events;
4014	},
4015
4016	// @section Extension methods
4017	// Layers extending `GridLayer` shall reimplement the following method.
4018	// @method createTile(coords: Object, done?: Function): HTMLElement
4019	// Called only internally, must be overriden by classes extending `GridLayer`.
4020	// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4021	// is specified, it must be called when the tile has finished loading and drawing.
4022	createTile: function () {
4023		return document.createElement('div');
4024	},
4025
4026	// @section
4027	// @method getTileSize: Point
4028	// Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4029	getTileSize: function () {
4030		var s = this.options.tileSize;
4031		return s instanceof L.Point ? s : new L.Point(s, s);
4032	},
4033
4034	_updateZIndex: function () {
4035		if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4036			this._container.style.zIndex = this.options.zIndex;
4037		}
4038	},
4039
4040	_setAutoZIndex: function (compare) {
4041		// go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4042
4043		var layers = this.getPane().children,
4044		    edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4045
4046		for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4047
4048			zIndex = layers[i].style.zIndex;
4049
4050			if (layers[i] !== this._container && zIndex) {
4051				edgeZIndex = compare(edgeZIndex, +zIndex);
4052			}
4053		}
4054
4055		if (isFinite(edgeZIndex)) {
4056			this.options.zIndex = edgeZIndex + compare(-1, 1);
4057			this._updateZIndex();
4058		}
4059	},
4060
4061	_updateOpacity: function () {
4062		if (!this._map) { return; }
4063
4064		// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4065		if (L.Browser.ielt9) { return; }
4066
4067		L.DomUtil.setOpacity(this._container, this.options.opacity);
4068
4069		var now = +new Date(),
4070		    nextFrame = false,
4071		    willPrune = false;
4072
4073		for (var key in this._tiles) {
4074			var tile = this._tiles[key];
4075			if (!tile.current || !tile.loaded) { continue; }
4076
4077			var fade = Math.min(1, (now - tile.loaded) / 200);
4078
4079			L.DomUtil.setOpacity(tile.el, fade);
4080			if (fade < 1) {
4081				nextFrame = true;
4082			} else {
4083				if (tile.active) { willPrune = true; }
4084				tile.active = true;
4085			}
4086		}
4087
4088		if (willPrune && !this._noPrune) { this._pruneTiles(); }
4089
4090		if (nextFrame) {
4091			L.Util.cancelAnimFrame(this._fadeFrame);
4092			this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4093		}
4094	},
4095
4096	_initContainer: function () {
4097		if (this._container) { return; }
4098
4099		this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4100		this._updateZIndex();
4101
4102		if (this.options.opacity < 1) {
4103			this._updateOpacity();
4104		}
4105
4106		this.getPane().appendChild(this._container);
4107	},
4108
4109	_updateLevels: function () {
4110
4111		var zoom = this._tileZoom,
4112		    maxZoom = this.options.maxZoom;
4113
4114		if (zoom === undefined) { return undefined; }
4115
4116		for (var z in this._levels) {
4117			if (this._levels[z].el.children.length || z === zoom) {
4118				this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4119			} else {
4120				L.DomUtil.remove(this._levels[z].el);
4121				this._removeTilesAtZoom(z);
4122				delete this._levels[z];
4123			}
4124		}
4125
4126		var level = this._levels[zoom],
4127		    map = this._map;
4128
4129		if (!level) {
4130			level = this._levels[zoom] = {};
4131
4132			level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4133			level.el.style.zIndex = maxZoom;
4134
4135			level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
4136			level.zoom = zoom;
4137
4138			this._setZoomTransform(level, map.getCenter(), map.getZoom());
4139
4140			// force the browser to consider the newly added element for transition
4141			L.Util.falseFn(level.el.offsetWidth);
4142		}
4143
4144		this._level = level;
4145
4146		return level;
4147	},
4148
4149	_pruneTiles: function () {
4150		if (!this._map) {
4151			return;
4152		}
4153
4154		var key, tile;
4155
4156		var zoom = this._map.getZoom();
4157		if (zoom > this.options.maxZoom ||
4158			zoom < this.options.minZoom) {
4159			this._removeAllTiles();
4160			return;
4161		}
4162
4163		for (key in this._tiles) {
4164			tile = this._tiles[key];
4165			tile.retain = tile.current;
4166		}
4167
4168		for (key in this._tiles) {
4169			tile = this._tiles[key];
4170			if (tile.current && !tile.active) {
4171				var coords = tile.coords;
4172				if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
4173					this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
4174				}
4175			}
4176		}
4177
4178		for (key in this._tiles) {
4179			if (!this._tiles[key].retain) {
4180				this._removeTile(key);
4181			}
4182		}
4183	},
4184
4185	_removeTilesAtZoom: function (zoom) {
4186		for (var key in this._tiles) {
4187			if (this._tiles[key].coords.z !== zoom) {
4188				continue;
4189			}
4190			this._removeTile(key);
4191		}
4192	},
4193
4194	_removeAllTiles: function () {
4195		for (var key in this._tiles) {
4196			this._removeTile(key);
4197		}
4198	},
4199
4200	_invalidateAll: function () {
4201		for (var z in this._levels) {
4202			L.DomUtil.remove(this._levels[z].el);
4203			delete this._levels[z];
4204		}
4205		this._removeAllTiles();
4206
4207		this._tileZoom = null;
4208	},
4209
4210	_retainParent: function (x, y, z, minZoom) {
4211		var x2 = Math.floor(x / 2),
4212		    y2 = Math.floor(y / 2),
4213		    z2 = z - 1,
4214		    coords2 = new L.Point(+x2, +y2);
4215		coords2.z = +z2;
4216
4217		var key = this._tileCoordsToKey(coords2),
4218		    tile = this._tiles[key];
4219
4220		if (tile && tile.active) {
4221			tile.retain = true;
4222			return true;
4223
4224		} else if (tile && tile.loaded) {
4225			tile.retain = true;
4226		}
4227
4228		if (z2 > minZoom) {
4229			return this._retainParent(x2, y2, z2, minZoom);
4230		}
4231
4232		return false;
4233	},
4234
4235	_retainChildren: function (x, y, z, maxZoom) {
4236
4237		for (var i = 2 * x; i < 2 * x + 2; i++) {
4238			for (var j = 2 * y; j < 2 * y + 2; j++) {
4239
4240				var coords = new L.Point(i, j);
4241				coords.z = z + 1;
4242
4243				var key = this._tileCoordsToKey(coords),
4244				    tile = this._tiles[key];
4245
4246				if (tile && tile.active) {
4247					tile.retain = true;
4248					continue;
4249
4250				} else if (tile && tile.loaded) {
4251					tile.retain = true;
4252				}
4253
4254				if (z + 1 < maxZoom) {
4255					this._retainChildren(i, j, z + 1, maxZoom);
4256				}
4257			}
4258		}
4259	},
4260
4261	_resetView: function (e) {
4262		var animating = e && (e.pinch || e.flyTo);
4263		this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
4264	},
4265
4266	_animateZoom: function (e) {
4267		this._setView(e.center, e.zoom, true, e.noUpdate);
4268	},
4269
4270	_setView: function (center, zoom, noPrune, noUpdate) {
4271		var tileZoom = Math.round(zoom);
4272		if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
4273		    (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
4274			tileZoom = undefined;
4275		}
4276
4277		var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
4278
4279		if (!noUpdate || tileZoomChanged) {
4280
4281			this._tileZoom = tileZoom;
4282
4283			if (this._abortLoading) {
4284				this._abortLoading();
4285			}
4286
4287			this._updateLevels();
4288			this._resetGrid();
4289
4290			if (tileZoom !== undefined) {
4291				this._update(center);
4292			}
4293
4294			if (!noPrune) {
4295				this._pruneTiles();
4296			}
4297
4298			// Flag to prevent _updateOpacity from pruning tiles during
4299			// a zoom anim or a pinch gesture
4300			this._noPrune = !!noPrune;
4301		}
4302
4303		this._setZoomTransforms(center, zoom);
4304	},
4305
4306	_setZoomTransforms: function (center, zoom) {
4307		for (var i in this._levels) {
4308			this._setZoomTransform(this._levels[i], center, zoom);
4309		}
4310	},
4311
4312	_setZoomTransform: function (level, center, zoom) {
4313		var scale = this._map.getZoomScale(zoom, level.zoom),
4314		    translate = level.origin.multiplyBy(scale)
4315		        .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
4316
4317		if (L.Browser.any3d) {
4318			L.DomUtil.setTransform(level.el, translate, scale);
4319		} else {
4320			L.DomUtil.setPosition(level.el, translate);
4321		}
4322	},
4323
4324	_resetGrid: function () {
4325		var map = this._map,
4326		    crs = map.options.crs,
4327		    tileSize = this._tileSize = this.getTileSize(),
4328		    tileZoom = this._tileZoom;
4329
4330		var bounds = this._map.getPixelWorldBounds(this._tileZoom);
4331		if (bounds) {
4332			this._globalTileRange = this._pxBoundsToTileRange(bounds);
4333		}
4334
4335		this._wrapX = crs.wrapLng && !this.options.noWrap && [
4336			Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
4337			Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
4338		];
4339		this._wrapY = crs.wrapLat && !this.options.noWrap && [
4340			Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
4341			Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
4342		];
4343	},
4344
4345	_onMoveEnd: function () {
4346		if (!this._map || this._map._animatingZoom) { return; }
4347
4348		this._update();
4349	},
4350
4351	_getTiledPixelBounds: function (center) {
4352		var map = this._map,
4353		    mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
4354		    scale = map.getZoomScale(mapZoom, this._tileZoom),
4355		    pixelCenter = map.project(center, this._tileZoom).floor(),
4356		    halfSize = map.getSize().divideBy(scale * 2);
4357
4358		return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
4359	},
4360
4361	// Private method to load tiles in the grid's active zoom level according to map bounds
4362	_update: function (center) {
4363		var map = this._map;
4364		if (!map) { return; }
4365		var zoom = map.getZoom();
4366
4367		if (center === undefined) { center = map.getCenter(); }
4368		if (this._tileZoom === undefined) { return; }	// if out of minzoom/maxzoom
4369
4370		var pixelBounds = this._getTiledPixelBounds(center),
4371		    tileRange = this._pxBoundsToTileRange(pixelBounds),
4372		    tileCenter = tileRange.getCenter(),
4373		    queue = [],
4374		    margin = this.options.keepBuffer,
4375		    noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
4376		                              tileRange.getTopRight().add([margin, -margin]));
4377
4378		for (var key in this._tiles) {
4379			var c = this._tiles[key].coords;
4380			if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
4381				this._tiles[key].current = false;
4382			}
4383		}
4384
4385		// _update just loads more tiles. If the tile zoom level differs too much
4386		// from the map's, let _setView reset levels and prune old tiles.
4387		if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
4388
4389		// create a queue of coordinates to load tiles from
4390		for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
4391			for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
4392				var coords = new L.Point(i, j);
4393				coords.z = this._tileZoom;
4394
4395				if (!this._isValidTile(coords)) { continue; }
4396
4397				var tile = this._tiles[this._tileCoordsToKey(coords)];
4398				if (tile) {
4399					tile.current = true;
4400				} else {
4401					queue.push(coords);
4402				}
4403			}
4404		}
4405
4406		// sort tile queue to load tiles in order of their distance to center
4407		queue.sort(function (a, b) {
4408			return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
4409		});
4410
4411		if (queue.length !== 0) {
4412			// if it's the first batch of tiles to load
4413			if (!this._loading) {
4414				this._loading = true;
4415				// @event loading: Event
4416				// Fired when the grid layer starts loading tiles.
4417				this.fire('loading');
4418			}
4419
4420			// create DOM fragment to append tiles in one batch
4421			var fragment = document.createDocumentFragment();
4422
4423			for (i = 0; i < queue.length; i++) {
4424				this._addTile(queue[i], fragment);
4425			}
4426
4427			this._level.el.appendChild(fragment);
4428		}
4429	},
4430
4431	_isValidTile: function (coords) {
4432		var crs = this._map.options.crs;
4433
4434		if (!crs.infinite) {
4435			// don't load tile if it's out of bounds and not wrapped
4436			var bounds = this._globalTileRange;
4437			if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
4438			    (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
4439		}
4440
4441		if (!this.options.bounds) { return true; }
4442
4443		// don't load tile if it doesn't intersect the bounds in options
4444		var tileBounds = this._tileCoordsToBounds(coords);
4445		return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
4446	},
4447
4448	_keyToBounds: function (key) {
4449		return this._tileCoordsToBounds(this._keyToTileCoords(key));
4450	},
4451
4452	// converts tile coordinates to its geographical bounds
4453	_tileCoordsToBounds: function (coords) {
4454
4455		var map = this._map,
4456		    tileSize = this.getTileSize(),
4457
4458		    nwPoint = coords.scaleBy(tileSize),
4459		    sePoint = nwPoint.add(tileSize),
4460
4461		    nw = map.unproject(nwPoint, coords.z),
4462		    se = map.unproject(sePoint, coords.z);
4463
4464		if (!this.options.noWrap) {
4465			nw = map.wrapLatLng(nw);
4466			se = map.wrapLatLng(se);
4467		}
4468
4469		return new L.LatLngBounds(nw, se);
4470	},
4471
4472	// converts tile coordinates to key for the tile cache
4473	_tileCoordsToKey: function (coords) {
4474		return coords.x + ':' + coords.y + ':' + coords.z;
4475	},
4476
4477	// converts tile cache key to coordinates
4478	_keyToTileCoords: function (key) {
4479		var k = key.split(':'),
4480		    coords = new L.Point(+k[0], +k[1]);
4481		coords.z = +k[2];
4482		return coords;
4483	},
4484
4485	_removeTile: function (key) {
4486		var tile = this._tiles[key];
4487		if (!tile) { return; }
4488
4489		L.DomUtil.remove(tile.el);
4490
4491		delete this._tiles[key];
4492
4493		// @event tileunload: TileEvent
4494		// Fired when a tile is removed (e.g. when a tile goes off the screen).
4495		this.fire('tileunload', {
4496			tile: tile.el,
4497			coords: this._keyToTileCoords(key)
4498		});
4499	},
4500
4501	_initTile: function (tile) {
4502		L.DomUtil.addClass(tile, 'leaflet-tile');
4503
4504		var tileSize = this.getTileSize();
4505		tile.style.width = tileSize.x + 'px';
4506		tile.style.height = tileSize.y + 'px';
4507
4508		tile.onselectstart = L.Util.falseFn;
4509		tile.onmousemove = L.Util.falseFn;
4510
4511		// update opacity on tiles in IE7-8 because of filter inheritance problems
4512		if (L.Browser.ielt9 && this.options.opacity < 1) {
4513			L.DomUtil.setOpacity(tile, this.options.opacity);
4514		}
4515
4516		// without this hack, tiles disappear after zoom on Chrome for Android
4517		// https://github.com/Leaflet/Leaflet/issues/2078
4518		if (L.Browser.android && !L.Browser.android23) {
4519			tile.style.WebkitBackfaceVisibility = 'hidden';
4520		}
4521	},
4522
4523	_addTile: function (coords, container) {
4524		var tilePos = this._getTilePos(coords),
4525		    key = this._tileCoordsToKey(coords);
4526
4527		var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
4528
4529		this._initTile(tile);
4530
4531		// if createTile is defined with a second argument ("done" callback),
4532		// we know that tile is async and will be ready later; otherwise
4533		if (this.createTile.length < 2) {
4534			// mark tile as ready, but delay one frame for opacity animation to happen
4535			L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
4536		}
4537
4538		L.DomUtil.setPosition(tile, tilePos);
4539
4540		// save tile in cache
4541		this._tiles[key] = {
4542			el: tile,
4543			coords: coords,
4544			current: true
4545		};
4546
4547		container.appendChild(tile);
4548		// @event tileloadstart: TileEvent
4549		// Fired when a tile is requested and starts loading.
4550		this.fire('tileloadstart', {
4551			tile: tile,
4552			coords: coords
4553		});
4554	},
4555
4556	_tileReady: function (coords, err, tile) {
4557		if (!this._map) { return; }
4558
4559		if (err) {
4560			// @event tileerror: TileErrorEvent
4561			// Fired when there is an error loading a tile.
4562			this.fire('tileerror', {
4563				error: err,
4564				tile: tile,
4565				coords: coords
4566			});
4567		}
4568
4569		var key = this._tileCoordsToKey(coords);
4570
4571		tile = this._tiles[key];
4572		if (!tile) { return; }
4573
4574		tile.loaded = +new Date();
4575		if (this._map._fadeAnimated) {
4576			L.DomUtil.setOpacity(tile.el, 0);
4577			L.Util.cancelAnimFrame(this._fadeFrame);
4578			this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4579		} else {
4580			tile.active = true;
4581			this._pruneTiles();
4582		}
4583
4584		if (!err) {
4585			L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
4586
4587			// @event tileload: TileEvent
4588			// Fired when a tile loads.
4589			this.fire('tileload', {
4590				tile: tile.el,
4591				coords: coords
4592			});
4593		}
4594
4595		if (this._noTilesToLoad()) {
4596			this._loading = false;
4597			// @event load: Event
4598			// Fired when the grid layer loaded all visible tiles.
4599			this.fire('load');
4600
4601			if (L.Browser.ielt9 || !this._map._fadeAnimated) {
4602				L.Util.requestAnimFrame(this._pruneTiles, this);
4603			} else {
4604				// Wait a bit more than 0.2 secs (the duration of the tile fade-in)
4605				// to trigger a pruning.
4606				setTimeout(L.bind(this._pruneTiles, this), 250);
4607			}
4608		}
4609	},
4610
4611	_getTilePos: function (coords) {
4612		return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
4613	},
4614
4615	_wrapCoords: function (coords) {
4616		var newCoords = new L.Point(
4617			this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
4618			this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
4619		newCoords.z = coords.z;
4620		return newCoords;
4621	},
4622
4623	_pxBoundsToTileRange: function (bounds) {
4624		var tileSize = this.getTileSize();
4625		return new L.Bounds(
4626			bounds.min.unscaleBy(tileSize).floor(),
4627			bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
4628	},
4629
4630	_noTilesToLoad: function () {
4631		for (var key in this._tiles) {
4632			if (!this._tiles[key].loaded) { return false; }
4633		}
4634		return true;
4635	}
4636});
4637
4638// @factory L.gridLayer(options?: GridLayer options)
4639// Creates a new instance of GridLayer with the supplied options.
4640L.gridLayer = function (options) {
4641	return new L.GridLayer(options);
4642};
4643
4644
4645
4646/*
4647 * @class TileLayer
4648 * @inherits GridLayer
4649 * @aka L.TileLayer
4650 * Used to load and display tile layers on the map. Extends `GridLayer`.
4651 *
4652 * @example
4653 *
4654 * ```js
4655 * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
4656 * ```
4657 *
4658 * @section URL template
4659 * @example
4660 *
4661 * A string of the following form:
4662 *
4663 * ```
4664 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
4665 * ```
4666 *
4667 * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add @2x to the URL to load retina tiles.
4668 *
4669 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
4670 *
4671 * ```
4672 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
4673 * ```
4674 */
4675
4676
4677L.TileLayer = L.GridLayer.extend({
4678
4679	// @section
4680	// @aka TileLayer options
4681	options: {
4682		// @option minZoom: Number = 0
4683		// Minimum zoom number.
4684		minZoom: 0,
4685
4686		// @option maxZoom: Number = 18
4687		// Maximum zoom number.
4688		maxZoom: 18,
4689
4690		// @option maxNativeZoom: Number = null
4691		// Maximum zoom number the tile source has available. If it is specified,
4692		// the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
4693		// from `maxNativeZoom` level and auto-scaled.
4694		maxNativeZoom: null,
4695
4696		// @option subdomains: String|String[] = 'abc'
4697		// Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of str
4697ings.
4698		subdomains: 'abc',
4699
4700		// @option errorTileUrl: String = ''
4701		// URL to the tile image to show in place of the tile that failed to load.
4702		errorTileUrl: '',
4703
4704		// @option zoomOffset: Number = 0
4705		// The zoom number used in tile URLs will be offset with this value.
4706		zoomOffset: 0,
4707
4708		// @option tms: Boolean = false
4709		// If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
4710		tms: false,
4711
4712		// @option zoomReverse: Boolean = false
4713		// If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
4714		zoomReverse: false,
4715
4716		// @option detectRetina: Boolean = false
4717		// If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
4718		detectRetina: false,
4719
4720		// @option crossOrigin: Boolean = false
4721		// If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
4722		crossOrigin: false
4723	},
4724
4725	initialize: function (url, options) {
4726
4727		this._url = url;
4728
4729		options = L.setOptions(this, options);
4730
4731		// detecting retina displays, adjusting tileSize and zoom levels
4732		if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
4733
4734			options.tileSize = Math.floor(options.tileSize / 2);
4735
4736			if (!options.zoomReverse) {
4737				options.zoomOffset++;
4738				options.maxZoom--;
4739			} else {
4740				options.zoomOffset--;
4741				options.minZoom++;
4742			}
4743
4744			options.minZoom = Math.max(0, options.minZoom);
4745		}
4746
4747		if (typeof options.subdomains === 'string') {
4748			options.subdomains = options.subdomains.split('');
4749		}
4750
4751		// for https://github.com/Leaflet/Leaflet/issues/137
4752		if (!L.Browser.android) {
4753			this.on('tileunload', this._onTileRemove);
4754		}
4755	},
4756
4757	// @method setUrl(url: String, noRedraw?: Boolean): this
4758	// Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
4759	setUrl: function (url, noRedraw) {
4760		this._url = url;
4761
4762		if (!noRedraw) {
4763			this.redraw();
4764		}
4765		return this;
4766	},
4767
4768	// @method createTile(coords: Object, done?: Function): HTMLElement
4769	// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
4770	// to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
4771	// callback is called when the tile has been loaded.
4772	createTile: function (coords, done) {
4773		var tile = document.createElement('img');
4774
4775		L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
4776		L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
4777
4778		if (this.options.crossOrigin) {
4779			tile.crossOrigin = '';
4780		}
4781
4782		/*
4783		 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
4784		 http://www.w3.org/TR/WCAG20-TECHS/H67
4785		*/
4786		tile.alt = '';
4787
4788		tile.src = this.getTileUrl(coords);
4789
4790		return tile;
4791	},
4792
4793	// @section Extension methods
4794	// @uninheritable
4795	// Layers extending `TileLayer` might reimplement the following method.
4796	// @method getTileUrl(coords: Object): String
4797	// Called only internally, returns the URL for a tile given its coordinates.
4798	// Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
4799	getTileUrl: function (coords) {
4800		var data = {
4801			r: L.Browser.retina ? '@2x' : '',
4802			s: this._getSubdomain(coords),
4803			x: coords.x,
4804			y: coords.y,
4805			z: this._getZoomForUrl()
4806		};
4807		if (this._map && !this._map.options.crs.infinite) {
4808			var invertedY = this._globalTileRange.max.y - coords.y;
4809			if (this.options.tms) {
4810				data['y'] = invertedY;
4811			}
4812			data['-y'] = invertedY;
4813		}
4814
4815		return L.Util.template(this._url, L.extend(data, this.options));
4816	},
4817
4818	_tileOnLoad: function (done, tile) {
4819		// For https://github.com/Leaflet/Leaflet/issues/3332
4820		if (L.Browser.ielt9) {
4821			setTimeout(L.bind(done, this, null, tile), 0);
4822		} else {
4823			done(null, tile);
4824		}
4825	},
4826
4827	_tileOnError: function (done, tile, e) {
4828		var errorUrl = this.options.errorTileUrl;
4829		if (errorUrl) {
4830			tile.src = errorUrl;
4831		}
4832		done(e, tile);
4833	},
4834
4835	getTileSize: function () {
4836		var map = this._map,
4837		    tileSize = L.GridLayer.prototype.getTileSize.call(this),
4838		    zoom = this._tileZoom + this.options.zoomOffset,
4839		    zoomN = this.options.maxNativeZoom;
4840
4841		// increase tile size when overscaling
4842		return zoomN !== null && zoom > zoomN ?
4843				tileSize.divideBy(map.getZoomScale(zoomN, zoom)).round() :
4844				tileSize;
4845	},
4846
4847	_onTileRemove: function (e) {
4848		e.tile.onload = null;
4849	},
4850
4851	_getZoomForUrl: function () {
4852
4853		var options = this.options,
4854		    zoom = this._tileZoom;
4855
4856		if (options.zoomReverse) {
4857			zoom = options.maxZoom - zoom;
4858		}
4859
4860		zoom += options.zoomOffset;
4861
4862		return options.maxNativeZoom !== null ? Math.min(zoom, options.maxNativeZoom) : zoom;
4863	},
4864
4865	_getSubdomain: function (tilePoint) {
4866		var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
4867		return this.options.subdomains[index];
4868	},
4869
4870	// stops loading all tiles in the background layer
4871	_abortLoading: function () {
4872		var i, tile;
4873		for (i in this._tiles) {
4874			if (this._tiles[i].coords.z !== this._tileZoom) {
4875				tile = this._tiles[i].el;
4876
4877				tile.onload = L.Util.falseFn;
4878				tile.onerror = L.Util.falseFn;
4879
4880				if (!tile.complete) {
4881					tile.src = L.Util.emptyImageUrl;
4882					L.DomUtil.remove(tile);
4883				}
4884			}
4885		}
4886	}
4887});
4888
4889
4890// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
4891// Instantiates a tile layer object given a `URL template` and optionally an options object.
4892
4893L.tileLayer = function (url, options) {
4894	return new L.TileLayer(url, options);
4895};
4896
4897
4898
4899/*
4900 * @class TileLayer.WMS
4901 * @inherits TileLayer
4902 * @aka L.TileLayer.WMS
4903 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
4904 *
4905 * @example
4906 *
4907 * ```js
4908 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
4909 * 	layers: 'nexrad-n0r-900913',
4910 * 	format: 'image/png',
4911 * 	transparent: true,
4912 * 	attribution: "Weather data © 2012 IEM Nexrad"
4913 * });
4914 * ```
4915 */
4916
4917L.TileLayer.WMS = L.TileLayer.extend({
4918
4919	// @section
4920	// @aka TileLayer.WMS options
4921	// If any custom options not documented here are used, they will be sent to the
4922	// WMS server as extra parameters in each request URL. This can be useful for
4923	// [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
4924	defaultWmsParams: {
4925		service: 'WMS',
4926		request: 'GetMap',
4927
4928		// @option layers: String = ''
4929		// **(required)** Comma-separated list of WMS layers to show.
4930		layers: '',
4931
4932		// @option styles: String = ''
4933		// Comma-separated list of WMS styles.
4934		styles: '',
4935
4936		// @option format: String = 'image/jpeg'
4937		// WMS image format (use `'image/png'` for layers with transparency).
4938		format: 'image/jpeg',
4939
4940		// @option transparent: Boolean = false
4941		// If `true`, the WMS service will return images with transparency.
4942		transparent: false,
4943
4944		// @option version: String = '1.1.1'
4945		// Version of the WMS service to use
4946		version: '1.1.1'
4947	},
4948
4949	options: {
4950		// @option crs: CRS = null
4951		// Coordinate Reference System to use for the WMS requests, defaults to
4952		// map CRS. Don't change this if you're not sure what it means.
4953		crs: null,
4954
4955		// @option uppercase: Boolean = false
4956		// If `true`, WMS request parameter keys will be uppercase.
4957		uppercase: false
4958	},
4959
4960	initialize: function (url, options) {
4961
4962		this._url = url;
4963
4964		var wmsParams = L.extend({}, this.defaultWmsParams);
4965
4966		// all keys that are not TileLayer options go to WMS params
4967		for (var i in options) {
4968			if (!(i in this.options)) {
4969				wmsParams[i] = options[i];
4970			}
4971		}
4972
4973		options = L.setOptions(this, options);
4974
4975		wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
4976
4977		this.wmsParams = wmsParams;
4978	},
4979
4980	onAdd: function (map) {
4981
4982		this._crs = this.options.crs || map.options.crs;
4983		this._wmsVersion = parseFloat(this.wmsParams.version);
4984
4985		var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
4986		this.wmsParams[projectionKey] = this._crs.code;
4987
4988		L.TileLayer.prototype.onAdd.call(this, map);
4989	},
4990
4991	getTileUrl: function (coords) {
4992
4993		var tileBounds = this._tileCoordsToBounds(coords),
4994		    nw = this._crs.project(tileBounds.getNorthWest()),
4995		    se = this._crs.project(tileBounds.getSouthEast()),
4996
4997		    bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
4998			    [se.y, nw.x, nw.y, se.x] :
4999			    [nw.x, se.y, se.x, nw.y]).join(','),
5000
5001		    url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5002
5003		return url +
5004			L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5005			(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5006	},
5007
5008	// @method setParams(params: Object, noRedraw?: Boolean): this
5009	// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5010	setParams: function (params, noRedraw) {
5011
5012		L.extend(this.wmsParams, params);
5013
5014		if (!noRedraw) {
5015			this.redraw();
5016		}
5017
5018		return this;
5019	}
5020});
5021
5022
5023// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5024// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5025L.tileLayer.wms = function (url, options) {
5026	return new L.TileLayer.WMS(url, options);
5027};
5028
5029
5030
5031/*
5032 * @class ImageOverlay
5033 * @aka L.ImageOverlay
5034 * @inherits Interactive layer
5035 *
5036 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5037 *
5038 * @example
5039 *
5040 * ```js
5041 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5042 * 	imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5043 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5044 * ```
5045 */
5046
5047L.ImageOverlay = L.Layer.extend({
5048
5049	// @section
5050	// @aka ImageOverlay options
5051	options: {
5052		// @option opacity: Number = 1.0
5053		// The opacity of the image overlay.
5054		opacity: 1,
5055
5056		// @option alt: String = ''
5057		// Text for the `alt` attribute of the image (useful for accessibility).
5058		alt: '',
5059
5060		// @option interactive: Boolean = false
5061		// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5062		interactive: false,
5063
5064		// @option attribution: String = null
5065		// An optional string containing HTML to be shown on the `Attribution control`
5066		attribution: null,
5067
5068		// @option crossOrigin: Boolean = false
5069		// If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5070		crossOrigin: false
5071	},
5072
5073	initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5074		this._url = url;
5075		this._bounds = L.latLngBounds(bounds);
5076
5077		L.setOptions(this, options);
5078	},
5079
5080	onAdd: function () {
5081		if (!this._image) {
5082			this._initImage();
5083
5084			if (this.options.opacity < 1) {
5085				this._updateOpacity();
5086			}
5087		}
5088
5089		if (this.options.interactive) {
5090			L.DomUtil.addClass(this._image, 'leaflet-interactive');
5091			this.addInteractiveTarget(this._image);
5092		}
5093
5094		this.getPane().appendChild(this._image);
5095		this._reset();
5096	},
5097
5098	onRemove: function () {
5099		L.DomUtil.remove(this._image);
5100		if (this.options.interactive) {
5101			this.removeInteractiveTarget(this._image);
5102		}
5103	},
5104
5105	// @method setOpacity(opacity: Number): this
5106	// Sets the opacity of the overlay.
5107	setOpacity: function (opacity) {
5108		this.options.opacity = opacity;
5109
5110		if (this._image) {
5111			this._updateOpacity();
5112		}
5113		return this;
5114	},
5115
5116	setStyle: function (styleOpts) {
5117		if (styleOpts.opacity) {
5118			this.setOpacity(styleOpts.opacity);
5119		}
5120		return this;
5121	},
5122
5123	// @method bringToFront(): this
5124	// Brings the layer to the top of all overlays.
5125	bringToFront: function () {
5126		if (this._map) {
5127			L.DomUtil.toFront(this._image);
5128		}
5129		return this;
5130	},
5131
5132	// @method bringToBack(): this
5133	// Brings the layer to the bottom of all overlays.
5134	bringToBack: function () {
5135		if (this._map) {
5136			L.DomUtil.toBack(this._image);
5137		}
5138		return this;
5139	},
5140
5141	// @method setUrl(url: String): this
5142	// Changes the URL of the image.
5143	setUrl: function (url) {
5144		this._url = url;
5145
5146		if (this._image) {
5147			this._image.src = url;
5148		}
5149		return this;
5150	},
5151
5152	setBounds: function (bounds) {
5153		this._bounds = bounds;
5154
5155		if (this._map) {
5156			this._reset();
5157		}
5158		return this;
5159	},
5160
5161	getAttribution: function () {
5162		return this.options.attribution;
5163	},
5164
5165	getEvents: function () {
5166		var events = {
5167			zoom: this._reset,
5168			viewreset: this._reset
5169		};
5170
5171		if (this._zoomAnimated) {
5172			events.zoomanim = this._animateZoom;
5173		}
5174
5175		return events;
5176	},
5177
5178	getBounds: function () {
5179		return this._bounds;
5180	},
5181
5182	getElement: function () {
5183		return this._image;
5184	},
5185
5186	_initImage: function () {
5187		var img = this._image = L.DomUtil.create('img',
5188				'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
5189
5190		img.onselectstart = L.Util.falseFn;
5191		img.onmousemove = L.Util.falseFn;
5192
5193		img.onload = L.bind(this.fire, this, 'load');
5194
5195		if (this.options.crossOrigin) {
5196			img.crossOrigin = '';
5197		}
5198
5199		img.src = this._url;
5200		img.alt = this.options.alt;
5201	},
5202
5203	_animateZoom: function (e) {
5204		var scale = this._map.getZoomScale(e.zoom),
5205		    offset = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), e.zoom, e.center);
5206
5207		L.DomUtil.setTransform(this._image, offset, scale);
5208	},
5209
5210	_reset: function () {
5211		var image = this._image,
5212		    bounds = new L.Bounds(
5213		        this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
5214		        this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
5215		    size = bounds.getSize();
5216
5217		L.DomUtil.setPosition(image, bounds.min);
5218
5219		image.style.width  = size.x + 'px';
5220		image.style.height = size.y + 'px';
5221	},
5222
5223	_updateOpacity: function () {
5224		L.DomUtil.setOpacity(this._image, this.options.opacity);
5225	}
5226});
5227
5228// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
5229// Instantiates an image overlay object given the URL of the image and the
5230// geographical bounds it is tied to.
5231L.imageOverlay = function (url, bounds, options) {
5232	return new L.ImageOverlay(url, bounds, options);
5233};
5234
5235
5236
5237/*
5238 * @class Icon
5239 * @aka L.Icon
5240 * @inherits Layer
5241 *
5242 * Represents an icon to provide when creating a marker.
5243 *
5244 * @example
5245 *
5246 * ```js
5247 * var myIcon = L.icon({
5248 *     iconUrl: 'my-icon.png',
5249 *     iconRetinaUrl: '[email protected]',
5250 *     iconSize: [38, 95],
5251 *     iconAnchor: [22, 94],
5252 *     popupAnchor: [-3, -76],
5253 *     shadowUrl: 'my-icon-shadow.png',
5254 *     shadowRetinaUrl: '[email protected]',
5255 *     shadowSize: [68, 95],
5256 *     shadowAnchor: [22, 94]
5257 * });
5258 *
5259 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5260 * ```
5261 *
5262 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
5263 *
5264 */
5265
5266L.Icon = L.Class.extend({
5267
5268	/* @section
5269	 * @aka Icon options
5270	 *
5271	 * @option iconUrl: String = null
5272	 * **(required)** The URL to the icon image (absolute or relative to your script path).
5273	 *
5274	 * @option iconRetinaUrl: String = null
5275	 * The URL to a retina sized version of the icon image (absolute or relative to your
5276	 * script path). Used for Retina screen devices.
5277	 *
5278	 * @option iconSize: Point = null
5279	 * Size of the icon image in pixels.
5280	 *
5281	 * @option iconAnchor: Point = null
5282	 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
5283	 * will be aligned so that this point is at the marker's geographical location. Centered
5284	 * by default if size is specified, also can be set in CSS with negative margins.
5285	 *
5286	 * @option popupAnchor: Point = null
5287	 * The coordinates of the point from which popups will "open", relative to the icon anchor.
5288	 *
5289	 * @option shadowUrl: String = null
5290	 * The URL to the icon shadow image. If not specified, no shadow image will be created.
5291	 *
5292	 * @option shadowRetinaUrl: String = null
5293	 *
5294	 * @option shadowSize: Point = null
5295	 * Size of the shadow image in pixels.
5296	 *
5297	 * @option shadowAnchor: Point = null
5298	 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
5299	 * as iconAnchor if not specified).
5300	 *
5301	 * @option className: String = ''
5302	 * A custom class name to assign to both icon and shadow images. Empty by default.
5303	 */
5304
5305	initialize: function (options) {
5306		L.setOptions(this, options);
5307	},
5308
5309	// @method createIcon(oldIcon?: HTMLElement): HTMLElement
5310	// Called internally when the icon has to be shown, returns a `<img>` HTML element
5311	// styled according to the options.
5312	createIcon: function (oldIcon) {
5313		return this._createIcon('icon', oldIcon);
5314	},
5315
5316	// @method createShadow(oldIcon?: HTMLElement): HTMLElement
5317	// As `createIcon`, but for the shadow beneath it.
5318	createShadow: function (oldIcon) {
5319		return this._createIcon('shadow', oldIcon);
5320	},
5321
5322	_createIcon: function (name, oldIcon) {
5323		var src = this._getIconUrl(name);
5324
5325		if (!src) {
5326			if (name === 'icon') {
5327				throw new Error('iconUrl not set in Icon options (see the docs).');
5328			}
5329			return null;
5330		}
5331
5332		var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
5333		this._setIconStyles(img, name);
5334
5335		return img;
5336	},
5337
5338	_setIconStyles: function (img, name) {
5339		var options = this.options;
5340		var sizeOption = options[name + 'Size'];
5341
5342		if (typeof sizeOption === 'number') {
5343			sizeOption = [sizeOption, sizeOption];
5344		}
5345
5346		var size = L.point(sizeOption),
5347		    anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
5348		            size && size.divideBy(2, true));
5349
5350		img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
5351
5352		if (anchor) {
5353			img.style.marginLeft = (-anchor.x) + 'px';
5354			img.style.marginTop  = (-anchor.y) + 'px';
5355		}
5356
5357		if (size) {
5358			img.style.width  = size.x + 'px';
5359			img.style.height = size.y + 'px';
5360		}
5361	},
5362
5363	_createImg: function (src, el) {
5364		el = el || document.createElement('img');
5365		el.src = src;
5366		return el;
5367	},
5368
5369	_getIconUrl: function (name) {
5370		return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
5371	}
5372});
5373
5374
5375// @factory L.icon(options: Icon options)
5376// Creates an icon instance with the given options.
5377L.icon = function (options) {
5378	return new L.Icon(options);
5379};
5380
5381
5382
5383/*
5384 * @miniclass Icon.Default (Icon)
5385 * @aka L.Icon.Default
5386 * @section
5387 *
5388 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
5389 * no icon is specified. Points to the blue marker image distributed with Leaflet
5390 * releases.
5391 *
5392 * In order to change the default icon, just change the properties of `L.Icon.Default.prototype.options`
5393 * (which is a set of `Icon options`).
5394 */
5395
5396L.Icon.Default = L.Icon.extend({
5397
5398	options: {
5399		iconUrl:       'marker-icon.png',
5400		iconRetinaUrl: 'marker-icon-2x.png',
5401		shadowUrl:     'marker-shadow.png',
5402		iconSize:    [25, 41],
5403		iconAnchor:  [12, 41],
5404		popupAnchor: [1, -34],
5405		tooltipAnchor: [16, -28],
5406		shadowSize:  [41, 41]
5407	},
5408
5409	_getIconUrl: function (name) {
5410		if (!L.Icon.Default.imagePath) {	// Deprecated, backwards-compatibility only
5411			L.Icon.Default.imagePath = this._detectIconPath();
5412		}
5413
5414		// @option imagePath: String
5415		// `L.Icon.Default` will try to auto-detect the absolute location of the
5416		// blue icon images. If you are placing these images in a non-standard
5417		// way, set this option to point to the right absolute path.
5418		return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
5419	},
5420
5421	_detectIconPath: function () {
5422		var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
5423		var path = L.DomUtil.getStyle(el, 'background-image') ||
5424		           L.DomUtil.getStyle(el, 'backgroundImage');	// IE8
5425
5426		document.body.removeChild(el);
5427
5428		return path.indexOf('url') === 0 ?
5429			path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
5430	}
5431});
5432
5433
5434
5435/*
5436 * @class Marker
5437 * @inherits Interactive layer
5438 * @aka L.Marker
5439 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
5440 *
5441 * @example
5442 *
5443 * ```js
5444 * L.marker([50.5, 30.5]).addTo(map);
5445 * ```
5446 */
5447
5448L.Marker = L.Layer.extend({
5449
5450	// @section
5451	// @aka Marker options
5452	options: {
5453		// @option icon: Icon = *
5454		// Icon class to use for rendering the marker. See [Icon documentation](#L.Icon) for details on how to customize the marker icon. If not specified, a new `L.Icon.Default` is used.
5455		icon: new L.Icon.Default(),
5456
5457		// Option inherited from "Interactive layer" abstract class
5458		interactive: true,
5459
5460		// @option draggable: Boolean = false
5461		// Whether the marker is draggable with mouse/touch or not.
5462		draggable: false,
5463
5464		// @option keyboard: Boolean = true
5465		// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
5466		keyboard: true,
5467
5468		// @option title: String = ''
5469		// Text for the browser tooltip that appear on marker hover (no tooltip by default).
5470		title: '',
5471
5472		// @option alt: String = ''
5473		// Text for the `alt` attribute of the icon image (useful for accessibility).
5474		alt: '',
5475
5476		// @option zIndexOffset: Number = 0
5477		// By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
5478		zIndexOffset: 0,
5479
5480		// @option opacity: Number = 1.0
5481		// The opacity of the marker.
5482		opacity: 1,
5483
5484		// @option riseOnHover: Boolean = false
5485		// If `true`, the marker will get on top of others when you hover the mouse over it.
5486		riseOnHover: false,
5487
5488		// @option riseOffset: Number = 250
5489		// The z-index offset used for the `riseOnHover` feature.
5490		riseOffset: 250,
5491
5492		// @option pane: String = 'markerPane'
5493		// `Map pane` where the markers icon will be added.
5494		pane: 'markerPane',
5495
5496		// FIXME: shadowPane is no longer a valid option
5497		nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
5498	},
5499
5500	/* @section
5501	 *
5502	 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
5503	 */
5504
5505	initialize: function (latlng, options) {
5506		L.setOptions(this, options);
5507		this._latlng = L.latLng(latlng);
5508	},
5509
5510	onAdd: function (map) {
5511		this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
5512
5513		if (this._zoomAnimated) {
5514			map.on('zoomanim', this._animateZoom, this);
5515		}
5516
5517		this._initIcon();
5518		this.update();
5519	},
5520
5521	onRemove: function (map) {
5522		if (this.dragging && this.dragging.enabled()) {
5523			this.options.draggable = true;
5524			this.dragging.removeHooks();
5525		}
5526
5527		if (this._zoomAnimated) {
5528			map.off('zoomanim', this._animateZoom, this);
5529		}
5530
5531		this._removeIcon();
5532		this._removeShadow();
5533	},
5534
5535	getEvents: function () {
5536		return {
5537			zoom: this.update,
5538			viewreset: this.update
5539		};
5540	},
5541
5542	// @method getLatLng: LatLng
5543	// Returns the current geographical position of the marker.
5544	getLatLng: function () {
5545		return this._latlng;
5546	},
5547
5548	// @method setLatLng(latlng: LatLng): this
5549	// Changes the marker position to the given point.
5550	setLatLng: function (latlng) {
5551		var oldLatLng = this._latlng;
5552		this._latlng = L.latLng(latlng);
5553		this.update();
5554
5555		// @event move: Event
5556		// Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
5557		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
5558	},
5559
5560	// @method setZIndexOffset(offset: Number): this
5561	// Changes the [zIndex offset](#marker-zindexoffset) of the marker.
5562	setZIndexOffset: function (offset) {
5563		this.options.zIndexOffset = offset;
5564		return this.update();
5565	},
5566
5567	// @method setIcon(icon: Icon): this
5568	// Changes the marker icon.
5569	setIcon: function (icon) {
5570
5571		this.options.icon = icon;
5572
5573		if (this._map) {
5574			this._initIcon();
5575			this.update();
5576		}
5577
5578		if (this._popup) {
5579			this.bindPopup(this._popup, this._popup.options);
5580		}
5581
5582		return this;
5583	},
5584
5585	getElement: function () {
5586		return this._icon;
5587	},
5588
5589	update: function () {
5590
5591		if (this._icon) {
5592			var pos = this._map.latLngToLayerPoint(this._latlng).round();
5593			this._setPos(pos);
5594		}
5595
5596		return this;
5597	},
5598
5599	_initIcon: function () {
5600		var options = this.options,
5601		    classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
5602
5603		var icon = options.icon.createIcon(this._icon),
5604		    addIcon = false;
5605
5606		// if we're not reusing the icon, remove the old one and init new one
5607		if (icon !== this._icon) {
5608			if (this._icon) {
5609				this._removeIcon();
5610			}
5611			addIcon = true;
5612
5613			if (options.title) {
5614				icon.title = options.title;
5615			}
5616			if (options.alt) {
5617				icon.alt = options.alt;
5618			}
5619		}
5620
5621		L.DomUtil.addClass(icon, classToAdd);
5622
5623		if (options.keyboard) {
5624			icon.tabIndex = '0';
5625		}
5626
5627		this._icon = icon;
5628
5629		if (options.riseOnHover) {
5630			this.on({
5631				mouseover: this._bringToFront,
5632				mouseout: this._resetZIndex
5633			});
5634		}
5635
5636		var newShadow = options.icon.createShadow(this._shadow),
5637		    addShadow = false;
5638
5639		if (newShadow !== this._shadow) {
5640			this._removeShadow();
5641			addShadow = true;
5642		}
5643
5644		if (newShadow) {
5645			L.DomUtil.addClass(newShadow, classToAdd);
5646		}
5647		this._shadow = newShadow;
5648
5649
5650		if (options.opacity < 1) {
5651			this._updateOpacity();
5652		}
5653
5654
5655		if (addIcon) {
5656			this.getPane().appendChild(this._icon);
5657		}
5658		this._initInteraction();
5659		if (newShadow && addShadow) {
5660			this.getPane('shadowPane').appendChild(this._shadow);
5661		}
5662	},
5663
5664	_removeIcon: function () {
5665		if (this.options.riseOnHover) {
5666			this.off({
5667				mouseover: this._bringToFront,
5668				mouseout: this._resetZIndex
5669			});
5670		}
5671
5672		L.DomUtil.remove(this._icon);
5673		this.removeInteractiveTarget(this._icon);
5674
5675		this._icon = null;
5676	},
5677
5678	_removeShadow: function () {
5679		if (this._shadow) {
5680			L.DomUtil.remove(this._shadow);
5681		}
5682		this._shadow = null;
5683	},
5684
5685	_setPos: function (pos) {
5686		L.DomUtil.setPosition(this._icon, pos);
5687
5688		if (this._shadow) {
5689			L.DomUtil.setPosition(this._shadow, pos);
5690		}
5691
5692		this._zIndex = pos.y + this.options.zIndexOffset;
5693
5694		this._resetZIndex();
5695	},
5696
5697	_updateZIndex: function (offset) {
5698		this._icon.style.zIndex = this._zIndex + offset;
5699	},
5700
5701	_animateZoom: function (opt) {
5702		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
5703
5704		this._setPos(pos);
5705	},
5706
5707	_initInteraction: function () {
5708
5709		if (!this.options.interactive) { return; }
5710
5711		L.DomUtil.addClass(this._icon, 'leaflet-interactive');
5712
5713		this.addInteractiveTarget(this._icon);
5714
5715		if (L.Handler.MarkerDrag) {
5716			var draggable = this.options.draggable;
5717			if (this.dragging) {
5718				draggable = this.dragging.enabled();
5719				this.dragging.disable();
5720			}
5721
5722			this.dragging = new L.Handler.MarkerDrag(this);
5723
5724			if (draggable) {
5725				this.dragging.enable();
5726			}
5727		}
5728	},
5729
5730	// @method setOpacity(opacity: Number): this
5731	// Changes the opacity of the marker.
5732	setOpacity: function (opacity) {
5733		this.options.opacity = opacity;
5734		if (this._map) {
5735			this._updateOpacity();
5736		}
5737
5738		return this;
5739	},
5740
5741	_updateOpacity: function () {
5742		var opacity = this.options.opacity;
5743
5744		L.DomUtil.setOpacity(this._icon, opacity);
5745
5746		if (this._shadow) {
5747			L.DomUtil.setOpacity(this._shadow, opacity);
5748		}
5749	},
5750
5751	_bringToFront: function () {
5752		this._updateZIndex(this.options.riseOffset);
5753	},
5754
5755	_resetZIndex: function () {
5756		this._updateZIndex(0);
5757	}
5758});
5759
5760
5761// factory L.marker(latlng: LatLng, options? : Marker options)
5762
5763// @factory L.marker(latlng: LatLng, options? : Marker options)
5764// Instantiates a Marker object given a geographical point and optionally an options object.
5765L.marker = function (latlng, options) {
5766	return new L.Marker(latlng, options);
5767};
5768
5769
5770
5771/*
5772 * @class DivIcon
5773 * @aka L.DivIcon
5774 * @inherits Icon
5775 *
5776 * Represents a lightweight icon for markers that uses a simple `<div>`
5777 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
5778 *
5779 * @example
5780 * ```js
5781 * var myIcon = L.divIcon({className: 'my-div-icon'});
5782 * // you can set .my-div-icon styles in CSS
5783 *
5784 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5785 * ```
5786 *
5787 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
5788 */
5789
5790L.DivIcon = L.Icon.extend({
5791	options: {
5792		// @section
5793		// @aka DivIcon options
5794		iconSize: [12, 12], // also can be set through CSS
5795
5796		// iconAnchor: (Point),
5797		// popupAnchor: (Point),
5798
5799		// @option html: String = ''
5800		// Custom HTML code to put inside the div element, empty by default.
5801		html: false,
5802
5803		// @option bgPos: Point = [0, 0]
5804		// Optional relative position of the background, in pixels
5805		bgPos: null,
5806
5807		className: 'leaflet-div-icon'
5808	},
5809
5810	createIcon: function (oldIcon) {
5811		var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
5812		    options = this.options;
5813
5814		div.innerHTML = options.html !== false ? options.html : '';
5815
5816		if (options.bgPos) {
5817			var bgPos = L.point(options.bgPos);
5818			div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
5819		}
5820		this._setIconStyles(div, 'icon');
5821
5822		return div;
5823	},
5824
5825	createShadow: function () {
5826		return null;
5827	}
5828});
5829
5830// @factory L.divIcon(options: DivIcon options)
5831// Creates a `DivIcon` instance with the given options.
5832L.divIcon = function (options) {
5833	return new L.DivIcon(options);
5834};
5835
5836
5837
5838/*
5839 * @class DivOverlay
5840 * @inherits Layer
5841 * @aka L.DivOverlay
5842 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
5843 */
5844
5845// @namespace DivOverlay
5846L.DivOverlay = L.Layer.extend({
5847
5848	// @section
5849	// @aka DivOverlay options
5850	options: {
vendor: 5,258 bytes, lines 5851-6058
5851		// @option offset: Point = Point(0, 7)
5852		// The offset of the popup position. Useful to control the anchor
5853		// of the popup when opening it on some overlays.
5854		offset: [0, 7],
5855
5856		// @option className: String = ''
5857		// A custom CSS class name to assign to the popup.
5858		className: '',
5859
5860		// @option pane: String = 'popupPane'
5861		// `Map pane` where the popup will be added.
5862		pane: 'popupPane'
5863	},
5864
5865	initialize: function (options, source) {
5866		L.setOptions(this, options);
5867
5868		this._source = source;
5869	},
5870
5871	onAdd: function (map) {
5872		this._zoomAnimated = map._zoomAnimated;
5873
5874		if (!this._container) {
5875			this._initLayout();
5876		}
5877
5878		if (map._fadeAnimated) {
5879			L.DomUtil.setOpacity(this._container, 0);
5880		}
5881
5882		clearTimeout(this._removeTimeout);
5883		this.getPane().appendChild(this._container);
5884		this.update();
5885
5886		if (map._fadeAnimated) {
5887			L.DomUtil.setOpacity(this._container, 1);
5888		}
5889
5890		this.bringToFront();
5891	},
5892
5893	onRemove: function (map) {
5894		if (map._fadeAnimated) {
5895			L.DomUtil.setOpacity(this._container, 0);
5896			this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
5897		} else {
5898			L.DomUtil.remove(this._container);
5899		}
5900	},
5901
5902	// @namespace Popup
5903	// @method getLatLng: LatLng
5904	// Returns the geographical point of popup.
5905	getLatLng: function () {
5906		return this._latlng;
5907	},
5908
5909	// @method setLatLng(latlng: LatLng): this
5910	// Sets the geographical point where the popup will open.
5911	setLatLng: function (latlng) {
5912		this._latlng = L.latLng(latlng);
5913		if (this._map) {
5914			this._updatePosition();
5915			this._adjustPan();
5916		}
5917		return this;
5918	},
5919
5920	// @method getContent: String|HTMLElement
5921	// Returns the content of the popup.
5922	getContent: function () {
5923		return this._content;
5924	},
5925
5926	// @method setContent(htmlContent: String|HTMLElement|Function): this
5927	// Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
5928	setContent: function (content) {
5929		this._content = content;
5930		this.update();
5931		return this;
5932	},
5933
5934	// @method getElement: String|HTMLElement
5935	// Alias for [getContent()](#popup-getcontent)
5936	getElement: function () {
5937		return this._container;
5938	},
5939
5940	// @method update: null
5941	// Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
5942	update: function () {
5943		if (!this._map) { return; }
5944
5945		this._container.style.visibility = 'hidden';
5946
5947		this._updateContent();
5948		this._updateLayout();
5949		this._updatePosition();
5950
5951		this._container.style.visibility = '';
5952
5953		this._adjustPan();
5954	},
5955
5956	getEvents: function () {
5957		var events = {
5958			zoom: this._updatePosition,
5959			viewreset: this._updatePosition
5960		};
5961
5962		if (this._zoomAnimated) {
5963			events.zoomanim = this._animateZoom;
5964		}
5965		return events;
5966	},
5967
5968	// @method isOpen: Boolean
5969	// Returns `true` when the popup is visible on the map.
5970	isOpen: function () {
5971		return !!this._map && this._map.hasLayer(this);
5972	},
5973
5974	// @method bringToFront: this
5975	// Brings this popup in front of other popups (in the same map pane).
5976	bringToFront: function () {
5977		if (this._map) {
5978			L.DomUtil.toFront(this._container);
5979		}
5980		return this;
5981	},
5982
5983	// @method bringToBack: this
5984	// Brings this popup to the back of other popups (in the same map pane).
5985	bringToBack: function () {
5986		if (this._map) {
5987			L.DomUtil.toBack(this._container);
5988		}
5989		return this;
5990	},
5991
5992	_updateContent: function () {
5993		if (!this._content) { return; }
5994
5995		var node = this._contentNode;
5996		var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
5997
5998		if (typeof content === 'string') {
5999			node.innerHTML = content;
6000		} else {
6001			while (node.hasChildNodes()) {
6002				node.removeChild(node.firstChild);
6003			}
6004			node.appendChild(content);
6005		}
6006		this.fire('contentupdate');
6007	},
6008
6009	_updatePosition: function () {
6010		if (!this._map) { return; }
6011
6012		var pos = this._map.latLngToLayerPoint(this._latlng),
6013		    offset = L.point(this.options.offset),
6014		    anchor = this._getAnchor();
6015
6016		if (this._zoomAnimated) {
6017			L.DomUtil.setPosition(this._container, pos.add(anchor));
6018		} else {
6019			offset = offset.add(pos).add(anchor);
6020		}
6021
6022		var bottom = this._containerBottom = -offset.y,
6023		    left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6024
6025		// bottom position the popup in case the height of the popup changes (images loading etc)
6026		this._container.style.bottom = bottom + 'px';
6027		this._container.style.left = left + 'px';
6028	},
6029
6030	_getAnchor: function () {
6031		return [0, 0];
6032	}
6033
6034});
6035
6036
6037
6038/*
6039 * @class Popup
6040 * @inherits DivOverlay
6041 * @aka L.Popup
6042 * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6043 * open popups while making sure that only one popup is open at one time
6044 * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6045 *
6046 * @example
6047 *
6048 * If you want to just bind a popup to marker click and then open it, it's really easy:
6049 *
6050 * ```js
6051 * marker.bindPopup(popupContent).openPopup();
6052 * ```
6053 * Path overlays like polylines also have a `bindPopup` method.
6054 * Here's a more complicated way to open a popup on a map:
6055 *
6056 * ```js
6057 * var popup = L.popup()
6058 * 	.setLatLng(latlng)
6059 * 	.setContent('<p>Hello world!<br />This is a nice popup.</p>')
6060 * 	.openOn(map);
6061 * ```
6062 */
6063
6064
6065// @namespace Popup
6066L.Popup = L.DivOverlay.extend({
6067
6068	// @section
6069	// @aka Popup options
6070	options: {
6071		// @option maxWidth: Number = 300
6072		// Max width of the popup, in pixels.
6073		maxWidth: 300,
6074
6075		// @option minWidth: Number = 50
6076		// Min width of the popup, in pixels.
6077		minWidth: 50,
6078
6079		// @option maxHeight: Number = null
6080		// If set, creates a scrollable container of the given height
6081		// inside a popup if its content exceeds it.
6082		maxHeight: null,
6083
6084		// @option autoPan: Boolean = true
6085		// Set it to `false` if you don't want the map to do panning animation
6086		// to fit the opened popup.
6087		autoPan: true,
6088
6089		// @option autoPanPaddingTopLeft: Point = null
6090		// The margin between the popup and the top left corner of the map
6091		// view after autopanning was performed.
6092		autoPanPaddingTopLeft: null,
6093
6094		// @option autoPanPaddingBottomRight: Point = null
6095		// The margin between the popup and the bottom right corner of the map
6096		// view after autopanning was performed.
6097		autoPanPaddingBottomRight: null,
6098
6099		// @option autoPanPadding: Point = Point(5, 5)
6100		// Equivalent of setting both top left and bottom right autopan padding to the same value.
6101		autoPanPadding: [5, 5],
6102
6103		// @option keepInView: Boolean = false
6104		// Set it to `true` if you want to prevent users from panning the popup
6105		// off of the screen while it is open.
6106		keepInView: false,
6107
6108		// @option closeButton: Boolean = true
6109		// Controls the presence of a close button in the popup.
6110		closeButton: true,
6111
6112		// @option autoClose: Boolean = true
6113		// Set it to `false` if you want to override the default behavior of
6114		// the popup closing when user clicks the map (set globally by
6115		// the Map's [closePopupOnClick](#map-closepopuponclick) option).
6116		autoClose: true,
6117
6118		// @option className: String = ''
6119		// A custom CSS class name to assign to the popup.
6120		className: ''
6121	},
6122
6123	// @namespace Popup
6124	// @method openOn(map: Map): this
6125	// Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
6126	openOn: function (map) {
6127		map.openPopup(this);
6128		return this;
6129	},
6130
6131	onAdd: function (map) {
6132		L.DivOverlay.prototype.onAdd.call(this, map);
6133
6134		// @namespace Map
6135		// @section Popup events
6136		// @event popupopen: PopupEvent
6137		// Fired when a popup is opened in the map
6138		map.fire('popupopen', {popup: this});
6139
6140		if (this._source) {
6141			// @namespace Layer
6142			// @section Popup events
6143			// @event popupopen: PopupEvent
6144			// Fired when a popup bound to this layer is opened
6145			this._source.fire('popupopen', {popup: this}, true);
6146			// For non-path layers, we toggle the popup when clicking
6147			// again the layer, so prevent the map to reopen it.
6148			if (!(this._source instanceof L.Path)) {
6149				this._source.on('preclick', L.DomEvent.stopPropagation);
6150			}
6151		}
6152	},
6153
6154	onRemove: function (map) {
6155		L.DivOverlay.prototype.onRemove.call(this, map);
6156
6157		// @namespace Map
6158		// @section Popup events
6159		// @event popupclose: PopupEvent
6160		// Fired when a popup in the map is closed
6161		map.fire('popupclose', {popup: this});
6162
6163		if (this._source) {
6164			// @namespace Layer
6165			// @section Popup events
6166			// @event popupclose: PopupEvent
6167			// Fired when a popup bound to this layer is closed
6168			this._source.fire('popupclose', {popup: this}, true);
6169			if (!(this._source instanceof L.Path)) {
6170				this._source.off('preclick', L.DomEvent.stopPropagation);
6171			}
6172		}
6173	},
6174
6175	getEvents: function () {
6176		var events = L.DivOverlay.prototype.getEvents.call(this);
6177
6178		if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
6179			events.preclick = this._close;
6180		}
6181
6182		if (this.options.keepInView) {
6183			events.moveend = this._adjustPan;
6184		}
6185
6186		return events;
6187	},
6188
6189	_close: function () {
6190		if (this._map) {
6191			this._map.closePopup(this);
6192		}
6193	},
6194
6195	_initLayout: function () {
6196		var prefix = 'leaflet-popup',
6197		    container = this._container = L.DomUtil.create('div',
6198			prefix + ' ' + (this.options.className || '') +
6199			' leaflet-zoom-animated');
6200
6201		if (this.options.closeButton) {
6202			var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
6203			closeButton.href = '#close';
6204			closeButton.innerHTML = '&#215;';
6205
6206			L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
6207		}
6208
6209		var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
6210		this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
6211
6212		L.DomEvent
6213			.disableClickPropagation(wrapper)
6214			.disableScrollPropagation(this._contentNode)
6215			.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
6216
6217		this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
6218		this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
6219	},
6220
6221	_updateLayout: function () {
6222		var container = this._contentNode,
6223		    style = container.style;
6224
6225		style.width = '';
6226		style.whiteSpace = 'nowrap';
6227
6228		var width = container.offsetWidth;
6229		width = Math.min(width, this.options.maxWidth);
6230		width = Math.max(width, this.options.minWidth);
6231
6232		style.width = (width + 1) + 'px';
6233		style.whiteSpace = '';
6234
6235		style.height = '';
6236
6237		var height = container.offsetHeight,
6238		    maxHeight = this.options.maxHeight,
6239		    scrolledClass = 'leaflet-popup-scrolled';
6240
6241		if (maxHeight && height > maxHeight) {
6242			style.height = maxHeight + 'px';
6243			L.DomUtil.addClass(container, scrolledClass);
6244		} else {
6245			L.DomUtil.removeClass(container, scrolledClass);
6246		}
6247
6248		this._containerWidth = this._container.offsetWidth;
6249	},
6250
6251	_animateZoom: function (e) {
6252		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
6253		    anchor = this._getAnchor();
6254		L.DomUtil.setPosition(this._container, pos.add(anchor));
6255	},
6256
6257	_adjustPan: function () {
6258		if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
6259
6260		var map = this._map,
6261		    marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
6262		    containerHeight = this._container.offsetHeight + marginBottom,
6263		    containerWidth = this._containerWidth,
6264		    layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
6265
6266		layerPos._add(L.DomUtil.getPosition(this._container));
6267
6268		var containerPos = map.layerPointToContainerPoint(layerPos),
6269		    padding = L.point(this.options.autoPanPadding),
6270		    paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
6271		    paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
6272		    size = map.getSize(),
6273		    dx = 0,
6274		    dy = 0;
6275
6276		if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
6277			dx = containerPos.x + containerWidth - size.x + paddingBR.x;
6278		}
6279		if (containerPos.x - dx - paddingTL.x < 0) { // left
6280			dx = containerPos.x - paddingTL.x;
6281		}
6282		if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
6283			dy = containerPos.y + containerHeight - size.y + paddingBR.y;
6284		}
6285		if (containerPos.y - dy - paddingTL.y < 0) { // top
6286			dy = containerPos.y - paddingTL.y;
6287		}
6288
6289		// @namespace Map
6290		// @section Popup events
6291		// @event autopanstart: Event
6292		// Fired when the map starts autopanning when opening a popup.
6293		if (dx || dy) {
6294			map
6295			    .fire('autopanstart')
6296			    .panBy([dx, dy]);
6297		}
6298	},
6299
6300	_onCloseButtonClick: function (e) {
6301		this._close();
6302		L.DomEvent.stop(e);
6303	},
6304
6305	_getAnchor: function () {
6306		// Where should we anchor the popup on the source layer?
6307		return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
6308	}
6309
6310});
6311
6312// @namespace Popup
6313// @factory L.popup(options?: Popup options, source?: Layer)
6314// Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
6315L.popup = function (options, source) {
6316	return new L.Popup(options, source);
6317};
6318
6319
6320/* @namespace Map
6321 * @section Interaction Options
6322 * @option closePopupOnClick: Boolean = true
6323 * Set it to `false` if you don't want popups to close when user clicks the map.
6324 */
6325L.Map.mergeOptions({
6326	closePopupOnClick: true
6327});
6328
6329
6330// @namespace Map
6331// @section Methods for Layers and Controls
6332L.Map.include({
6333	// @method openPopup(popup: Popup): this
6334	// Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
6335	// @alternative
6336	// @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
6337	// Creates a popup with the specified content and options and opens it in the given point on a map.
6338	openPopup: function (popup, latlng, options) {
6339		if (!(popup instanceof L.Popup)) {
6340			popup = new L.Popup(options).setContent(popup);
6341		}
6342
6343		if (latlng) {
6344			popup.setLatLng(latlng);
6345		}
6346
6347		if (this.hasLayer(popup)) {
6348			return this;
6349		}
6350
6351		if (this._popup && this._popup.options.autoClose) {
6352			this.closePopup();
6353		}
6354
6355		this._popup = popup;
6356		return this.addLayer(popup);
6357	},
6358
6359	// @method closePopup(popup?: Popup): this
6360	// Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
6361	closePopup: function (popup) {
6362		if (!popup || popup === this._popup) {
6363			popup = this._popup;
6364			this._popup = null;
6365		}
6366		if (popup) {
6367			this.removeLayer(popup);
6368		}
6369		return this;
6370	}
6371});
6372
6373
6374
6375/*
6376 * @namespace Layer
6377 * @section Popup methods example
6378 *
6379 * All layers share a set of methods convenient for binding popups to it.
6380 *
6381 * ```js
6382 * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
6383 * layer.openPopup();
6384 * layer.closePopup();
6385 * ```
6386 *
6387 * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
6388 */
6389
6390// @section Popup methods
6391L.Layer.include({
6392
6393	// @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
6394	// Binds a popup to the layer with the passed `content` and sets up the
6395	// neccessary event listeners. If a `Function` is passed it will receive
6396	// the layer as the first argument and should return a `String` or `HTMLElement`.
6397	bindPopup: function (content, options) {
6398
6399		if (content instanceof L.Popup) {
6400			L.setOptions(content, options);
6401			this._popup = content;
6402			content._source = this;
6403		} else {
6404			if (!this._popup || options) {
6405				this._popup = new L.Popup(options, this);
6406			}
6407			this._popup.setContent(content);
6408		}
6409
6410		if (!this._popupHandlersAdded) {
6411			this.on({
6412				click: this._openPopup,
6413				remove: this.closePopup,
6414				move: this._movePopup
6415			});
6416			this._popupHandlersAdded = true;
6417		}
6418
6419		return this;
6420	},
6421
6422	// @method unbindPopup(): this
6423	// Removes the popup previously bound with `bindPopup`.
6424	unbindPopup: function () {
6425		if (this._popup) {
6426			this.off({
6427				click: this._openPopup,
6428				remove: this.closePopup,
6429				move: this._movePopup
6430			});
6431			this._popupHandlersAdded = false;
6432			this._popup = null;
6433		}
6434		return this;
6435	},
6436
6437	// @method openPopup(latlng?: LatLng): this
6438	// Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
6439	openPopup: function (layer, latlng) {
6440		if (!(layer instanceof L.Layer)) {
6441			latlng = layer;
6442			layer = this;
6443		}
6444
6445		if (layer instanceof L.FeatureGroup) {
6446			for (var id in this._layers) {
6447				layer = this._layers[id];
6448				break;
6449			}
6450		}
6451
6452		if (!latlng) {
6453			latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6454		}
6455
6456		if (this._popup && this._map) {
6457			// set popup source to this layer
6458			this._popup._source = layer;
6459
6460			// update the popup (content, layout, ect...)
6461			this._popup.update();
6462
6463			// open the popup on the map
6464			this._map.openPopup(this._popup, latlng);
6465		}
6466
6467		return this;
6468	},
6469
6470	// @method closePopup(): this
6471	// Closes the popup bound to this layer if it is open.
6472	closePopup: function () {
6473		if (this._popup) {
6474			this._popup._close();
6475		}
6476		return this;
6477	},
6478
6479	// @method togglePopup(): this
6480	// Opens or closes the popup bound to this layer depending on its current state.
6481	togglePopup: function (target) {
6482		if (this._popup) {
6483			if (this._popup._map) {
6484				this.closePopup();
6485			} else {
6486				this.openPopup(target);
6487			}
6488		}
6489		return this;
6490	},
6491
6492	// @method isPopupOpen(): boolean
6493	// Returns `true` if the popup bound to this layer is currently open.
6494	isPopupOpen: function () {
6495		return this._popup.isOpen();
6496	},
6497
6498	// @method setPopupContent(content: String|HTMLElement|Popup): this
6499	// Sets the content of the popup bound to this layer.
6500	setPopupContent: function (content) {
6501		if (this._popup) {
6502			this._popup.setContent(content);
6503		}
6504		return this;
6505	},
6506
6507	// @method getPopup(): Popup
6508	// Returns the popup bound to this layer.
6509	getPopup: function () {
6510		return this._popup;
6511	},
6512
6513	_openPopup: function (e) {
6514		var layer = e.layer || e.target;
6515
6516		if (!this._popup) {
6517			return;
6518		}
6519
6520		if (!this._map) {
6521			return;
6522		}
6523
6524		// prevent map click
6525		L.DomEvent.stop(e);
6526
6527		// if this inherits from Path its a vector and we can just
6528		// open the popup at the new location
6529		if (layer instanceof L.Path) {
6530			this.openPopup(e.layer || e.target, e.latlng);
6531			return;
6532		}
6533
6534		// otherwise treat it like a marker and figure out
6535		// if we should toggle it open/closed
6536		if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
6537			this.closePopup();
6538		} else {
6539			this.openPopup(layer, e.latlng);
6540		}
6541	},
6542
6543	_movePopup: function (e) {
6544		this._popup.setLatLng(e.latlng);
6545	}
6546});
6547
6548
6549
6550/*
6551 * Popup extension to L.Marker, adding popup-related methods.
6552 */
6553
6554L.Marker.include({
6555	_getPopupAnchor: function () {
6556		return this.options.icon.options.popupAnchor || [0, 0];
6557	}
6558});
6559
6560
6561
6562/*
6563 * @class Tooltip
6564 * @inherits DivOverlay
6565 * @aka L.Tooltip
6566 * Used to display small texts on top of map layers.
6567 *
6568 * @example
6569 *
6570 * ```js
6571 * marker.bindTooltip("my tooltip text").openTooltip();
6572 * ```
6573 * Note about tooltip offset. Leaflet takes two options in consideration
6574 * for computing tooltip offseting:
6575 * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
6576 *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
6577 *   move it to the bottom. Negatives will move to the left and top.
6578 * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
6579 *   should adapt this value if you use a custom icon.
6580 */
6581
6582
6583// @namespace Tooltip
6584L.Tooltip = L.DivOverlay.extend({
6585
6586	// @section
6587	// @aka Tooltip options
6588	options: {
6589		// @option pane: String = 'tooltipPane'
6590		// `Map pane` where the tooltip will be added.
6591		pane: 'tooltipPane',
6592
6593		// @option offset: Point = Point(0, 0)
6594		// Optional offset of the tooltip position.
6595		offset: [0, 0],
6596
6597		// @option direction: String = 'auto'
6598		// Direction where to open the tooltip. Possible values are: `right`, `left`,
6599		// `top`, `bottom`, `center`, `auto`.
6600		// `auto` will dynamicaly switch between `right` and `left` according to the tooltip
6601		// position on the map.
6602		direction: 'auto',
6603
6604		// @option permanent: Boolean = false
6605		// Whether to open the tooltip permanently or only on mouseover.
6606		permanent: false,
6607
6608		// @option sticky: Boolean = false
6609		// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
6610		sticky: false,
6611
6612		// @option interactive: Boolean = false
6613		// If true, the tooltip will listen to the feature events.
6614		interactive: false,
6615
6616		// @option opacity: Number = 0.9
6617		// Tooltip container opacity.
6618		opacity: 0.9
6619	},
6620
6621	onAdd: function (map) {
6622		L.DivOverlay.prototype.onAdd.call(this, map);
6623		this.setOpacity(this.options.opacity);
6624
6625		// @namespace Map
6626		// @section Tooltip events
6627		// @event tooltipopen: TooltipEvent
6628		// Fired when a tooltip is opened in the map.
6629		map.fire('tooltipopen', {tooltip: this});
6630
6631		if (this._source) {
6632			// @namespace Layer
6633			// @section Tooltip events
6634			// @event tooltipopen: TooltipEvent
6635			// Fired when a tooltip bound to this layer is opened.
6636			this._source.fire('tooltipopen', {tooltip: this}, true);
6637		}
6638	},
6639
6640	onRemove: function (map) {
6641		L.DivOverlay.prototype.onRemove.call(this, map);
6642
6643		// @namespace Map
6644		// @section Tooltip events
6645		// @event tooltipclose: TooltipEvent
6646		// Fired when a tooltip in the map is closed.
6647		map.fire('tooltipclose', {tooltip: this});
6648
6649		if (this._source) {
6650			// @namespace Layer
6651			// @section Tooltip events
6652			// @event tooltipclose: TooltipEvent
6653			// Fired when a tooltip bound to this layer is closed.
6654			this._source.fire('tooltipclose', {tooltip: this}, true);
6655		}
6656	},
6657
6658	getEvents: function () {
6659		var events = L.DivOverlay.prototype.getEvents.call(this);
6660
6661		if (L.Browser.touch && !this.options.permanent) {
6662			events.preclick = this._close;
6663		}
6664
6665		return events;
6666	},
6667
6668	_close: function () {
6669		if (this._map) {
6670			this._map.closeTooltip(this);
6671		}
6672	},
6673
6674	_initLayout: function () {
6675		var prefix = 'leaflet-tooltip',
6676		    className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6677
6678		this._contentNode = this._container = L.DomUtil.create('div', className);
6679	},
6680
6681	_updateLayout: function () {},
6682
6683	_adjustPan: function () {},
6684
6685	_setPosition: function (pos) {
6686		var map = this._map,
6687		    container = this._container,
6688		    centerPoint = map.latLngToContainerPoint(map.getCenter()),
6689		    tooltipPoint = map.layerPointToContainerPoint(pos),
6690		    direction = this.options.direction,
6691		    tooltipWidth = container.offsetWidth,
6692		    tooltipHeight = container.offsetHeight,
6693		    offset = L.point(this.options.offset),
6694		    anchor = this._getAnchor();
6695
6696		if (direction === 'top') {
6697			pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y));
6698		} else if (direction === 'bottom') {
6699			pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y));
6700		} else if (direction === 'center') {
6701			pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y));
6702		} else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
6703			direction = 'right';
6704			pos = pos.add([offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y]);
6705		} else {
6706			direction = 'left';
6707			pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y));
6708		}
6709
6710		L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
6711		L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
6712		L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
6713		L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
6714		L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
6715		L.DomUtil.setPosition(container, pos);
6716	},
6717
6718	_updatePosition: function () {
6719		var pos = this._map.latLngToLayerPoint(this._latlng);
6720		this._setPosition(pos);
6721	},
6722
6723	setOpacity: function (opacity) {
6724		this.options.opacity = opacity;
6725
6726		if (this._container) {
6727			L.DomUtil.setOpacity(this._container, opacity);
6728		}
6729	},
6730
6731	_animateZoom: function (e) {
6732		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
6733		this._setPosition(pos);
6734	},
6735
6736	_getAnchor: function () {
6737		// Where should we anchor the tooltip on the source layer?
6738		return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
6739	}
6740
6741});
6742
6743// @namespace Tooltip
6744// @factory L.tooltip(options?: Tooltip options, source?: Layer)
6745// Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
6746L.tooltip = function (options, source) {
6747	return new L.Tooltip(options, source);
6748};
6749
6750// @namespace Map
6751// @section Methods for Layers and Controls
6752L.Map.include({
6753
6754	// @method openTooltip(tooltip: Tooltip): this
6755	// Opens the specified tooltip.
6756	// @alternative
6757	// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
6758	// Creates a tooltip with the specified content and options and open it.
6759	openTooltip: function (tooltip, latlng, options) {
6760		if (!(tooltip instanceof L.Tooltip)) {
6761			tooltip = new L.Tooltip(options).setContent(tooltip);
6762		}
6763
6764		if (latlng) {
6765			tooltip.setLatLng(latlng);
6766		}
6767
6768		if (this.hasLayer(tooltip)) {
6769			return this;
6770		}
6771
6772		return this.addLayer(tooltip);
6773	},
6774
6775	// @method closeTooltip(tooltip?: Tooltip): this
6776	// Closes the tooltip given as parameter.
6777	closeTooltip: function (tooltip) {
6778		if (tooltip) {
6779			this.removeLayer(tooltip);
6780		}
6781		return this;
6782	}
6783
6784});
6785
6786
6787
6788/*
6789 * @namespace Layer
6790 * @section Tooltip methods example
6791 *
6792 * All layers share a set of methods convenient for binding tooltips to it.
6793 *
6794 * ```js
6795 * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
6796 * layer.openTooltip();
6797 * layer.closeTooltip();
6798 * ```
6799 */
6800
6801// @section Tooltip methods
6802L.Layer.include({
6803
6804	// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
6805	// Binds a tooltip to the layer with the passed `content` and sets up the
6806	// neccessary event listeners. If a `Function` is passed it will receive
6807	// the layer as the first argument and should return a `String` or `HTMLElement`.
6808	bindTooltip: function (content, options) {
6809
6810		if (content instanceof L.Tooltip) {
6811			L.setOptions(content, options);
6812			this._tooltip = content;
6813			content._source = this;
6814		} else {
6815			if (!this._tooltip || options) {
6816				this._tooltip = L.tooltip(options, this);
6817			}
6818			this._tooltip.setContent(content);
6819
6820		}
6821
6822		this._initTooltipInteractions();
6823
6824		if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
6825			this.openTooltip();
6826		}
6827
6828		return this;
6829	},
6830
6831	// @method unbindTooltip(): this
6832	// Removes the tooltip previously bound with `bindTooltip`.
6833	unbindTooltip: function () {
6834		if (this._tooltip) {
6835			this._initTooltipInteractions(true);
6836			this.closeTooltip();
6837			this._tooltip = null;
6838		}
6839		return this;
6840	},
6841
6842	_initTooltipInteractions: function (remove) {
6843		if (!remove && this._tooltipHandlersAdded) { return; }
6844		var onOff = remove ? 'off' : 'on',
6845		    events = {
6846			remove: this.closeTooltip,
6847			move: this._moveTooltip
6848		    };
6849		if (!this._tooltip.options.permanent) {
6850			events.mouseover = this._openTooltip;
6851			events.mouseout = this.closeTooltip;
6852			if (this._tooltip.options.sticky) {
6853				events.mousemove = this._moveTooltip;
6854			}
6855			if (L.Browser.touch) {
6856				events.click = this._openTooltip;
6857			}
6858		} else {
6859			events.add = this._openTooltip;
6860		}
6861		this[onOff](events);
6862		this._tooltipHandlersAdded = !remove;
6863	},
6864
6865	// @method openTooltip(latlng?: LatLng): this
6866	// Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
6867	openTooltip: function (layer, latlng) {
6868		if (!(layer instanceof L.Layer)) {
6869			latlng = layer;
6870			layer = this;
6871		}
6872
6873		if (layer instanceof L.FeatureGroup) {
6874			for (var id in this._layers) {
6875				layer = this._layers[id];
6876				break;
6877			}
6878		}
6879
6880		if (!latlng) {
6881			latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6882		}
6883
6884		if (this._tooltip && this._map) {
6885
6886			// set tooltip source to this layer
6887			this._tooltip._source = layer;
6888
6889			// update the tooltip (content, layout, ect...)
6890			this._tooltip.update();
6891
6892			// open the tooltip on the map
6893			this._map.openTooltip(this._tooltip, latlng);
6894
6895			// Tooltip container may not be defined if not permanent and never
6896			// opened.
6897			if (this._tooltip.options.interactive && this._tooltip._container) {
6898				L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
6899				this.addInteractiveTarget(this._tooltip._container);
6900			}
6901		}
6902
6903		return this;
6904	},
6905
6906	// @method closeTooltip(): this
6907	// Closes the tooltip bound to this layer if it is open.
6908	closeTooltip: function () {
6909		if (this._tooltip) {
6910			this._tooltip._close();
6911			if (this._tooltip.options.interactive && this._tooltip._container) {
6912				L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
6913				this.removeInteractiveTarget(this._tooltip._container);
6914			}
6915		}
6916		return this;
6917	},
6918
6919	// @method toggleTooltip(): this
6920	// Opens or closes the tooltip bound to this layer depending on its current state.
6921	toggleTooltip: function (target) {
6922		if (this._tooltip) {
6923			if (this._tooltip._map) {
6924				this.closeTooltip();
6925			} else {
6926				this.openTooltip(target);
6927			}
6928		}
6929		return this;
6930	},
6931
6932	// @method isTooltipOpen(): boolean
6933	// Returns `true` if the tooltip bound to this layer is currently open.
6934	isTooltipOpen: function () {
6935		return this._tooltip.isOpen();
6936	},
6937
6938	// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
6939	// Sets the content of the tooltip bound to this layer.
6940	setTooltipContent: function (content) {
6941		if (this._tooltip) {
6942			this._tooltip.setContent(content);
6943		}
6944		return this;
6945	},
6946
6947	// @method getTooltip(): Tooltip
6948	// Returns the tooltip bound to this layer.
6949	getTooltip: function () {
6950		return this._tooltip;
6951	},
6952
6953	_openTooltip: function (e) {
6954		var layer = e.layer || e.target;
6955
6956		if (!this._tooltip || !this._map) {
6957			return;
6958		}
6959		this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
6960	},
6961
6962	_moveTooltip: function (e) {
6963		var latlng = e.latlng, containerPoint, layerPoint;
6964		if (this._tooltip.options.sticky && e.originalEvent) {
6965			containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
6966			layerPoint = this._map.containerPointToLayerPoint(containerPoint);
6967			latlng = this._map.layerPointToLatLng(layerPoint);
6968		}
6969		this._tooltip.setLatLng(latlng);
6970	}
6971});
6972
6973
6974
6975/*
6976 * Tooltip extension to L.Marker, adding tooltip-related methods.
6977 */
6978
6979L.Marker.include({
6980	_getTooltipAnchor: function () {
6981		return this.options.icon.options.tooltipAnchor || [0, 0];
6982	}
6983});
6984
6985
6986
6987/*
6988 * @class LayerGroup
6989 * @aka L.LayerGroup
6990 * @inherits Layer
6991 *
6992 * Used to group several layers and handle them as one. If you add it to the map,
6993 * any layers added or removed from the group will be added/removed on the map as
6994 * well. Extends `Layer`.
6995 *
6996 * @example
6997 *
6998 * ```js
6999 * L.layerGroup([marker1, marker2])
7000 * 	.addLayer(polyline)
7001 * 	.addTo(map);
7002 * ```
7003 */
7004
7005L.LayerGroup = L.Layer.extend({
7006
7007	initialize: function (layers) {
7008		this._layers = {};
7009
7010		var i, len;
7011
7012		if (layers) {
7013			for (i = 0, len = layers.length; i < len; i++) {
7014				this.addLayer(layers[i]);
7015			}
7016		}
7017	},
7018
7019	// @method addLayer(layer: Layer): this
7020	// Adds the given layer to the group.
7021	addLayer: function (layer) {
7022		var id = this.getLayerId(layer);
7023
7024		this._layers[id] = layer;
7025
7026		if (this._map) {
7027			this._map.addLayer(layer);
7028		}
7029
7030		return this;
7031	},
7032
7033	// @method removeLayer(layer: Layer): this
7034	// Removes the given layer from the group.
7035	// @alternative
7036	// @method removeLayer(id: Number): this
7037	// Removes the layer with the given internal ID from the group.
7038	removeLayer: function (layer) {
7039		var id = layer in this._layers ? layer : this.getLayerId(layer);
7040
7041		if (this._map && this._layers[id]) {
7042			this._map.removeLayer(this._layers[id]);
7043		}
7044
7045		delete this._layers[id];
7046
7047		return this;
7048	},
7049
7050	// @method hasLayer(layer: Layer): Boolean
7051	// Returns `true` if the given layer is currently added to the group.
7052	hasLayer: function (layer) {
7053		return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7054	},
7055
7056	// @method clearLayers(): this
7057	// Removes all the layers from the group.
7058	clearLayers: function () {
7059		for (var i in this._layers) {
7060			this.removeLayer(this._layers[i]);
7061		}
7062		return this;
7063	},
7064
7065	// @method invoke(methodName: String, …): this
7066	// Calls `methodName` on every layer contained in this group, passing any
7067	// additional parameters. Has no effect if the layers contained do not
7068	// implement `methodName`.
7069	invoke: function (methodName) {
7070		var args = Array.prototype.slice.call(arguments, 1),
7071		    i, layer;
7072
7073		for (i in this._layers) {
7074			layer = this._layers[i];
7075
7076			if (layer[methodName]) {
7077				layer[methodName].apply(layer, args);
7078			}
7079		}
7080
7081		return this;
7082	},
7083
7084	onAdd: function (map) {
7085		for (var i in this._layers) {
7086			map.addLayer(this._layers[i]);
7087		}
7088	},
7089
7090	onRemove: function (map) {
7091		for (var i in this._layers) {
7092			map.removeLayer(this._layers[i]);
7093		}
7094	},
7095
7096	// @method eachLayer(fn: Function, context?: Object): this
7097	// Iterates over the layers of the group, optionally specifying context of the iterator function.
7098	// ```js
7099	// group.eachLayer(function (layer) {
7100	// 	layer.bindPopup('Hello');
7101	// });
7102	// ```
7103	eachLayer: function (method, context) {
7104		for (var i in this._layers) {
7105			method.call(context, this._layers[i]);
7106		}
7107		return this;
7108	},
7109
7110	// @method getLayer(id: Number): Layer
7111	// Returns the layer with the given internal ID.
7112	getLayer: function (id) {
7113		return this._layers[id];
7114	},
7115
7116	// @method getLayers(): Layer[]
7117	// Returns an array of all the layers added to the group.
7118	getLayers: function () {
7119		var layers = [];
7120
7121		for (var i in this._layers) {
7122			layers.push(this._layers[i]);
7123		}
7124		return layers;
7125	},
7126
7127	// @method setZIndex(zIndex: Number): this
7128	// Calls `setZIndex` on every layer contained in this group, passing the z-index.
7129	setZIndex: function (zIndex) {
7130		return this.invoke('setZIndex', zIndex);
7131	},
7132
7133	// @method getLayerId(layer: Layer): Number
7134	// Returns the internal ID for a layer
7135	getLayerId: function (layer) {
7136		return L.stamp(layer);
7137	}
7138});
7139
7140
7141// @factory L.layerGroup(layers: Layer[])
7142// Create a layer group, optionally given an initial set of layers.
7143L.layerGroup = function (layers) {
7144	return new L.LayerGroup(layers);
7145};
7146
7147
7148
7149/*
7150 * @class FeatureGroup
7151 * @aka L.FeatureGroup
7152 * @inherits LayerGroup
7153 *
7154 * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7155 *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7156 *  * Events are propagated to the `FeatureGroup`, so if the group has an event
7157 * handler, it will handle events from any of the layers. This includes mouse events
7158 * and custom events.
7159 *  * Has `layeradd` and `layerremove` events
7160 *
7161 * @example
7162 *
7163 * ```js
7164 * L.featureGroup([marker1, marker2, polyline])
7165 * 	.bindPopup('Hello world!')
7166 * 	.on('click', function() { alert('Clicked on a member of the group!'); })
7167 * 	.addTo(map);
7168 * ```
7169 */
7170
7171L.FeatureGroup = L.LayerGroup.extend({
7172
7173	addLayer: function (layer) {
7174		if (this.hasLayer(layer)) {
7175			return this;
7176		}
7177
7178		layer.addEventParent(this);
7179
7180		L.LayerGroup.prototype.addLayer.call(this, layer);
7181
7182		// @event layeradd: LayerEvent
7183		// Fired when a layer is added to this `FeatureGroup`
7184		return this.fire('layeradd', {layer: layer});
7185	},
7186
7187	removeLayer: function (layer) {
7188		if (!this.hasLayer(layer)) {
7189			return this;
7190		}
7191		if (layer in this._layers) {
7192			layer = this._layers[layer];
7193		}
7194
7195		layer.removeEventParent(this);
7196
7197		L.LayerGroup.prototype.removeLayer.call(this, layer);
7198
7199		// @event layerremove: LayerEvent
7200		// Fired when a layer is removed from this `FeatureGroup`
7201		return this.fire('layerremove', {layer: layer});
7202	},
7203
7204	// @method setStyle(style: Path options): this
7205	// Sets the given path options to each layer of the group that has a `setStyle` method.
7206	setStyle: function (style) {
7207		return this.invoke('setStyle', style);
7208	},
7209
7210	// @method bringToFront(): this
7211	// Brings the layer group to the top of all other layers
7212	bringToFront: function () {
7213		return this.invoke('bringToFront');
7214	},
7215
7216	// @method bringToBack(): this
7217	// Brings the layer group to the top of all other layers
7218	bringToBack: function () {
7219		return this.invoke('bringToBack');
7220	},
7221
7222	// @method getBounds(): LatLngBounds
7223	// Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7224	getBounds: function () {
7225		var bounds = new L.LatLngBounds();
7226
7227		for (var id in this._layers) {
7228			var layer = this._layers[id];
7229			bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7230		}
7231		return bounds;
7232	}
7233});
7234
7235// @factory L.featureGroup(layers: Layer[])
7236// Create a feature group, optionally given an initial set of layers.
7237L.featureGroup = function (layers) {
7238	return new L.FeatureGroup(layers);
7239};
7240
7241
7242
7243/*
7244 * @class Renderer
7245 * @inherits Layer
7246 * @aka L.Renderer
7247 *
7248 * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
7249 * DOM container of the renderer, its bounds, and its zoom animation.
7250 *
7251 * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
7252 * itself can be added or removed to the map. All paths use a renderer, which can
7253 * be implicit (the map will decide the type of renderer and use it automatically)
7254 * or explicit (using the [`renderer`](#path-renderer) option of the path).
7255 *
7256 * Do not use this class directly, use `SVG` and `Canvas` instead.
7257 *
7258 * @event update: Event
7259 * Fired when the renderer updates its bounds, center and zoom, for example when
7260 * its map has moved
7261 */
7262
7263L.Renderer = L.Layer.extend({
7264
7265	// @section
7266	// @aka Renderer options
7267	options: {
7268		// @option padding: Number = 0.1
7269		// How much to extend the clip area around the map view (relative to its size)
7270		// e.g. 0.1 would be 10% of map view in each direction
7271		padding: 0.1
7272	},
7273
7274	initialize: function (options) {
7275		L.setOptions(this, options);
7276		L.stamp(this);
7277	},
7278
7279	onAdd: function () {
7280		if (!this._container) {
7281			this._initContainer(); // defined by renderer implementations
7282
7283			if (this._zoomAnimated) {
7284				L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
7285			}
7286		}
7287
7288		this.getPane().appendChild(this._container);
7289		this._update();
7290	},
7291
7292	onRemove: function () {
7293		L.DomUtil.remove(this._container);
7294	},
7295
7296	getEvents: function () {
7297		var events = {
7298			viewreset: this._reset,
7299			zoom: this._onZoom,
7300			moveend: this._update
7301		};
7302		if (this._zoomAnimated) {
7303			events.zoomanim = this._onAnimZoom;
7304		}
7305		return events;
7306	},
7307
7308	_onAnimZoom: function (ev) {
7309		this._updateTransform(ev.center, ev.zoom);
7310	},
7311
7312	_onZoom: function () {
7313		this._updateTransform(this._map.getCenter(), this._map.getZoom());
7314	},
7315
7316	_updateTransform: function (center, zoom) {
7317		var scale = this._map.getZoomScale(zoom, this._zoom),
7318		    position = L.DomUtil.getPosition(this._container),
7319		    viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
7320		    currentCenterPoint = this._map.project(this._center, zoom),
7321		    destCenterPoint = this._map.project(center, zoom),
7322		    centerOffset = destCenterPoint.subtract(currentCenterPoint),
7323
7324		    topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
7325
7326		if (L.Browser.any3d) {
7327			L.DomUtil.setTransform(this._container, topLeftOffset, scale);
7328		} else {
7329			L.DomUtil.setPosition(this._container, topLeftOffset);
7330		}
7331	},
7332
7333	_reset: function () {
7334		this._update();
7335		this._updateTransform(this._center, this._zoom);
7336	},
7337
7338	_update: function () {
7339		// Update pixel bounds of renderer container (for positioning/sizing/clipping later)
7340		// Subclasses are responsible of firing the 'update' event.
7341		var p = this.options.padding,
7342		    size = this._map.getSize(),
7343		    min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
7344
7345		this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
7346
7347		this._center = this._map.getCenter();
7348		this._zoom = this._map.getZoom();
7349	}
7350});
7351
7352
7353L.Map.include({
7354	// @namespace Map; @method getRenderer(layer: Path): Renderer
7355	// Returns the instance of `Renderer` that should be used to render the given
7356	// `Path`. It will ensure that the `renderer` options of the map and paths
7357	// are respected, and that the renderers do exist on the map.
7358	getRenderer: function (layer) {
7359		// @namespace Path; @option renderer: Renderer
7360		// Use this specific instance of `Renderer` for this path. Takes
7361		// precedence over the map's [default renderer](#map-renderer).
7362		var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
7363
7364		if (!renderer) {
7365			// @namespace Map; @option preferCanvas: Boolean = false
7366			// Whether `Path`s should be rendered on a `Canvas` renderer.
7367			// By default, all `Path`s are rendered in a `SVG` renderer.
7368			renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
7369		}
7370
7371		if (!this.hasLayer(renderer)) {
7372			this.addLayer(renderer);
7373		}
7374		return renderer;
7375	},
7376
7377	_getPaneRenderer: function (name) {
7378		if (name === 'overlayPane' || name === undefined) {
7379			return false;
7380		}
7381
7382		var renderer = this._paneRenderers[name];
7383		if (renderer === undefined) {
7384			renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
7385			this._paneRenderers[name] = renderer;
7386		}
7387		return renderer;
7388	}
7389});
7390
7391
7392
7393/*
7394 * @class Path
7395 * @aka L.Path
7396 * @inherits Interactive layer
7397 *
7398 * An abstract class that contains options and constants shared between vector
7399 * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7400 */
7401
7402L.Path = L.Layer.extend({
7403
7404	// @section
7405	// @aka Path options
7406	options: {
7407		// @option stroke: Boolean = true
7408		// Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7409		stroke: true,
7410
7411		// @option color: String = '#3388ff'
7412		// Stroke color
7413		color: '#3388ff',
7414
7415		// @option weight: Number = 3
7416		// Stroke width in pixels
7417		weight: 3,
7418
7419		// @option opacity: Number = 1.0
7420		// Stroke opacity
7421		opacity: 1,
7422
7423		// @option lineCap: String= 'round'
7424		// A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7425		lineCap: 'round',
7426
7427		// @option lineJoin: String = 'round'
7428		// A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7429		lineJoin: 'round',
7430
7431		// @option dashArray: String = null
7432		// A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
7433		dashArray: null,
7434
7435		// @option dashOffset: String = null
7436		// A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
7437		dashOffset: null,
7438
7439		// @option fill: Boolean = depends
7440		// Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7441		fill: false,
7442
7443		// @option fillColor: String = *
7444		// Fill color. Defaults to the value of the [`color`](#path-color) option
7445		fillColor: null,
7446
7447		// @option fillOpacity: Number = 0.2
7448		// Fill opacity.
7449		fillOpacity: 0.2,
7450
7451		// @option fillRule: String = 'evenodd'
7452		// A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7453		fillRule: 'evenodd',
7454
7455		// className: '',
7456
7457		// Option inherited from "Interactive layer" abstract class
7458		interactive: true
7459	},
7460
7461	beforeAdd: function (map) {
7462		// Renderer is set here because we need to call renderer.getEvents
7463		// before this.getEvents.
7464		this._renderer = map.getRenderer(this);
7465	},
7466
7467	onAdd: function () {
7468		this._renderer._initPath(this);
7469		this._reset();
7470		this._renderer._addPath(this);
7471		this._renderer.on('update', this._update, this);
7472	},
7473
7474	onRemove: function () {
7475		this._renderer._removePath(this);
7476		this._renderer.off('update', this._update, this);
7477	},
7478
7479	getEvents: function () {
7480		return {
7481			zoomend: this._project,
7482			viewreset: this._reset
7483		};
7484	},
7485
7486	// @method redraw(): this
7487	// Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7488	redraw: function () {
7489		if (this._map) {
7490			this._renderer._updatePath(this);
7491		}
7492		return this;
7493	},
7494
7495	// @method setStyle(style: Path options): this
7496	// Changes the appearance of a Path based on the options in the `Path options` object.
7497	setStyle: function (style) {
7498		L.setOptions(this, style);
7499		if (this._renderer) {
7500			this._renderer._updateStyle(this);
7501		}
7502		return this;
7503	},
7504
7505	// @method bringToFront(): this
7506	// Brings the layer to the top of all path layers.
7507	bringToFront: function () {
7508		if (this._renderer) {
7509			this._renderer._bringToFront(this);
7510		}
7511		return this;
7512	},
7513
7514	// @method bringToBack(): this
7515	// Brings the layer to the bottom of all path layers.
7516	bringToBack: function () {
7517		if (this._renderer) {
7518			this._renderer._bringToBack(this);
7519		}
7520		return this;
7521	},
7522
7523	getElement: function () {
7524		return this._path;
7525	},
7526
7527	_reset: function () {
7528		// defined in children classes
7529		this._project();
7530		this._update();
7531	},
7532
7533	_clickTolerance: function () {
7534		// used when doing hit detection for Canvas layers
7535		return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
7536	}
7537});
7538
7539
7540
7541/*
7542 * @namespace LineUtil
7543 *
7544 * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
7545 */
7546
7547L.LineUtil = {
7548
7549	// Simplify polyline with vertex reduction and Douglas-Peucker simplification.
7550	// Improves rendering performance dramatically by lessening the number of points to draw.
7551
7552	// @function simplify(points: Point[], tolerance: Number): Point[]
7553	// Dramatically reduces the number of points in a polyline while retaining
7554	// its shape and returns a new array of simplified points, using the
7555	// [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
7556	// Used for a huge performance boost when processing/displaying Leaflet polylines for
7557	// each zoom level and also reducing visual noise. tolerance affects the amount of
7558	// simplification (lesser value means higher quality but slower and with more points).
7559	// Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
7560	simplify: function (points, tolerance) {
7561		if (!tolerance || !points.length) {
7562			return points.slice();
7563		}
7564
7565		var sqTolerance = tolerance * tolerance;
7566
7567		// stage 1: vertex reduction
7568		points = this._reducePoints(points, sqTolerance);
7569
7570		// stage 2: Douglas-Peucker simplification
7571		points = this._simplifyDP(points, sqTolerance);
7572
7573		return points;
7574	},
7575
7576	// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
7577	// Returns the distance between point `p` and segment `p1` to `p2`.
7578	pointToSegmentDistance:  function (p, p1, p2) {
7579		return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
7580	},
7581
7582	// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
7583	// Returns the closest point from a point `p` on a segment `p1` to `p2`.
7584	closestPointOnSegment: function (p, p1, p2) {
7585		return this._sqClosestPointOnSegment(p, p1, p2);
7586	},
7587
7588	// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
7589	_simplifyDP: function (points, sqTolerance) {
7590
7591		var len = points.length,
7592		    ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
7593		    markers = new ArrayConstructor(len);
7594
7595		markers[0] = markers[len - 1] = 1;
7596
7597		this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
7598
7599		var i,
7600		    newPoints = [];
7601
7602		for (i = 0; i < len; i++) {
7603			if (markers[i]) {
7604				newPoints.push(points[i]);
7605			}
7606		}
7607
7608		return newPoints;
7609	},
7610
7611	_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
7612
7613		var maxSqDist = 0,
7614		    index, i, sqDist;
7615
7616		for (i = first + 1; i <= last - 1; i++) {
7617			sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
7618
7619			if (sqDist > maxSqDist) {
7620				index = i;
7621				maxSqDist = sqDist;
7622			}
7623		}
7624
7625		if (maxSqDist > sqTolerance) {
7626			markers[index] = 1;
7627
7628			this._simplifyDPStep(points, markers, sqTolerance, first, index);
7629			this._simplifyDPStep(points, markers, sqTolerance, index, last);
7630		}
7631	},
7632
7633	// reduce points that are too close to each other to a single point
7634	_reducePoints: function (points, sqTolerance) {
7635		var reducedPoints = [points[0]];
7636
7637		for (var i = 1, prev = 0, len = points.length; i < len; i++) {
7638			if (this._sqDist(points[i], points[prev]) > sqTolerance) {
7639				reducedPoints.push(points[i]);
7640				prev = i;
7641			}
7642		}
7643		if (prev < len - 1) {
7644			reducedPoints.push(points[len - 1]);
7645		}
7646		return reducedPoints;
7647	},
7648
7649
7650	// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, roun
7650d?: Boolean): Point[]|Boolean
7651	// Clips the segment a to b by rectangular bounds with the
7652	// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
7653	// (modifying the segment points directly!). Used by Leaflet to only show polyline
7654	// points that are on the screen or near, increasing performance.
7655	clipSegment: function (a, b, bounds, useLastCode, round) {
7656		var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
7657		    codeB = this._getBitCode(b, bounds),
7658
7659		    codeOut, p, newCode;
7660
7661		// save 2nd code to avoid calculating it on the next segment
7662		this._lastCode = codeB;
7663
7664		while (true) {
7665			// if a,b is inside the clip window (trivial accept)
7666			if (!(codeA | codeB)) {
7667				return [a, b];
7668			}
7669
7670			// if a,b is outside the clip window (trivial reject)
7671			if (codeA & codeB) {
7672				return false;
7673			}
7674
7675			// other cases
7676			codeOut = codeA || codeB;
7677			p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
7678			newCode = this._getBitCode(p, bounds);
7679
7680			if (codeOut === codeA) {
7681				a = p;
7682				codeA = newCode;
7683			} else {
7684				b = p;
7685				codeB = newCode;
7686			}
7687		}
7688	},
7689
vendor: 4,999 bytes, lines 7690-7907
7690	_getEdgeIntersection: function (a, b, code, bounds, round) {
7691		var dx = b.x - a.x,
7692		    dy = b.y - a.y,
7693		    min = bounds.min,
7694		    max = bounds.max,
7695		    x, y;
7696
7697		if (code & 8) { // top
7698			x = a.x + dx * (max.y - a.y) / dy;
7699			y = max.y;
7700
7701		} else if (code & 4) { // bottom
7702			x = a.x + dx * (min.y - a.y) / dy;
7703			y = min.y;
7704
7705		} else if (code & 2) { // right
7706			x = max.x;
7707			y = a.y + dy * (max.x - a.x) / dx;
7708
7709		} else if (code & 1) { // left
7710			x = min.x;
7711			y = a.y + dy * (min.x - a.x) / dx;
7712		}
7713
7714		return new L.Point(x, y, round);
7715	},
7716
7717	_getBitCode: function (p, bounds) {
7718		var code = 0;
7719
7720		if (p.x < bounds.min.x) { // left
7721			code |= 1;
7722		} else if (p.x > bounds.max.x) { // right
7723			code |= 2;
7724		}
7725
7726		if (p.y < bounds.min.y) { // bottom
7727			code |= 4;
7728		} else if (p.y > bounds.max.y) { // top
7729			code |= 8;
7730		}
7731
7732		return code;
7733	},
7734
7735	// square distance (to avoid unnecessary Math.sqrt calls)
7736	_sqDist: function (p1, p2) {
7737		var dx = p2.x - p1.x,
7738		    dy = p2.y - p1.y;
7739		return dx * dx + dy * dy;
7740	},
7741
7742	// return closest point on segment or distance to that point
7743	_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
7744		var x = p1.x,
7745		    y = p1.y,
7746		    dx = p2.x - x,
7747		    dy = p2.y - y,
7748		    dot = dx * dx + dy * dy,
7749		    t;
7750
7751		if (dot > 0) {
7752			t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
7753
7754			if (t > 1) {
7755				x = p2.x;
7756				y = p2.y;
7757			} else if (t > 0) {
7758				x += dx * t;
7759				y += dy * t;
7760			}
7761		}
7762
7763		dx = p.x - x;
7764		dy = p.y - y;
7765
7766		return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
7767	}
7768};
7769
7770
7771
7772/*
7773 * @class Polyline
7774 * @aka L.Polyline
7775 * @inherits Path
7776 *
7777 * A class for drawing polyline overlays on a map. Extends `Path`.
7778 *
7779 * @example
7780 *
7781 * ```js
7782 * // create a red polyline from an array of LatLng points
7783 * var latlngs = [
7784 * 	[-122.68, 45.51],
7785 * 	[-122.43, 37.77],
7786 * 	[-118.2, 34.04]
7787 * ];
7788 *
7789 * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7790 *
7791 * // zoom the map to the polyline
7792 * map.fitBounds(polyline.getBounds());
7793 * ```
7794 *
7795 * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7796 *
7797 * ```js
7798 * // create a red polyline from an array of arrays of LatLng points
7799 * var latlngs = [
7800 * 	[[-122.68, 45.51],
7801 * 	 [-122.43, 37.77],
7802 * 	 [-118.2, 34.04]],
7803 * 	[[-73.91, 40.78],
7804 * 	 [-87.62, 41.83],
7805 * 	 [-96.72, 32.76]]
7806 * ];
7807 * ```
7808 */
7809
7810L.Polyline = L.Path.extend({
7811
7812	// @section
7813	// @aka Polyline options
7814	options: {
7815		// @option smoothFactor: Number = 1.0
7816		// How much to simplify the polyline on each zoom level. More means
7817		// better performance and smoother look, and less means more accurate representation.
7818		smoothFactor: 1.0,
7819
7820		// @option noClip: Boolean = false
7821		// Disable polyline clipping.
7822		noClip: false
7823	},
7824
7825	initialize: function (latlngs, options) {
7826		L.setOptions(this, options);
7827		this._setLatLngs(latlngs);
7828	},
7829
7830	// @method getLatLngs(): LatLng[]
7831	// Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7832	getLatLngs: function () {
7833		return this._latlngs;
7834	},
7835
7836	// @method setLatLngs(latlngs: LatLng[]): this
7837	// Replaces all the points in the polyline with the given array of geographical points.
7838	setLatLngs: function (latlngs) {
7839		this._setLatLngs(latlngs);
7840		return this.redraw();
7841	},
7842
7843	// @method isEmpty(): Boolean
7844	// Returns `true` if the Polyline has no LatLngs.
7845	isEmpty: function () {
7846		return !this._latlngs.length;
7847	},
7848
7849	closestLayerPoint: function (p) {
7850		var minDistance = Infinity,
7851		    minPoint = null,
7852		    closest = L.LineUtil._sqClosestPointOnSegment,
7853		    p1, p2;
7854
7855		for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7856			var points = this._parts[j];
7857
7858			for (var i = 1, len = points.length; i < len; i++) {
7859				p1 = points[i - 1];
7860				p2 = points[i];
7861
7862				var sqDist = closest(p, p1, p2, true);
7863
7864				if (sqDist < minDistance) {
7865					minDistance = sqDist;
7866					minPoint = closest(p, p1, p2);
7867				}
7868			}
7869		}
7870		if (minPoint) {
7871			minPoint.distance = Math.sqrt(minDistance);
7872		}
7873		return minPoint;
7874	},
7875
7876	// @method getCenter(): LatLng
7877	// Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7878	getCenter: function () {
7879		// throws error when not yet added to map as this center calculation requires projected coordinates
7880		if (!this._map) {
7881			throw new Error('Must add layer to map before using getCenter()');
7882		}
7883
7884		var i, halfDist, segDist, dist, p1, p2, ratio,
7885		    points = this._rings[0],
7886		    len = points.length;
7887
7888		if (!len) { return null; }
7889
7890		// polyline centroid algorithm; only uses the first ring if there are multiple
7891
7892		for (i = 0, halfDist = 0; i < len - 1; i++) {
7893			halfDist += points[i].distanceTo(points[i + 1]) / 2;
7894		}
7895
7896		// The line is so small in the current view that all points are on the same pixel.
7897		if (halfDist === 0) {
7898			return this._map.layerPointToLatLng(points[0]);
7899		}
7900
7901		for (i = 0, dist = 0; i < len - 1; i++) {
7902			p1 = points[i];
7903			p2 = points[i + 1];
7904			segDist = p1.distanceTo(p2);
7905			dist += segDist;
7906
7907			if (dist > halfDist) {
7908				ratio = (dist - halfDist) / segDist;
7909				return this._map.layerPointToLatLng([
7910					p2.x - ratio * (p2.x - p1.x),
7911					p2.y - ratio * (p2.y - p1.y)
7912				]);
7913			}
7914		}
7915	},
7916
7917	// @method getBounds(): LatLngBounds
7918	// Returns the `LatLngBounds` of the path.
7919	getBounds: function () {
7920		return this._bounds;
7921	},
7922
7923	// @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7924	// Adds a given point to the polyline. By default, adds to the first ring of
7925	// the polyline in case of a multi-polyline, but can be overridden by passing
7926	// a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7927	addLatLng: function (latlng, latlngs) {
7928		latlngs = latlngs || this._defaultShape();
7929		latlng = L.latLng(latlng);
7930		latlngs.push(latlng);
7931		this._bounds.extend(latlng);
7932		return this.redraw();
7933	},
7934
7935	_setLatLngs: function (latlngs) {
7936		this._bounds = new L.LatLngBounds();
7937		this._latlngs = this._convertLatLngs(latlngs);
7938	},
7939
7940	_defaultShape: function () {
7941		return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
7942	},
7943
7944	// recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
7945	_convertLatLngs: function (latlngs) {
7946		var result = [],
7947		    flat = L.Polyline._flat(latlngs);
7948
7949		for (var i = 0, len = latlngs.length; i < len; i++) {
7950			if (flat) {
7951				result[i] = L.latLng(latlngs[i]);
7952				this._bounds.extend(result[i]);
7953			} else {
7954				result[i] = this._convertLatLngs(latlngs[i]);
7955			}
7956		}
7957
7958		return result;
7959	},
7960
7961	_project: function () {
7962		var pxBounds = new L.Bounds();
7963		this._rings = [];
7964		this._projectLatlngs(this._latlngs, this._rings, pxBounds);
7965
7966		var w = this._clickTolerance(),
7967		    p = new L.Point(w, w);
7968
7969		if (this._bounds.isValid() && pxBounds.isValid()) {
7970			pxBounds.min._subtract(p);
7971			pxBounds.max._add(p);
7972			this._pxBounds = pxBounds;
7973		}
7974	},
7975
7976	// recursively turns latlngs into a set of rings with projected coordinates
7977	_projectLatlngs: function (latlngs, result, projectedBounds) {
7978		var flat = latlngs[0] instanceof L.LatLng,
7979		    len = latlngs.length,
7980		    i, ring;
7981
7982		if (flat) {
7983			ring = [];
7984			for (i = 0; i < len; i++) {
7985				ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
7986				projectedBounds.extend(ring[i]);
7987			}
7988			result.push(ring);
7989		} else {
7990			for (i = 0; i < len; i++) {
7991				this._projectLatlngs(latlngs[i], result, projectedBounds);
7992			}
7993		}
7994	},
7995
7996	// clip polyline by renderer bounds so that we have less to render for performance
7997	_clipPoints: function () {
7998		var bounds = this._renderer._bounds;
7999
8000		this._parts = [];
8001		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8002			return;
8003		}
8004
8005		if (this.options.noClip) {
8006			this._parts = this._rings;
8007			return;
8008		}
8009
8010		var parts = this._parts,
8011		    i, j, k, len, len2, segment, points;
8012
8013		for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8014			points = this._rings[i];
8015
8016			for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8017				segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8018
8019				if (!segment) { continue; }
8020
8021				parts[k] = parts[k] || [];
8022				parts[k].push(segment[0]);
8023
8024				// if segment goes out of screen, or it's the last one, it's the end of the line part
8025				if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8026					parts[k].push(segment[1]);
8027					k++;
8028				}
8029			}
8030		}
8031	},
8032
8033	// simplify each clipped part of the polyline for performance
8034	_simplifyPoints: function () {
8035		var parts = this._parts,
8036		    tolerance = this.options.smoothFactor;
8037
8038		for (var i = 0, len = parts.length; i < len; i++) {
8039			parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8040		}
8041	},
8042
8043	_update: function () {
8044		if (!this._map) { return; }
8045
8046		this._clipPoints();
8047		this._simplifyPoints();
8048		this._updatePath();
8049	},
8050
8051	_updatePath: function () {
8052		this._renderer._updatePoly(this);
8053	}
8054});
8055
8056// @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8057// Instantiates a polyline object given an array of geographical points and
8058// optionally an options object. You can create a `Polyline` object with
8059// multiple separate lines (`MultiPolyline`) by passing an array of arrays
8060// of geographic points.
8061L.polyline = function (latlngs, options) {
8062	return new L.Polyline(latlngs, options);
8063};
8064
8065L.Polyline._flat = function (latlngs) {
8066	// true if it's a flat array of latlngs; false if nested
8067	return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8068};
8069
8070
8071
8072/*
8073 * @namespace PolyUtil
8074 * Various utility functions for polygon geometries.
8075 */
8076
8077L.PolyUtil = {};
8078
8079/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8080 * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
8081 * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8082 * performance. Note that polygon points needs different algorithm for clipping
8083 * than polyline, so there's a seperate method for it.
8084 */
8085L.PolyUtil.clipPolygon = function (points, bounds, round) {
8086	var clippedPoints,
8087	    edges = [1, 4, 2, 8],
8088	    i, j, k,
8089	    a, b,
8090	    len, edge, p,
8091	    lu = L.LineUtil;
8092
8093	for (i = 0, len = points.length; i < len; i++) {
8094		points[i]._code = lu._getBitCode(points[i], bounds);
8095	}
8096
8097	// for each edge (left, bottom, right, top)
8098	for (k = 0; k < 4; k++) {
8099		edge = edges[k];
8100		clippedPoints = [];
8101
8102		for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8103			a = points[i];
8104			b = points[j];
8105
8106			// if a is inside the clip window
8107			if (!(a._code & edge)) {
8108				// if b is outside the clip window (a->b goes out of screen)
8109				if (b._code & edge) {
8110					p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8111					p._code = lu._getBitCode(p, bounds);
8112					clippedPoints.push(p);
8113				}
8114				clippedPoints.push(a);
8115
8116			// else if b is inside the clip window (a->b enters the screen)
8117			} else if (!(b._code & edge)) {
8118				p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8119				p._code = lu._getBitCode(p, bounds);
8120				clippedPoints.push(p);
8121			}
8122		}
8123		points = clippedPoints;
8124	}
8125
8126	return points;
8127};
8128
8129
8130
8131/*
8132 * @class Polygon
8133 * @aka L.Polygon
8134 * @inherits Polyline
8135 *
8136 * A class for drawing polygon overlays on a map. Extends `Polyline`.
8137 *
8138 * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
8139 *
8140 *
8141 * @example
8142 *
8143 * ```js
8144 * // create a red polygon from an array of LatLng points
8145 * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
8146 *
8147 * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8148 *
8149 * // zoom the map to the polygon
8150 * map.fitBounds(polygon.getBounds());
8151 * ```
8152 *
8153 * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
8154 *
8155 * ```js
8156 * var latlngs = [
8157 *   [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8158 *   [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8159 * ];
8160 * ```
8161 *
8162 * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8163 *
8164 * ```js
8165 * var latlngs = [
8166 *   [ // first polygon
8167 *     [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8168 *     [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8169 *   ],
8170 *   [ // second polygon
8171 *     [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
8172 *   ]
8173 * ];
8174 * ```
8175 */
8176
8177L.Polygon = L.Polyline.extend({
8178
8179	options: {
8180		fill: true
8181	},
8182
8183	isEmpty: function () {
8184		return !this._latlngs.length || !this._latlngs[0].length;
8185	},
8186
8187	getCenter: function () {
8188		// throws error when not yet added to map as this center calculation requires projected coordinates
8189		if (!this._map) {
8190			throw new Error('Must add layer to map before using getCenter()');
8191		}
8192
8193		var i, j, p1, p2, f, area, x, y, center,
8194		    points = this._rings[0],
8195		    len = points.length;
8196
8197		if (!len) { return null; }
8198
8199		// polygon centroid algorithm; only uses the first ring if there are multiple
8200
8201		area = x = y = 0;
8202
8203		for (i = 0, j = len - 1; i < len; j = i++) {
8204			p1 = points[i];
8205			p2 = points[j];
8206
8207			f = p1.y * p2.x - p2.y * p1.x;
8208			x += (p1.x + p2.x) * f;
8209			y += (p1.y + p2.y) * f;
8210			area += f * 3;
8211		}
8212
8213		if (area === 0) {
8214			// Polygon is so small that all points are on same pixel.
8215			center = points[0];
8216		} else {
8217			center = [x / area, y / area];
8218		}
8219		return this._map.layerPointToLatLng(center);
8220	},
8221
8222	_convertLatLngs: function (latlngs) {
8223		var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
8224		    len = result.length;
8225
8226		// remove last point if it equals first one
8227		if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
8228			result.pop();
8229		}
8230		return result;
8231	},
8232
8233	_setLatLngs: function (latlngs) {
8234		L.Polyline.prototype._setLatLngs.call(this, latlngs);
8235		if (L.Polyline._flat(this._latlngs)) {
8236			this._latlngs = [this._latlngs];
8237		}
8238	},
8239
8240	_defaultShape: function () {
8241		return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8242	},
8243
8244	_clipPoints: function () {
8245		// polygons need a different clipping algorithm so we redefine that
8246
8247		var bounds = this._renderer._bounds,
8248		    w = this.options.weight,
8249		    p = new L.Point(w, w);
8250
8251		// increase clip padding by stroke width to avoid stroke on clip edges
8252		bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
8253
8254		this._parts = [];
8255		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8256			return;
8257		}
8258
8259		if (this.options.noClip) {
8260			this._parts = this._rings;
8261			return;
8262		}
8263
8264		for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8265			clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
8266			if (clipped.length) {
8267				this._parts.push(clipped);
8268			}
8269		}
8270	},
8271
8272	_updatePath: function () {
8273		this._renderer._updatePoly(this, true);
8274	}
8275});
8276
8277
8278// @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8279L.polygon = function (latlngs, options) {
8280	return new L.Polygon(latlngs, options);
8281};
8282
8283
8284
8285/*
8286 * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
8287 */
8288
8289/*
8290 * @class Rectangle
8291 * @aka L.Retangle
8292 * @inherits Polygon
8293 *
8294 * A class for drawing rectangle overlays on a map. Extends `Polygon`.
8295 *
8296 * @example
8297 *
8298 * ```js
8299 * // define rectangle geographical bounds
8300 * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
8301 *
8302 * // create an orange rectangle
8303 * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
8304 *
8305 * // zoom the map to the rectangle bounds
8306 * map.fitBounds(bounds);
8307 * ```
8308 *
8309 */
8310
8311
8312L.Rectangle = L.Polygon.extend({
8313	initialize: function (latLngBounds, options) {
8314		L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
8315	},
8316
8317	// @method setBounds(latLngBounds: LatLngBounds): this
8318	// Redraws the rectangle with the passed bounds.
8319	setBounds: function (latLngBounds) {
8320		return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
8321	},
8322
8323	_boundsToLatLngs: function (latLngBounds) {
8324		latLngBounds = L.latLngBounds(latLngBounds);
8325		return [
8326			latLngBounds.getSouthWest(),
8327			latLngBounds.getNorthWest(),
8328			latLngBounds.getNorthEast(),
8329			latLngBounds.getSouthEast()
8330		];
8331	}
8332});
8333
8334
8335// @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
8336L.rectangle = function (latLngBounds, options) {
8337	return new L.Rectangle(latLngBounds, options);
8338};
8339
8340
8341
8342/*
8343 * @class CircleMarker
8344 * @aka L.CircleMarker
8345 * @inherits Path
8346 *
8347 * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8348 */
8349
8350L.CircleMarker = L.Path.extend({
8351
8352	// @section
8353	// @aka CircleMarker options
8354	options: {
8355		fill: true,
8356
8357		// @option radius: Number = 10
8358		// Radius of the circle marker, in pixels
8359		radius: 10
8360	},
8361
8362	initialize: function (latlng, options) {
8363		L.setOptions(this, options);
8364		this._latlng = L.latLng(latlng);
8365		this._radius = this.options.radius;
8366	},
8367
8368	// @method setLatLng(latLng: LatLng): this
8369	// Sets the position of a circle marker to a new location.
8370	setLatLng: function (latlng) {
8371		this._latlng = L.latLng(latlng);
8372		this.redraw();
8373		return this.fire('move', {latlng: this._latlng});
8374	},
8375
8376	// @method getLatLng(): LatLng
8377	// Returns the current geographical position of the circle marker
8378	getLatLng: function () {
8379		return this._latlng;
8380	},
8381
8382	// @method setRadius(radius: Number): this
8383	// Sets the radius of a circle marker. Units are in pixels.
8384	setRadius: function (radius) {
8385		this.options.radius = this._radius = radius;
8386		return this.redraw();
8387	},
8388
8389	// @method getRadius(): Number
8390	// Returns the current radius of the circle
8391	getRadius: function () {
8392		return this._radius;
8393	},
8394
8395	setStyle : function (options) {
8396		var radius = options && options.radius || this._radius;
8397		L.Path.prototype.setStyle.call(this, options);
8398		this.setRadius(radius);
8399		return this;
8400	},
8401
8402	_project: function () {
8403		this._point = this._map.latLngToLayerPoint(this._latlng);
8404		this._updateBounds();
8405	},
8406
8407	_updateBounds: function () {
8408		var r = this._radius,
8409		    r2 = this._radiusY || r,
8410		    w = this._clickTolerance(),
8411		    p = [r + w, r2 + w];
8412		this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
8413	},
8414
8415	_update: function () {
8416		if (this._map) {
8417			this._updatePath();
8418		}
8419	},
8420
8421	_updatePath: function () {
8422		this._renderer._updateCircle(this);
8423	},
8424
8425	_empty: function () {
8426		return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8427	}
8428});
8429
8430
8431// @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8432// Instantiates a circle marker object given a geographical point, and an optional options object.
8433L.circleMarker = function (latlng, options) {
8434	return new L.CircleMarker(latlng, options);
8435};
8436
8437
8438
8439/*
8440 * @class Circle
8441 * @aka L.Circle
8442 * @inherits CircleMarker
8443 *
8444 * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8445 *
8446 * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8447 *
8448 * @example
8449 *
8450 * ```js
8451 * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8452 * ```
8453 */
8454
8455L.Circle = L.CircleMarker.extend({
8456
8457	initialize: function (latlng, options, legacyOptions) {
8458		if (typeof options === 'number') {
8459			// Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8460			options = L.extend({}, legacyOptions, {radius: options});
8461		}
8462		L.setOptions(this, options);
8463		this._latlng = L.latLng(latlng);
8464
8465		if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8466
8467		// @section
8468		// @aka Circle options
8469		// @option radius: Number; Radius of the circle, in meters.
8470		this._mRadius = this.options.radius;
8471	},
8472
8473	// @method setRadius(radius: Number): this
8474	// Sets the radius of a circle. Units are in meters.
8475	setRadius: function (radius) {
8476		this._mRadius = radius;
8477		return this.redraw();
8478	},
8479
8480	// @method getRadius(): Number
8481	// Returns the current radius of a circle. Units are in meters.
8482	getRadius: function () {
8483		return this._mRadius;
8484	},
8485
8486	// @method getBounds(): LatLngBounds
8487	// Returns the `LatLngBounds` of the path.
8488	getBounds: function () {
8489		var half = [this._radius, this._radiusY || this._radius];
8490
8491		return new L.LatLngBounds(
8492			this._map.layerPointToLatLng(this._point.subtract(half)),
8493			this._map.layerPointToLatLng(this._point.add(half)));
8494	},
8495
8496	setStyle: L.Path.prototype.setStyle,
8497
8498	_project: function () {
8499
8500		var lng = this._latlng.lng,
8501		    lat = this._latlng.lat,
8502		    map = this._map,
8503		    crs = map.options.crs;
8504
8505		if (crs.distance === L.CRS.Earth.distance) {
8506			var d = Math.PI / 180,
8507			    latR = (this._mRadius / L.CRS.Earth.R) / d,
8508			    top = map.project([lat + latR, lng]),
8509			    bottom = map.project([lat - latR, lng]),
8510			    p = top.add(bottom).divideBy(2),
8511			    lat2 = map.unproject(p).lat,
8512			    lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8513			            (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8514
8515			if (isNaN(lngR) || lngR === 0) {
8516				lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8517			}
8518
8519			this._point = p.subtract(map.getPixelOrigin());
8520			this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
8521			this._radiusY = Math.max(Math.round(p.y - top.y), 1);
8522
8523		} else {
8524			var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8525
8526			this._point = map.latLngToLayerPoint(this._latlng);
8527			this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8528		}
8529
8530		this._updateBounds();
8531	}
8532});
8533
8534// @factory L.circle(latlng: LatLng, options?: Circle options)
8535// Instantiates a circle object given a geographical point, and an options object
8536// which contains the circle radius.
8537// @alternative
8538// @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8539// Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8540// Do not use in new applications or plugins.
8541L.circle = function (latlng, options, legacyOptions) {
8542	return new L.Circle(latlng, options, legacyOptions);
8543};
8544
8545
8546
8547/*
8548 * @class SVG
8549 * @inherits Renderer
8550 * @aka L.SVG
8551 *
8552 * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
8553 * Inherits `Renderer`.
8554 *
8555 * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
8556 * available in all web browsers, notably Android 2.x and 3.x.
8557 *
8558 * Although SVG is not available on IE7 and IE8, these browsers support
8559 * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
8560 * (a now deprecated technology), and the SVG renderer will fall back to VML in
8561 * this case.
8562 *
8563 * @example
8564 *
8565 * Use SVG by default for all paths in the map:
8566 *
8567 * ```js
8568 * var map = L.map('map', {
8569 * 	renderer: L.svg()
8570 * });
8571 * ```
8572 *
8573 * Use a SVG renderer with extra padding for specific vector geometries:
8574 *
8575 * ```js
8576 * var map = L.map('map');
8577 * var myRenderer = L.svg({ padding: 0.5 });
8578 * var line = L.polyline( coordinates, { renderer: myRenderer } );
8579 * var circle = L.circle( center, { renderer: myRenderer } );
8580 * ```
8581 */
8582
8583L.SVG = L.Renderer.extend({
8584
8585	getEvents: function () {
8586		var events = L.Renderer.prototype.getEvents.call(this);
8587		events.zoomstart = this._onZoomStart;
8588		return events;
8589	},
8590
8591	_initContainer: function () {
8592		this._container = L.SVG.create('svg');
8593
8594		// makes it possible to click through svg root; we'll reset it back in individual paths
8595		this._container.setAttribute('pointer-events', 'none');
8596
8597		this._rootGroup = L.SVG.create('g');
8598		this._container.appendChild(this._rootGroup);
8599	},
8600
8601	_onZoomStart: function () {
8602		// Drag-then-pinch interactions might mess up the center and zoom.
8603		// In this case, the easiest way to prevent this is re-do the renderer
8604		//   bounds and padding when the zooming starts.
8605		this._update();
8606	},
8607
8608	_update: function () {
8609		if (this._map._animatingZoom && this._bounds) { return; }
8610
8611		L.Renderer.prototype._update.call(this);
8612
8613		var b = this._bounds,
8614		    size = b.getSize(),
8615		    container = this._container;
8616
8617		// set size of svg-container if changed
8618		if (!this._svgSize || !this._svgSize.equals(size)) {
8619			this._svgSize = size;
8620			container.setAttribute('width', size.x);
8621			container.setAttribute('height', size.y);
8622		}
8623
8624		// movement: update container viewBox so that we don't have to change coordinates of individual layers
8625		L.DomUtil.setPosition(container, b.min);
8626		container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
8627
8628		this.fire('update');
8629	},
8630
8631	// methods below are called by vector layers implementations
8632
8633	_initPath: function (layer) {
8634		var path = layer._path = L.SVG.create('path');
8635
8636		// @namespace Path
8637		// @option className: String = null
8638		// Custom class name set on an element. Only for SVG renderer.
8639		if (layer.options.className) {
8640			L.DomUtil.addClass(path, layer.options.className);
8641		}
8642
8643		if (layer.options.interactive) {
8644			L.DomUtil.addClass(path, 'leaflet-interactive');
8645		}
8646
8647		this._updateStyle(layer);
8648	},
8649
8650	_addPath: function (layer) {
8651		this._rootGroup.appendChild(layer._path);
8652		layer.addInteractiveTarget(layer._path);
8653	},
8654
8655	_removePath: function (layer) {
8656		L.DomUtil.remove(layer._path);
8657		layer.removeInteractiveTarget(layer._path);
8658	},
8659
8660	_updatePath: function (layer) {
8661		layer._project();
8662		layer._update();
8663	},
8664
8665	_updateStyle: function (layer) {
8666		var path = layer._path,
8667		    options = layer.options;
8668
8669		if (!path) { return; }
8670
8671		if (options.stroke) {
8672			path.setAttribute('stroke', options.color);
8673			path.setAttribute('stroke-opacity', options.opacity);
8674			path.setAttribute('stroke-width', options.weight);
8675			path.setAttribute('stroke-linecap', options.lineCap);
8676			path.setAttribute('stroke-linejoin', options.lineJoin);
8677
8678			if (options.dashArray) {
8679				path.setAttribute('stroke-dasharray', options.dashArray);
8680			} else {
8681				path.removeAttribute('stroke-dasharray');
8682			}
8683
8684			if (options.dashOffset) {
8685				path.setAttribute('stroke-dashoffset', options.dashOffset);
8686			} else {
8687				path.removeAttribute('stroke-dashoffset');
8688			}
8689		} else {
8690			path.setAttribute('stroke', 'none');
8691		}
8692
8693		if (options.fill) {
8694			path.setAttribute('fill', options.fillColor || options.color);
8695			path.setAttribute('fill-opacity', options.fillOpacity);
8696			path.setAttribute('fill-rule', options.fillRule || 'evenodd');
8697		} else {
8698			path.setAttribute('fill', 'none');
8699		}
8700	},
8701
8702	_updatePoly: function (layer, closed) {
8703		this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
8704	},
8705
8706	_updateCircle: function (layer) {
8707		var p = layer._point,
8708		    r = layer._radius,
8709		    r2 = layer._radiusY || r,
8710		    arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
8711
8712		// drawing a circle with two half-arcs
8713		var d = layer._empty() ? 'M0 0' :
8714				'M' + (p.x - r) + ',' + p.y +
8715				arc + (r * 2) + ',0 ' +
8716				arc + (-r * 2) + ',0 ';
8717
8718		this._setPath(layer, d);
8719	},
8720
8721	_setPath: function (layer, path) {
8722		layer._path.setAttribute('d', path);
8723	},
8724
8725	// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
8726	_bringToFront: function (layer) {
8727		L.DomUtil.toFront(layer._path);
8728	},
8729
8730	_bringToBack: function (layer) {
8731		L.DomUtil.toBack(layer._path);
8732	}
8733});
8734
8735
8736// @namespace SVG; @section
8737// There are several static functions which can be called without instantiating L.SVG:
8738L.extend(L.SVG, {
8739	// @function create(name: String): SVGElement
8740	// Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
8741	// corresponding to the class name passed. For example, using 'line' will return
8742	// an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
8743	create: function (name) {
8744		return document.createElementNS('http://www.w3.org/2000/svg', name);
8745	},
8746
8747	// @function pointsToPath(rings: Point[], closed: Boolean): String
8748	// Generates a SVG path string for multiple rings, with each ring turning
8749	// into "M..L..L.." instructions
8750	pointsToPath: function (rings, closed) {
8751		var str = '',
8752		    i, j, len, len2, points, p;
8753
8754		for (i = 0, len = rings.length; i < len; i++) {
8755			points = rings[i];
8756
8757			for (j = 0, len2 = points.length; j < len2; j++) {
8758				p = points[j];
8759				str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
8760			}
8761
8762			// closes the ring for polygons; "x" is VML syntax
8763			str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
8764		}
8765
8766		// SVG complains about empty path strings
8767		return str || 'M0 0';
8768	}
8769});
8770
8771// @namespace Browser; @property svg: Boolean
8772// `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
8773L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
8774
8775
8776// @namespace SVG
8777// @factory L.svg(options?: Renderer options)
8778// Creates a SVG renderer with the given options.
8779L.svg = function (options) {
8780	return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
8781};
8782
8783
8784
8785/*
8786 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
8787 */
8788
8789/*
8790 * @class SVG
8791 *
8792 * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
8793 *
8794 * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
8795 * with old versions of Internet Explorer.
8796 */
8797
8798// @namespace Browser; @property vml: Boolean
8799// `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
8800L.Browser.vml = !L.Browser.svg && (function () {
8801	try {
8802		var div = document.createElement('div');
8803		div.innerHTML = '<v:shape adj="1"/>';
8804
8805		var shape = div.firstChild;
8806		shape.style.behavior = 'url(#default#VML)';
8807
8808		return shape && (typeof shape.adj === 'object');
8809
8810	} catch (e) {
8811		return false;
8812	}
8813}());
8814
8815// redefine some SVG methods to handle VML syntax which is similar but with some differences
8816L.SVG.include(!L.Browser.vml ? {} : {
8817
8818	_initContainer: function () {
8819		this._container = L.DomUtil.create('div', 'leaflet-vml-container');
8820	},
8821
8822	_update: function () {
8823		if (this._map._animatingZoom) { return; }
8824		L.Renderer.prototype._update.call(this);
8825		this.fire('update');
8826	},
8827
8828	_initPath: function (layer) {
8829		var container = layer._container = L.SVG.create('shape');
8830
8831		L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
8832
8833		container.coordsize = '1 1';
8834
8835		layer._path = L.SVG.create('path');
8836		container.appendChild(layer._path);
8837
8838		this._updateStyle(layer);
8839	},
8840
8841	_addPath: function (layer) {
8842		var container = layer._container;
8843		this._container.appendChild(container);
8844
8845		if (layer.options.interactive) {
8846			layer.addInteractiveTarget(container);
8847		}
8848	},
8849
8850	_removePath: function (layer) {
8851		var container = layer._container;
8852		L.DomUtil.remove(container);
8853		layer.removeInteractiveTarget(container);
8854	},
8855
8856	_updateStyle: function (layer) {
8857		var stroke = layer._stroke,
8858		    fill = layer._fill,
8859		    options = layer.options,
8860		    container = layer._container;
8861
8862		container.stroked = !!options.stroke;
8863		container.filled = !!options.fill;
8864
8865		if (options.stroke) {
8866			if (!stroke) {
8867				stroke = layer._stroke = L.SVG.create('stroke');
8868			}
8869			container.appendChild(stroke);
8870			stroke.weight = options.weight + 'px';
8871			stroke.color = options.color;
8872			stroke.opacity = options.opacity;
8873
8874			if (options.dashArray) {
8875				stroke.dashStyle = L.Util.isArray(options.dashArray) ?
8876				    options.dashArray.join(' ') :
8877				    options.dashArray.replace(/( *, *)/g, ' ');
8878			} else {
8879				stroke.dashStyle = '';
8880			}
8881			stroke.endcap = options.lineCap.replace('butt', 'flat');
8882			stroke.joinstyle = options.lineJoin;
8883
8884		} else if (stroke) {
8885			container.removeChild(stroke);
8886			layer._stroke = null;
8887		}
8888
8889		if (options.fill) {
8890			if (!fill) {
8891				fill = layer._fill = L.SVG.create('fill');
8892			}
8893			container.appendChild(fill);
8894			fill.color = options.fillColor || options.color;
8895			fill.opacity = options.fillOpacity;
8896
8897		} else if (fill) {
8898			container.removeChild(fill);
8899			layer._fill = null;
8900		}
8901	},
8902
8903	_updateCircle: function (layer) {
8904		var p = layer._point.round(),
8905		    r = Math.round(layer._radius),
8906		    r2 = Math.round(layer._radiusY || r);
8907
8908		this._setPath(layer, layer._empty() ? 'M0 0' :
8909				'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
8910	},
8911
8912	_setPath: function (layer, path) {
8913		layer._path.v = path;
8914	},
8915
8916	_bringToFront: function (layer) {
8917		L.DomUtil.toFront(layer._container);
8918	},
8919
8920	_bringToBack: function (layer) {
8921		L.DomUtil.toBack(layer._container);
8922	}
8923});
8924
8925if (L.Browser.vml) {
8926	L.SVG.create = (function () {
8927		try {
8928			document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
8929			return function (name) {
8930				return document.createElement('<lvml:' + name + ' class="lvml">');
8931			};
8932		} catch (e) {
8933			return function (name) {
8934				return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
8935			};
8936		}
8937	})();
8938}
8939
8940
8941
8942/*
8943 * @class Canvas
8944 * @inherits Renderer
8945 * @aka L.Canvas
8946 *
8947 * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
8948 * Inherits `Renderer`.
8949 *
8950 * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
8951 * available in all web browsers, notably IE8, and overlapping geometries might
8952 * not display properly in some edge cases.
8953 *
8954 * @example
8955 *
8956 * Use Canvas by default for all paths in the map:
8957 *
8958 * ```js
8959 * var map = L.map('map', {
8960 * 	renderer: L.canvas()
8961 * });
8962 * ```
8963 *
8964 * Use a Canvas renderer with extra padding for specific vector geometries:
8965 *
8966 * ```js
8967 * var map = L.map('map');
8968 * var myRenderer = L.canvas({ padding: 0.5 });
8969 * var line = L.polyline( coordinates, { renderer: myRenderer } );
8970 * var circle = L.circle( center, { renderer: myRenderer } );
8971 * ```
8972 */
8973
8974L.Canvas = L.Renderer.extend({
8975
8976	onAdd: function () {
8977		L.Renderer.prototype.onAdd.call(this);
8978
8979		this._layers = this._layers || {};
8980
8981		// Redraw vectors since canvas is cleared upon removal,
8982		// in case of removing the renderer itself from the map.
8983		this._draw();
8984	},
8985
8986	_initContainer: function () {
8987		var container = this._container = document.createElement('canvas');
8988
8989		L.DomEvent
8990			.on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
8991			.on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
8992			.on(container, 'mouseout', this._handleMouseOut, this);
8993
8994		this._ctx = container.getContext('2d');
8995	},
8996
8997	_update: function () {
8998		if (this._map._animatingZoom && this._bounds) { return; }
8999
9000		this._drawnLayers = {};
9001
9002		L.Renderer.prototype._update.call(this);
9003
9004		var b = this._bounds,
9005		    container = this._container,
9006		    size = b.getSize(),
9007		    m = L.Browser.retina ? 2 : 1;
9008
9009		L.DomUtil.setPosition(container, b.min);
9010
9011		// set canvas size (also clearing it); use double size on retina
9012		container.width = m * size.x;
9013		container.height = m * size.y;
9014		container.style.width = size.x + 'px';
9015		container.style.height = size.y + 'px';
9016
9017		if (L.Browser.retina) {
9018			this._ctx.scale(2, 2);
9019		}
9020
9021		// translate so we use the same path coordinates after canvas element moves
9022		this._ctx.translate(-b.min.x, -b.min.y);
9023
9024		// Tell paths to redraw themselves
9025		this.fire('update');
9026	},
9027
9028	_initPath: function (layer) {
9029		this._updateDashArray(layer);
9030		this._layers[L.stamp(layer)] = layer;
9031	},
9032
9033	_addPath: L.Util.falseFn,
9034
9035	_removePath: function (layer) {
9036		layer._removed = true;
9037		this._requestRedraw(layer);
9038	},
9039
9040	_updatePath: function (layer) {
9041		this._redrawBounds = layer._pxBounds;
9042		this._draw(true);
9043		layer._project();
9044		layer._update();
9045		this._draw();
9046		this._redrawBounds = null;
9047	},
9048
9049	_updateStyle: function (layer) {
9050		this._updateDashArray(layer);
9051		this._requestRedraw(layer);
9052	},
9053
9054	_updateDashArray: function (layer) {
9055		if (layer.options.dashArray) {
9056			var parts = layer.options.dashArray.split(','),
9057			    dashArray = [],
9058			    i;
9059			for (i = 0; i < parts.length; i++) {
9060				dashArray.push(Number(parts[i]));
9061			}
9062			layer.options._dashArray = dashArray;
9063		}
9064	},
9065
9066	_requestRedraw: function (layer) {
9067		if (!this._map) { return; }
9068
9069		var padding = (layer.options.weight || 0) + 1;
9070		this._redrawBounds = this._redrawBounds || new L.Bounds();
9071		this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
9072		this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
9073
9074		this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
9075	},
9076
9077	_redraw: function () {
9078		this._redrawRequest = null;
9079
9080		this._draw(true); // clear layers in redraw bounds
9081		this._draw(); // draw layers
9082
9083		this._redrawBounds = null;
9084	},
9085
9086	_draw: function (clear) {
9087		this._clear = clear;
9088		var layer, bounds = this._redrawBounds;
9089		this._ctx.save();
9090		if (bounds) {
9091			this._ctx.beginPath();
9092			this._ctx.rect(bounds.min.x, bounds.min.y, bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y);
9093			this._ctx.clip();
9094		}
9095
9096		for (var id in this._layers) {
9097			layer = this._layers[id];
9098			if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
9099				layer._updatePath();
9100			}
9101			if (clear && layer._removed) {
9102				delete layer._removed;
9103				delete this._layers[id];
9104			}
9105		}
9106		this._ctx.restore();  // Restore state before clipping.
9107	},
9108
9109	_updatePoly: function (layer, closed) {
9110
9111		var i, j, len2, p,
9112		    parts = layer._parts,
9113		    len = parts.length,
9114		    ctx = this._ctx;
9115
9116		if (!len) { return; }
9117
9118		this._drawnLayers[layer._leaflet_id] = layer;
9119
9120		ctx.beginPath();
9121
9122		if (ctx.setLineDash) {
9123			ctx.setLineDash(layer.options && layer.options._dashArray || []);
9124		}
9125
9126		for (i = 0; i < len; i++) {
9127			for (j = 0, len2 = parts[i].length; j < len2; j++) {
9128				p = parts[i][j];
9129				ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
9130			}
9131			if (closed) {
9132				ctx.closePath();
9133			}
9134		}
9135
9136		this._fillStroke(ctx, layer);
9137
9138		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
9139	},
9140
9141	_updateCircle: function (layer) {
9142
9143		if (layer._empty()) { return; }
9144
9145		var p = layer._point,
9146		    ctx = this._ctx,
9147		    r = layer._radius,
9148		    s = (layer._radiusY || r) / r;
9149
9150		this._drawnLayers[layer._leaflet_id] = layer;
9151
9152		if (s !== 1) {
9153			ctx.save();
9154			ctx.scale(1, s);
9155		}
9156
9157		ctx.beginPath();
9158		ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
9159
9160		if (s !== 1) {
9161			ctx.restore();
9162		}
9163
9164		this._fillStroke(ctx, layer);
9165	},
9166
9167	_fillStroke: function (ctx, layer) {
9168		var clear = this._clear,
9169		    options = layer.options;
9170
9171		ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over';
9172
9173		if (options.fill) {
9174			ctx.globalAlpha = clear ? 1 : options.fillOpacity;
9175			ctx.fillStyle = options.fillColor || options.color;
9176			ctx.fill(options.fillRule || 'evenodd');
9177		}
9178
9179		if (options.stroke && options.weight !== 0) {
9180			ctx.globalAlpha = clear ? 1 : options.opacity;
9181
9182			// if clearing shape, do it with the previously drawn line width
9183			layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight;
9184
9185			ctx.strokeStyle = options.color;
9186			ctx.lineCap = options.lineCap;
9187			ctx.lineJoin = options.lineJoin;
9188			ctx.stroke();
9189		}
9190	},
9191
9192	// Canvas obviously doesn't have mouse events for individual drawn objects,
9193	// so we emulate that by calculating what's under the mouse on mousemove/click manually
9194
9195	_onClick: function (e) {
9196		var point = this._map.mouseEventToLayerPoint(e), layers = [], layer;
9197
9198		for (var id in this._layers) {
9199			layer = this._layers[id];
9200			if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
9201				L.DomEvent._fakeStop(e);
9202				layers.push(layer);
9203			}
9204		}
9205		if (layers.length)  {
9206			this._fireEvent(layers, e);
9207		}
9208	},
9209
9210	_onMouseMove: function (e) {
9211		if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
9212
9213		var point = this._map.mouseEventToLayerPoint(e);
9214		this._handleMouseOut(e, point);
9215		this._handleMouseHover(e, point);
9216	},
9217
9218
9219	_handleMouseOut: function (e, point) {
9220		var layer = this._hoveredLayer;
9221		if (layer && (e.type === 'mouseout' || !layer._containsPoint(point))) {
9222			// if we're leaving the layer, fire mouseout
9223			L.DomUtil.removeClass(this._container, 'leaflet-interactive');
9224			this._fireEvent([layer], e, 'mouseout');
9225			this._hoveredLayer = null;
9226		}
9227	},
9228
9229	_handleMouseHover: function (e, point) {
9230		var id, layer;
9231
9232		for (id in this._drawnLayers) {
9233			layer = this._drawnLayers[id];
9234			if (layer.options.interactive && layer._containsPoint(point)) {
9235				L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
9236				this._fireEvent([layer], e, 'mouseover');
9237				this._hoveredLayer = layer;
9238			}
9239		}
9240
9241		if (this._hoveredLayer) {
9242			this._fireEvent([this._hoveredLayer], e);
9243		}
9244	},
9245
9246	_fireEvent: function (layers, e, type) {
9247		this._map._fireDOMEvent(e, type || e.type, layers);
9248	},
9249
9250	// TODO _bringToFront & _bringToBack, pretty tricky
9251
9252	_bringToFront: L.Util.falseFn,
9253	_bringToBack: L.Util.falseFn
9254});
9255
9256// @namespace Browser; @property canvas: Boolean
9257// `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9258L.Browser.canvas = (function () {
9259	return !!document.createElement('canvas').getContext;
9260}());
9261
9262// @namespace Canvas
9263// @factory L.canvas(options?: Renderer options)
9264// Creates a Canvas renderer with the given options.
9265L.canvas = function (options) {
9266	return L.Browser.canvas ? new L.Canvas(options) : null;
9267};
9268
9269L.Polyline.prototype._containsPoint = function (p, closed) {
9270	var i, j, k, len, len2, part,
9271	    w = this._clickTolerance();
9272
9273	if (!this._pxBounds.contains(p)) { return false; }
9274
9275	// hit detection for polylines
9276	for (i = 0, len = this._parts.length; i < len; i++) {
9277		part = this._parts[i];
9278
9279		for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9280			if (!closed && (j === 0)) { continue; }
9281
9282			if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
9283				return true;
9284			}
9285		}
9286	}
9287	return false;
9288};
9289
9290L.Polygon.prototype._containsPoint = function (p) {
9291	var inside = false,
9292	    part, p1, p2, i, j, k, len, len2;
9293
9294	if (!this._pxBounds.contains(p)) { return false; }
9295
9296	// ray casting algorithm for detecting if point is in polygon
9297	for (i = 0, len = this._parts.length; i < len; i++) {
9298		part = this._parts[i];
9299
9300		for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9301			p1 = part[j];
9302			p2 = part[k];
9303
9304			if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
9305				inside = !inside;
9306			}
9307		}
9308	}
9309
9310	// also check if it's on polygon stroke
9311	return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
9312};
9313
9314L.CircleMarker.prototype._containsPoint = function (p) {
9315	return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
9316};
9317
9318
9319
9320/*
9321 * @class GeoJSON
9322 * @aka L.GeoJSON
9323 * @inherits FeatureGroup
9324 *
9325 * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
9326 * GeoJSON data and display it on the map. Extends `FeatureGroup`.
9327 *
9328 * @example
9329 *
9330 * ```js
9331 * L.geoJSON(data, {
9332 * 	style: function (feature) {
9333 * 		return {color: feature.properties.color};
9334 * 	}
9335 * }).bindPopup(function (layer) {
9336 * 	return layer.feature.properties.description;
9337 * }).addTo(map);
9338 * ```
9339 */
9340
9341L.GeoJSON = L.FeatureGroup.extend({
9342
9343	/* @section
9344	 * @aka GeoJSON options
9345	 *
9346	 * @option pointToLayer: Function = *
9347	 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
9348	 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
9349	 * The default is to spawn a default `Marker`:
9350	 * ```js
9351	 * function(geoJsonPoint, latlng) {
9352	 * 	return L.marker(latlng);
9353	 * }
9354	 * ```
9355	 *
9356	 * @option style: Function = *
9357	 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
9358	 * called internally when data is added.
9359	 * The default value is to not override any defaults:
9360	 * ```js
9361	 * function (geoJsonFeature) {
9362	 * 	return {}
9363	 * }
9364	 * ```
9365	 *
9366	 * @option onEachFeature: Function = *
9367	 * A `Function` that will be called once for each created `Feature`, after it has
9368	 * been created and styled. Useful for attaching events and popups to features.
9369	 * The default is to do nothing with the newly created layers:
9370	 * ```js
9371	 * function (feature, layer) {}
9372	 * ```
9373	 *
9374	 * @option filter: Function = *
9375	 * A `Function` that will be used to decide whether to include a feature or not.
9376	 * The default is to include all features:
9377	 * ```js
9378	 * function (geoJsonFeature) {
9379	 * 	return true;
9380	 * }
9381	 * ```
9382	 * Note: dynamically changing the `filter` option will have effect only on newly
9383	 * added data. It will _not_ re-evaluate already included features.
9384	 *
9385	 * @option coordsToLatLng: Function = *
9386	 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
9387	 * The default is the `coordsToLatLng` static method.
9388	 */
9389
9390	initialize: function (geojson, options) {
9391		L.setOptions(this, options);
9392
9393		this._layers = {};
9394
9395		if (geojson) {
9396			this.addData(geojson);
9397		}
9398	},
9399
9400	// @method addData( <GeoJSON> data ): Layer
9401	// Adds a GeoJSON object to the layer.
9402	addData: function (geojson) {
9403		var features = L.Util.isArray(geojson) ? geojson : geojson.features,
9404		    i, len, feature;
9405
9406		if (features) {
9407			for (i = 0, len = features.length; i < len; i++) {
9408				// only add this if geometry or geometries are set and not null
9409				feature = features[i];
9410				if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
9411					this.addData(feature);
9412				}
9413			}
9414			return this;
9415		}
9416
9417		var options = this.options;
9418
9419		if (options.filter && !options.filter(geojson)) { return this; }
9420
9421		var layer = L.GeoJSON.geometryToLayer(geojson, options);
9422		if (!layer) {
9423			return this;
9424		}
9425		layer.feature = L.GeoJSON.asFeature(geojson);
9426
9427		layer.defaultOptions = layer.options;
9428		this.resetStyle(layer);
9429
9430		if (options.onEachFeature) {
9431			options.onEachFeature(geojson, layer);
9432		}
9433
9434		return this.addLayer(layer);
9435	},
9436
9437	// @method resetStyle( <Path>
9437 layer ): Layer
9438	// Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
9439	resetStyle: function (layer) {
9440		// reset any custom styles
9441		layer.options = L.Util.extend({}, layer.defaultOptions);
9442		this._setLayerStyle(layer, this.options.style);
9443		return this;
9444	},
9445
9446	// @method setStyle( <Function> style ): Layer
9447	// Changes styles of GeoJSON vector layers with the given style function.
9448	setStyle: function (style) {
9449		return this.eachLayer(function (layer) {
9450			this._setLayerStyle(layer, style);
9451		}, this);
9452	},
9453
9454	_setLayerStyle: function (layer, style) {
9455		if (typeof style === 'function') {
9456			style = style(layer.feature);
9457		}
9458		if (layer.setStyle) {
9459			layer.setStyle(style);
9460		}
9461	}
9462});
9463
9464// @section
9465// There are several static functions which can be called without instantiating L.GeoJSON:
9466L.extend(L.GeoJSON, {
9467	// @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
9468	// Creates a `Layer` from a given GeoJSON feature. Can use a custom
9469	// [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
9470	// functions if provided as options.
9471	geometryToLayer: function (geojson, options) {
9472
9473		var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
9474		    coords = geometry ? geometry.coordinates : null,
9475		    layers = [],
9476		    pointToLayer = options && options.pointToLayer,
9477		    coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
9478		    latlng, latlngs, i, len;
9479
9480		if (!coords && !geometry) {
9481			return null;
9482		}
9483
9484		switch (geometry.type) {
9485		case 'Point':
9486			latlng = coordsToLatLng(coords);
9487			return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
9488
9489		case 'MultiPoint':
9490			for (i = 0, len = coords.length; i < len; i++) {
9491				latlng = coordsToLatLng(coords[i]);
9492				layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
9493			}
9494			return new L.FeatureGroup(layers);
9495
9496		case 'LineString':
9497		case 'MultiLineString':
9498			latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
9499			return new L.Polyline(latlngs, options);
9500
9501		case 'Polygon':
9502		case 'MultiPolygon':
9503			latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
9504			return new L.Polygon(latlngs, options);
9505
9506		case 'GeometryCollection':
9507			for (i = 0, len = geometry.geometries.length; i < len; i++) {
9508				var layer = this.geometryToLayer({
9509					geometry: geometry.geometries[i],
9510					type: 'Feature',
9511					properties: geojson.properties
9512				}, options);
9513
9514				if (layer) {
9515					layers.push(layer);
9516				}
9517			}
9518			return new L.FeatureGroup(layers);
9519
9520		default:
9521			throw new Error('Invalid GeoJSON object.');
9522		}
9523	},
9524
9525	// @function coordsToLatLng(coords: Array): LatLng
9526	// Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
9527	// or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
9528	coordsToLatLng: function (coords) {
9529		return new L.LatLng(coords[1], coords[0], coords[2]);
9530	},
9531
9532	// @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
9533	// Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
9534	// `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
9535	// Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
9536	coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
9537		var latlngs = [];
9538
9539		for (var i = 0, len = coords.length, latlng; i < len; i++) {
9540			latlng = levelsDeep ?
9541			        this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
9542			        (coordsToLatLng || this.coordsToLatLng)(coords[i]);
9543
9544			latlngs.push(latlng);
9545		}
9546
9547		return latlngs;
9548	},
9549
9550	// @function latLngToCoords(latlng: LatLng): Array
9551	// Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
9552	latLngToCoords: function (latlng) {
9553		return latlng.alt !== undefined ?
9554				[latlng.lng, latlng.lat, latlng.alt] :
9555				[latlng.lng, latlng.lat];
9556	},
9557
9558	// @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, 
9558closed?: Boolean): Array
9559	// Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
9560	// `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
9561	latLngsToCoords: function (latlngs, levelsDeep, closed) {
9562		var coords = [];
9563
9564		for (var i = 0, len = latlngs.length; i < len; i++) {
9565			coords.push(levelsDeep ?
9566				L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
9567				L.GeoJSON.latLngToCoords(latlngs[i]));
9568		}
9569
9570		if (!levelsDeep && closed) {
9571			coords.push(coords[0]);
9572		}
9573
9574		return coords;
9575	},
9576
9577	getFeature: function (layer, newGeometry) {
9578		return layer.feature ?
9579				L.extend({}, layer.feature, {geometry: newGeometry}) :
9580				L.GeoJSON.asFeature(newGeometry);
9581	},
9582
9583	// @function asFeature(geojson: Object): Object
9584	// Normalize GeoJSON geometries/features into GeoJSON features.
9585	asFeature: function (geojson) {
9586		if (geojson.type === 'Feature') {
9587			return geojson;
9588		}
9589
9590		return {
9591			type: 'Feature',
9592			properties: {},
9593			geometry: geojson
9594		};
9595	}
9596});
9597
9598var PointToGeoJSON = {
9599	toGeoJSON: function () {
9600		return L.GeoJSON.getFeature(this, {
9601			type: 'Point',
9602			coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
9603		});
9604	}
9605};
9606
9607L.Marker.include(PointToGeoJSON);
9608
9609// @namespace CircleMarker
9610// @method toGeoJSON(): Object
9611// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
9612L.Circle.include(PointToGeoJSON);
9613L.CircleMarker.include(PointToGeoJSON);
9614
9615
9616// @namespace Polyline
9617// @method toGeoJSON(): Object
9618// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
9619L.Polyline.prototype.toGeoJSON = function () {
9620	var multi = !L.Polyline._flat(this._latlngs);
9621
9622	var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
9623
9624	return L.GeoJSON.getFeature(this, {
9625		type: (multi ? 'Multi' : '') + 'LineString',
9626		coordinates: coords
9627	});
9628};
9629
9630// @namespace Polygon
9631// @method toGeoJSON(): Object
9632// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
9633L.Polygon.prototype.toGeoJSON = function () {
9634	var holes = !L.Polyline._flat(this._latlngs),
9635	    multi = holes && !L.Polyline._flat(this._latlngs[0]);
9636
9637	var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
9638
9639	if (!holes) {
9640		coords = [coords];
9641	}
9642
9643	return L.GeoJSON.getFeature(this, {
9644		type: (multi ? 'Multi' : '') + 'Polygon',
9645		coordinates: coords
9646	});
9647};
9648
9649
9650// @namespace LayerGroup
9651L.LayerGroup.include({
9652	toMultiPoint: function () {
9653		var coords = [];
9654
9655		this.eachLayer(function (layer) {
9656			coords.push(layer.toGeoJSON().geometry.coordinates);
9657		});
9658
9659		return L.GeoJSON.getFeature(this, {
9660			type: 'MultiPoint',
9661			coordinates: coords
9662		});
9663	},
9664
9665	// @method toGeoJSON(): Object
9666	// Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
9667	toGeoJSON: function () {
9668
9669		var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9670
9671		if (type === 'MultiPoint') {
9672			return this.toMultiPoint();
9673		}
9674
9675		var isGeometryCollection = type === 'GeometryCollection',
9676		    jsons = [];
9677
9678		this.eachLayer(function (layer) {
9679			if (layer.toGeoJSON) {
9680				var json = layer.toGeoJSON();
9681				jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
9682			}
9683		});
9684
9685		if (isGeometryCollection) {
9686			return L.GeoJSON.getFeature(this, {
9687				geometries: jsons,
9688				type: 'GeometryCollection'
9689			});
9690		}
9691
9692		return {
9693			type: 'FeatureCollection',
9694			features: jsons
9695		};
9696	}
9697});
9698
9699// @namespace GeoJSON
9700// @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9701// Creates a GeoJSON layer. Optionally accepts an object in
9702// [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
9703// (you can alternatively add it later with `addData` method) and an `options` object.
9704L.geoJSON = function (geojson, options) {
9705	return new L.GeoJSON(geojson, options);
9706};
9707// Backward compatibility.
9708L.geoJson = L.geoJSON;
9709
9710
9711
9712/*
9713 * @namespace DomEvent
9714 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
9715 */
9716
9717// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
9718
9719
9720
9721var eventsKey = '_leaflet_events';
9722
9723L.DomEvent = {
9724
9725	// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
9726	// Adds a listener function (`fn`) to a particular DOM event type of the
9727	// element `el`. You can optionally specify the context of the listener
9728	// (object the `this` keyword will point to). You can also pass several
9729	// space-separated types (e.g. `'click dblclick'`).
9730
9731	// @alternative
9732	// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
9733	// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9734	on: function (obj, types, fn, context) {
9735
9736		if (typeof types === 'object') {
9737			for (var type in types) {
9738				this._on(obj, type, types[type], fn);
9739			}
9740		} else {
9741			types = L.Util.splitWords(types);
9742
9743			for (var i = 0, len = types.length; i < len; i++) {
9744				this._on(obj, types[i], fn, context);
9745			}
9746		}
9747
9748		return this;
9749	},
9750
9751	// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
9752	// Removes a previously added listener function. If no function is specified,
9753	// it will remove all the listeners of that particular DOM event from the element.
9754	// Note that if you passed a custom context to on, you must pass the same
9755	// context to `off` in order to remove the listener.
9756
9757	// @alternative
9758	// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
9759	// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9760	off: function (obj, types, fn, context) {
9761
9762		if (typeof types === 'object') {
9763			for (var type in types) {
9764				this._off(obj, type, types[type], fn);
9765			}
9766		} else {
9767			types = L.Util.splitWords(types);
9768
9769			for (var i = 0, len = types.length; i < len; i++) {
9770				this._off(obj, types[i], fn, context);
9771			}
9772		}
9773
9774		return this;
9775	},
9776
9777	_on: function (obj, type, fn, context) {
9778		var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
9779
9780		if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
9781
9782		var handler = function (e) {
9783			return fn.call(context || obj, e || window.event);
9784		};
9785
9786		var originalHandler = handler;
9787
9788		if (L.Browser.pointer && type.indexOf('touch') === 0) {
9789			this.addPointerListener(obj, type, handler, id);
9790
9791		} else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
9792			this.addDoubleTapListener(obj, handler, id);
9793
9794		} else if ('addEventListener' in obj) {
9795
9796			if (type === 'mousewheel') {
9797				obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9798
9799			} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
9800				handler = function (e) {
9801					e = e || window.event;
9802					if (L.DomEvent._isExternalTarget(obj, e)) {
9803						originalHandler(e);
9804					}
9805				};
9806				obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
9807
9808			} else {
9809				if (type === 'click' && L.Browser.android) {
9810					handler = function (e) {
9811						return L.DomEvent._filterClick(e, originalHandler);
9812					};
9813				}
9814				obj.addEventListener(type, handler, false);
9815			}
9816
9817		} else if ('attachEvent' in obj) {
9818			obj.attachEvent('on' + type, handler);
9819		}
9820
9821		obj[eventsKey] = obj[eventsKey] || {};
9822		obj[eventsKey][id] = handler;
9823
9824		return this;
9825	},
9826
9827	_off: function (obj, type, fn, context) {
9828
9829		var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
9830		    handler = obj[eventsKey] && obj[eventsKey][id];
9831
9832		if (!handler) { return this; }
9833
9834		if (L.Browser.pointer && type.indexOf('touch') === 0) {
9835			this.removePointerListener(obj, type, id);
9836
9837		} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
9838			this.removeDoubleTapListener(obj, id);
9839
9840		} else if ('removeEventListener' in obj) {
9841
9842			if (type === 'mousewheel') {
9843				obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9844
9845			} else {
9846				obj.removeEventListener(
9847					type === 'mouseenter' ? 'mouseover' :
9848					type === 'mouseleave' ? 'mouseout' : type, handler, false);
9849			}
9850
9851		} else if ('detachEvent' in obj) {
9852			obj.detachEvent('on' + type, handler);
9853		}
9854
9855		obj[eventsKey][id] = null;
9856
9857		return this;
9858	},
9859
9860	// @function stopPropagation(ev: DOMEvent): this
9861	// Stop the given event from propagation to parent elements. Used inside the listener functions:
9862	// ```js
9863	// L.DomEvent.on(div, 'click', function (ev) {
9864	// 	L.DomEvent.stopPropagation(ev);
9865	// });
9866	// ```
9867	stopPropagation: function (e) {
9868
9869		if (e.stopPropagation) {
9870			e.stopPropagation();
9871		} else if (e.originalEvent) {  // In case of Leaflet event.
9872			e.originalEvent._stopped = true;
9873		} else {
9874			e.cancelBubble = true;
9875		}
9876		L.DomEvent._skipped(e);
9877
9878		return this;
9879	},
9880
9881	// @function disableScrollPropagation(el: HTMLElement): this
9882	// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
9883	disableScrollPropagation: function (el) {
9884		return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
9885	},
9886
9887	// @function disableClickPropagation(el: HTMLElement): this
9888	// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
9889	// `'mousedown'` and `'touchstart'` events (plus browser variants).
9890	disableClickPropagation: function (el) {
9891		var stop = L.DomEvent.stopPropagation;
9892
9893		L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
9894
9895		return L.DomEvent.on(el, {
9896			click: L.DomEvent._fakeStop,
9897			dblclick: stop
9898		});
9899	},
9900
9901	// @function preventDefault(ev: DOMEvent): this
9902	// Prevents the default action of the DOM Event `ev` from happening (such as
9903	// following a link in the href of the a element, or doing a POST request
9904	// with page reload when a `<form>` is submitted).
9905	// Use it inside listener functions.
9906	preventDefault: function (e) {
9907
9908		if (e.preventDefault) {
9909			e.preventDefault();
9910		} else {
9911			e.returnValue = false;
9912		}
9913		return this;
9914	},
9915
9916	// @function stop(ev): this
9917	// Does `stopPropagation` and `preventDefault` at the same time.
9918	stop: function (e) {
9919		return L.DomEvent
9920			.preventDefault(e)
9921			.stopPropagation(e);
9922	},
9923
9924	// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
9925	// Gets normalized mouse position from a DOM event relative to the
9926	// `container` or to the whole page if not specified.
9927	getMousePosition: function (e, container) {
9928		if (!container) {
9929			return new L.Point(e.clientX, e.clientY);
9930		}
9931
9932		var rect = container.getBoundingClientRect();
9933
9934		return new L.Point(
9935			e.clientX - rect.left - container.clientLeft,
9936			e.clientY - rect.top - container.clientTop);
9937	},
9938
9939	// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
9940	// and Firefox scrolls device pixels, not CSS pixels
9941	_wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
9942	                L.Browser.gecko ? window.devicePixelRatio :
9943	                1,
9944
9945	// @function getWheelDelta(ev: DOMEvent): Number
9946	// Gets normalized wheel delta from a mousewheel DOM event, in vertical
9947	// pixels scrolled (negative if scrolling down).
9948	// Events from pointing devices without precise scrolling are mapped to
9949	// a best guess of 60 pixels.
9950	getWheelDelta: function (e) {
9951		return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
9952		       (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
9953		       (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
9954		       (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
9955		       (e.deltaX || e.deltaZ) ? 0 :	// Skip horizontal/depth wheel events
9956		       e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
9957		       (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
9958		       e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
9959		       0;
9960	},
9961
9962	_skipEvents: {},
9963
9964	_fakeStop: function (e) {
9965		// fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
9966		L.DomEvent._skipEvents[e.type] = true;
9967	},
9968
9969	_skipped: function (e) {
9970		var skipped = this._skipEvents[e.type];
9971		// reset when checking, as it's only used in map container and propagates outside of the map
9972		this._skipEvents[e.type] = false;
9973		return skipped;
9974	},
9975
9976	// check if element really left/entered the event target (for mouseenter/mouseleave)
9977	_isExternalTarget: function (el, e) {
9978
9979		var related = e.relatedTarget;
9980
9981		if (!related) { return true; }
9982
9983		try {
9984			while (related && (related !== el)) {
9985				related = related.parentNode;
9986			}
9987		} catch (err) {
9988			return false;
9989		}
9990		return (related !== el);
9991	},
9992
9993	// this is a horrible workaround for a bug in Android where a single touch triggers two click events
9994	_filterClick: function (e, handler) {
9995		var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
9996		    elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
9997
9998		// are they closer together than 500ms yet more than 100ms?
9999		// Android typically triggers them ~300ms apart while multiple listeners
10000		// on the same event should be triggered far faster;
10001		// or check if click is simulated on the element, and if it is, reject any non-simulated events
10002
10003		if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
10004			L.DomEvent.stop(e);
10005			return;
10006		}
10007		L.DomEvent._lastClick = timeStamp;
10008
10009		handler(e);
10010	}
10011};
10012
10013// @function addListener(…): this
10014// Alias to [`L.DomEvent.on`](#domevent-on)
10015L.DomEvent.addListener = L.DomEvent.on;
10016
10017// @function removeListener(…): this
10018// Alias to [`L.DomEvent.off`](#domevent-off)
10019L.DomEvent.removeListener = L.DomEvent.off;
10020
10021
10022
10023/*
10024 * @class Draggable
10025 * @aka L.Draggable
10026 * @inherits Evented
10027 *
10028 * A class for making DOM elements draggable (including touch support).
10029 * Used internally for map and marker dragging. Only works for elements
10030 * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10031 *
10032 * @example
10033 * ```js
10034 * var draggable = new L.Draggable(elementToDrag);
10035 * draggable.enable();
10036 * ```
10037 */
10038
10039L.Draggable = L.Evented.extend({
10040
10041	options: {
10042		// @option clickTolerance: Number = 3
10043		// The max number of pixels a user can shift the mouse pointer during a click
10044		// for it to be considered a valid click (as opposed to a mouse drag).
10045		clickTolerance: 3
10046	},
10047
10048	statics: {
10049		START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10050		END: {
10051			mousedown: 'mouseup',
10052			touchstart: 'touchend',
10053			pointerdown: 'touchend',
10054			MSPointerDown: 'touchend'
10055		},
10056		MOVE: {
10057			mousedown: 'mousemove',
10058			touchstart: 'touchmove',
10059			pointerdown: 'touchmove',
10060			MSPointerDown: 'touchmove'
10061		}
10062	},
10063
10064	// @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10065	// Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10066	initialize: function (element, dragStartTarget, preventOutline) {
10067		this._element = element;
10068		this._dragStartTarget = dragStartTarget || element;
10069		this._preventOutline = preventOutline;
10070	},
10071
10072	// @method enable()
10073	// Enables the dragging ability
10074	enable: function () {
10075		if (this._enabled) { return; }
10076
10077		L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10078
10079		this._enabled = true;
10080	},
10081
10082	// @method disable()
10083	// Disables the dragging ability
10084	disable: function () {
10085		if (!this._enabled) { return; }
10086
10087		L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10088
10089		this._enabled = false;
10090		this._moved = false;
10091	},
10092
10093	_onDown: function (e) {
10094		// Ignore simulated events, since we handle both touch and
10095		// mouse explicitly; otherwise we risk getting duplicates of
10096		// touch events, see #4315.
10097		// Also ignore the event if disabled; this happens in IE11
10098		// under some circumstances, see #3666.
10099		if (e._simulated || !this._enabled) { return; }
10100
10101		this._moved = false;
10102
10103		if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10104
10105		if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches) || !this._enabled) { return; }
10106		L.Draggable._dragging = true;  // Prevent dragging multiple objects at once.
10107
10108		if (this._preventOutline) {
10109			L.DomUtil.preventOutline(this._element);
10110		}
10111
10112		L.DomUtil.disableImageDrag();
10113		L.DomUtil.disableTextSelection();
10114
10115		if (this._moving) { return; }
10116
10117		// @event down: Event
10118		// Fired when a drag is about to start.
10119		this.fire('down');
10120
10121		var first = e.touches ? e.touches[0] : e;
10122
10123		this._startPoint = new L.Point(first.clientX, first.clientY);
10124
10125		L.DomEvent
10126			.on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10127			.on(document, L.Draggable.END[e.type], this._onUp, this);
10128	},
10129
10130	_onMove: function (e) {
10131		// Ignore simulated events, since we handle both touch and
10132		// mouse explicitly; otherwise we risk getting duplicates of
10133		// touch events, see #4315.
10134		// Also ignore the event if disabled; this happens in IE11
10135		// under some circumstances, see #3666.
10136		if (e._simulated || !this._enabled) { return; }
10137
10138		if (e.touches && e.touches.length > 1) {
10139			this._moved = true;
10140			return;
10141		}
10142
10143		var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10144		    newPoint = new L.Point(first.clientX, first.clientY),
10145		    offset = newPoint.subtract(this._startPoint);
10146
10147		if (!offset.x && !offset.y) { return; }
10148		if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10149
10150		L.DomEvent.preventDefault(e);
10151
10152		if (!this._moved) {
10153			// @event dragstart: Event
10154			// Fired when a drag starts
10155			this.fire('dragstart');
10156
10157			this._moved = true;
10158			this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10159
10160			L.DomUtil.addClass(document.body, 'leaflet-dragging');
10161
10162			this._lastTarget = e.target || e.srcElement;
10163			// IE and Edge do not give the <use> element, so fetch it
10164			// if necessary
10165			if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10166				this._lastTarget = this._lastTarget.correspondingUseElement;
10167			}
10168			L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10169		}
10170
10171		this._newPos = this._startPos.add(offset);
10172		this._moving = true;
10173
10174		L.Util.cancelAnimFrame(this._animRequest);
10175		this._lastEvent = e;
10176		this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10177	},
10178
10179	_updatePosition: function () {
10180		var e = {originalEvent: this._lastEvent};
10181
10182		// @event predrag: Event
10183		// Fired continuously during dragging *before* each corresponding
10184		// update of the element's position.
10185		this.fire('predrag', e);
10186		L.DomUtil.setPosition(this._element, this._newPos);
10187
10188		// @event drag: Event
10189		// Fired continuously during dragging.
10190		this.fire('drag', e);
10191	},
10192
10193	_onUp: function (e) {
10194		// Ignore simulated events, since we handle both touch and
10195		// mouse explicitly; otherwise we risk getting duplicates of
10196		// touch events, see #4315.
10197		// Also ignore the event if disabled; this happens in IE11
10198		// under some circumstances, see #3666.
10199		if (e._simulated || !this._enabled) { return; }
10200
10201		L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10202
10203		if (this._lastTarget) {
10204			L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10205			this._lastTarget = null;
10206		}
10207
10208		for (var i in L.Draggable.MOVE) {
10209			L.DomEvent
10210				.off(document, L.Draggable.MOVE[i], this._onMove, this)
10211				.off(document, L.Draggable.END[i], this._onUp, this);
10212		}
10213
10214		L.DomUtil.enableImageDrag();
10215		L.DomUtil.enableTextSelection();
10216
10217		if (this._moved && this._moving) {
10218			// ensure drag is not fired after dragend
10219			L.Util.cancelAnimFrame(this._animRequest);
10220
10221			// @event dragend: DragEndEvent
10222			// Fired when the drag ends.
10223			this.fire('dragend', {
10224				distance: this._newPos.distanceTo(this._startPos)
10225			});
10226		}
10227
10228		this._moving = false;
10229		L.Draggable._dragging = false;
10230	}
10231});
10232
10233
10234
10235/*
10236	L.Handler is a base class for handler classes that are used internally to inject
10237	interaction features like dragging to classes like Map and Marker.
10238*/
10239
10240// @class Handler
10241// @aka L.Handler
10242// Abstract class for map interaction handlers
10243
10244L.Handler = L.Class.extend({
10245	initialize: function (map) {
10246		this._map = map;
10247	},
10248
10249	// @method enable(): this
10250	// Enables the handler
10251	enable: function () {
10252		if (this._enabled) { return this; }
10253
10254		this._enabled = true;
10255		this.addHooks();
10256		return this;
10257	},
10258
10259	// @method disable(): this
10260	// Disables the handler
10261	disable: function () {
10262		if (!this._enabled) { return this; }
10263
10264		this._enabled = false;
10265		this.removeHooks();
10266		return this;
10267	},
10268
10269	// @method enabled(): Boolean
10270	// Returns `true` if the handler is enabled
10271	enabled: function () {
10272		return !!this._enabled;
10273	}
10274
10275	// @section Extension methods
10276	// Classes inheriting from `Handler` must implement the two following methods:
10277	// @method addHooks()
10278	// Called when the handler is enabled, should add event hooks.
10279	// @method removeHooks()
10280	// Called when the handler is disabled, should remove the event hooks added previously.
10281});
10282
10283
10284
10285/*
10286 * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
10287 */
10288
10289// @namespace Map
10290// @section Interaction Options
10291L.Map.mergeOptions({
10292	// @option dragging: Boolean = true
10293	// Whether the map be draggable with mouse/touch or not.
10294	dragging: true,
10295
10296	// @section Panning Inertia Options
10297	// @option inertia: Boolean = *
10298	// If enabled, panning of the map will have an inertia effect where
10299	// the map builds momentum while dragging and continues moving in
10300	// the same direction for some time. Feels especially nice on touch
10301	// devices. Enabled by default unless running on old Android devices.
10302	inertia: !L.Browser.android23,
10303
10304	// @option inertiaDeceleration: Number = 3000
10305	// The rate with which the inertial movement slows down, in pixels/second².
10306	inertiaDeceleration: 3400, // px/s^2
10307
10308	// @option inertiaMaxSpeed: Number = Infinity
10309	// Max speed of the inertial movement, in pixels/second.
10310	inertiaMaxSpeed: Infinity, // px/s
10311
10312	// @option easeLinearity: Number = 0.2
10313	easeLinearity: 0.2,
10314
10315	// TODO refactor, move to CRS
10316	// @option worldCopyJump: Boolean = false
10317	// With this option enabled, the map tracks when you pan to another "copy"
10318	// of the world and seamlessly jumps to the original one so that all overlays
10319	// like markers and vector layers are still visible.
vendor: 5,208 bytes, lines 10320-10493
10320	worldCopyJump: false,
10321
10322	// @option maxBoundsViscosity: Number = 0.0
10323	// If `maxBounds` is set, this option will control how solid the bounds
10324	// are when dragging the map around. The default value of `0.0` allows the
10325	// user to drag outside the bounds at normal speed, higher values will
10326	// slow down map dragging outside bounds, and `1.0` makes the bounds fully
10327	// solid, preventing the user from dragging outside the bounds.
10328	maxBoundsViscosity: 0.0
10329});
10330
10331L.Map.Drag = L.Handler.extend({
10332	addHooks: function () {
10333		if (!this._draggable) {
10334			var map = this._map;
10335
10336			this._draggable = new L.Draggable(map._mapPane, map._container);
10337
10338			this._draggable.on({
10339				down: this._onDown,
10340				dragstart: this._onDragStart,
10341				drag: this._onDrag,
10342				dragend: this._onDragEnd
10343			}, this);
10344
10345			this._draggable.on('predrag', this._onPreDragLimit, this);
10346			if (map.options.worldCopyJump) {
10347				this._draggable.on('predrag', this._onPreDragWrap, this);
10348				map.on('zoomend', this._onZoomEnd, this);
10349
10350				map.whenReady(this._onZoomEnd, this);
10351			}
10352		}
10353		L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
10354		this._draggable.enable();
10355		this._positions = [];
10356		this._times = [];
10357	},
10358
10359	removeHooks: function () {
10360		L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
10361		L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
10362		this._draggable.disable();
10363	},
10364
10365	moved: function () {
10366		return this._draggable && this._draggable._moved;
10367	},
10368
10369	moving: function () {
10370		return this._draggable && this._draggable._moving;
10371	},
10372
10373	_onDown: function () {
10374		this._map._stop();
10375	},
10376
10377	_onDragStart: function () {
10378		var map = this._map;
10379
10380		if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
10381			var bounds = L.latLngBounds(this._map.options.maxBounds);
10382
10383			this._offsetLimit = L.bounds(
10384				this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
10385				this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
10386					.add(this._map.getSize()));
10387
10388			this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
10389		} else {
10390			this._offsetLimit = null;
10391		}
10392
10393		map
10394		    .fire('movestart')
10395		    .fire('dragstart');
10396
10397		if (map.options.inertia) {
10398			this._positions = [];
10399			this._times = [];
10400		}
10401	},
10402
10403	_onDrag: function (e) {
10404		if (this._map.options.inertia) {
10405			var time = this._lastTime = +new Date(),
10406			    pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
10407
10408			this._positions.push(pos);
10409			this._times.push(time);
10410
10411			if (time - this._times[0] > 50) {
10412				this._positions.shift();
10413				this._times.shift();
10414			}
10415		}
10416
10417		this._map
10418		    .fire('move', e)
10419		    .fire('drag', e);
10420	},
10421
10422	_onZoomEnd: function () {
10423		var pxCenter = this._map.getSize().divideBy(2),
10424		    pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
10425
10426		this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
10427		this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
10428	},
10429
10430	_viscousLimit: function (value, threshold) {
10431		return value - (value - threshold) * this._viscosity;
10432	},
10433
10434	_onPreDragLimit: function () {
10435		if (!this._viscosity || !this._offsetLimit) { return; }
10436
10437		var offset = this._draggable._newPos.subtract(this._draggable._startPos);
10438
10439		var limit = this._offsetLimit;
10440		if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
10441		if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
10442		if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
10443		if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
10444
10445		this._draggable._newPos = this._draggable._startPos.add(offset);
10446	},
10447
10448	_onPreDragWrap: function () {
10449		// TODO refactor to be able to adjust map pane position after zoom
10450		var worldWidth = this._worldWidth,
10451		    halfWidth = Math.round(worldWidth / 2),
10452		    dx = this._initialWorldOffset,
10453		    x = this._draggable._newPos.x,
10454		    newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
10455		    newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
10456		    newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
10457
10458		this._draggable._absPos = this._draggable._newPos.clone();
10459		this._draggable._newPos.x = newX;
10460	},
10461
10462	_onDragEnd: function (e) {
10463		var map = this._map,
10464		    options = map.options,
10465
10466		    noInertia = !options.inertia || this._times.length < 2;
10467
10468		map.fire('dragend', e);
10469
10470		if (noInertia) {
10471			map.fire('moveend');
10472
10473		} else {
10474
10475			var direction = this._lastPos.subtract(this._positions[0]),
10476			    duration = (this._lastTime - this._times[0]) / 1000,
10477			    ease = options.easeLinearity,
10478
10479			    speedVector = direction.multiplyBy(ease / duration),
10480			    speed = speedVector.distanceTo([0, 0]),
10481
10482			    limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
10483			    limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
10484
10485			    decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
10486			    offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
10487
10488			if (!offset.x && !offset.y) {
10489				map.fire('moveend');
10490
10491			} else {
10492				offset = map._limitOffset(offset, map.options.maxBounds);
10493
10494				L.Util.requestAnimFrame(function () {
10495					map.panBy(offset, {
10496						duration: decelerationDuration,
10497						easeLinearity: ease,
10498						noMoveStart: true,
10499						animate: true
10500					});
10501				});
10502			}
10503		}
10504	}
10505});
10506
10507// @section Handlers
10508// @property dragging: Handler
10509// Map dragging handler (by both mouse and touch).
10510L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
10511
10512
10513
10514/*
10515 * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
10516 */
10517
10518// @namespace Map
10519// @section Interaction Options
10520
10521L.Map.mergeOptions({
10522	// @option doubleClickZoom: Boolean|String = true
10523	// Whether the map can be zoomed in by double clicking on it and
10524	// zoomed out by double clicking while holding shift. If passed
10525	// `'center'`, double-click zoom will zoom to the center of the
10526	//  view regardless of where the mouse was.
10527	doubleClickZoom: true
10528});
10529
10530L.Map.DoubleClickZoom = L.Handler.extend({
10531	addHooks: function () {
10532		this._map.on('dblclick', this._onDoubleClick, this);
10533	},
10534
10535	removeHooks: function () {
10536		this._map.off('dblclick', this._onDoubleClick, this);
10537	},
10538
10539	_onDoubleClick: function (e) {
10540		var map = this._map,
10541		    oldZoom = map.getZoom(),
10542		    delta = map.options.zoomDelta,
10543		    zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
10544
10545		if (map.options.doubleClickZoom === 'center') {
10546			map.setZoom(zoom);
10547		} else {
10548			map.setZoomAround(e.containerPoint, zoom);
10549		}
10550	}
10551});
10552
10553// @section Handlers
10554//
10555// Map properties include interaction handlers that allow you to control
10556// interaction behavior in runtime, enabling or disabling certain features such
10557// as dragging or touch zoom (see `Handler` methods). For example:
10558//
10559// ```js
10560// map.doubleClickZoom.disable();
10561// ```
10562//
10563// @property doubleClickZoom: Handler
10564// Double click zoom handler.
10565L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
10566
10567
10568
10569/*
10570 * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
10571 */
10572
10573// @namespace Map
10574// @section Interaction Options
10575L.Map.mergeOptions({
10576	// @section Mousewheel options
10577	// @option scrollWheelZoom: Boolean|String = true
10578	// Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
10579	// it will zoom to the center of the view regardless of where the mouse was.
10580	scrollWheelZoom: true,
10581
10582	// @option wheelDebounceTime: Number = 40
10583	// Limits the rate at which a wheel can fire (in milliseconds). By default
10584	// user can't zoom via wheel more often than once per 40 ms.
10585	wheelDebounceTime: 40,
10586
10587	// @option wheelPxPerZoomLevel: Number = 60
10588	// How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
10589	// mean a change of one full zoom level. Smaller values will make wheel-zooming
10590	// faster (and vice versa).
10591	wheelPxPerZoomLevel: 60
10592});
10593
10594L.Map.ScrollWheelZoom = L.Handler.extend({
10595	addHooks: function () {
10596		L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
10597
10598		this._delta = 0;
10599	},
10600
10601	removeHooks: function () {
10602		L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
10603	},
10604
10605	_onWheelScroll: function (e) {
10606		var delta = L.DomEvent.getWheelDelta(e);
10607
10608		var debounce = this._map.options.wheelDebounceTime;
10609
10610		this._delta += delta;
10611		this._lastMousePos = this._map.mouseEventToContainerPoint(e);
10612
10613		if (!this._startTime) {
10614			this._startTime = +new Date();
10615		}
10616
10617		var left = Math.max(debounce - (+new Date() - this._startTime), 0);
10618
10619		clearTimeout(this._timer);
10620		this._timer = setTimeout(L.bind(this._performZoom, this), left);
10621
10622		L.DomEvent.stop(e);
10623	},
10624
10625	_performZoom: function () {
10626		var map = this._map,
10627		    zoom = map.getZoom(),
10628		    snap = this._map.options.zoomSnap || 0;
10629
10630		map._stop(); // stop panning and fly animations if any
10631
10632		// map the delta with a sigmoid function to -4..4 range leaning on -1..1
10633		var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
10634		    d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
10635		    d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
10636		    delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
10637
10638		this._delta = 0;
10639		this._startTime = null;
10640
10641		if (!delta) { return; }
10642
10643		if (map.options.scrollWheelZoom === 'center') {
10644			map.setZoom(zoom + delta);
10645		} else {
10646			map.setZoomAround(this._lastMousePos, zoom + delta);
10647		}
10648	}
10649});
10650
10651// @section Handlers
10652// @property scrollWheelZoom: Handler
10653// Scroll wheel zoom handler.
10654L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
10655
10656
10657
10658/*
10659 * Extends the event handling code with double tap support for mobile browsers.
10660 */
10661
10662L.extend(L.DomEvent, {
10663
10664	_touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
10665	_touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
10666
10667	// inspired by Zepto touch code by Thomas Fuchs
10668	addDoubleTapListener: function (obj, handler, id) {
10669		var last, touch,
10670		    doubleTap = false,
10671		    delay = 250;
10672
10673		function onTouchStart(e) {
10674			var count;
10675
10676			if (L.Browser.pointer) {
10677				count = L.DomEvent._pointersCount;
10678			} else {
10679				count = e.touches.length;
10680			}
10681
10682			if (count > 1) { return; }
10683
10684			var now = Date.now(),
10685			    delta = now - (last || now);
10686
10687			touch = e.touches ? e.touches[0] : e;
10688			doubleTap = (delta > 0 && delta <= delay);
10689			last = now;
10690		}
10691
10692		function onTouchEnd() {
10693			if (doubleTap && !touch.cancelBubble) {
10694				if (L.Browser.pointer) {
10695					// work around .type being readonly with MSPointer* events
10696					var newTouch = {},
10697					    prop, i;
10698
10699					for (i in touch) {
10700						prop = touch[i];
10701						newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
10702					}
10703					touch = newTouch;
10704				}
10705				touch.type = 'dblclick';
10706				handler(touch);
10707				last = null;
10708			}
10709		}
10710
10711		var pre = '_leaflet_',
10712		    touchstart = this._touchstart,
10713		    touchend = this._touchend;
10714
10715		obj[pre + touchstart + id] = onTouchStart;
10716		obj[pre + touchend + id] = onTouchEnd;
10717		obj[pre + 'dblclick' + id] = handler;
10718
10719		obj.addEventListener(touchstart, onTouchStart, false);
10720		obj.addEventListener(touchend, onTouchEnd, false);
10721
10722		// On some platforms (notably, chrome on win10 + touchscreen + mouse),
10723		// the browser doesn't fire touchend/pointerup events but does fire
10724		// native dblclicks. See #4127.
10725		if (!L.Browser.edge) {
10726			obj.addEventListener('dblclick', handler, false);
10727		}
10728
10729		return this;
10730	},
10731
10732	removeDoubleTapListener: function (obj, id) {
10733		var pre = '_leaflet_',
10734		    touchstart = obj[pre + this._touchstart + id],
10735		    touchend = obj[pre + this._touchend + id],
10736		    dblclick = obj[pre + 'dblclick' + id];
10737
10738		obj.removeEventListener(this._touchstart, touchstart, false);
10739		obj.removeEventListener(this._touchend, touchend, false);
10740		if (!L.Browser.edge) {
10741			obj.removeEventListener('dblclick', dblclick, false);
10742		}
10743
10744		return this;
10745	}
10746});
10747
10748
10749
10750/*
10751 * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
10752 */
10753
10754L.extend(L.DomEvent, {
10755
10756	POINTER_DOWN:   L.Browser.msPointer ? 'MSPointerDown'   : 'pointerdown',
10757	POINTER_MOVE:   L.Browser.msPointer ? 'MSPointerMove'   : 'pointermove',
10758	POINTER_UP:     L.Browser.msPointer ? 'MSPointerUp'     : 'pointerup',
10759	POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
10760	TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
10761
10762	_pointers: {},
10763	_pointersCount: 0,
10764
10765	// Provides a touch events wrapper for (ms)pointer events.
10766	// ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
10767
10768	addPointerListener: function (obj, type, handler, id) {
10769
10770		if (type === 'touchstart') {
10771			this._addPointerStart(obj, handler, id);
10772
10773		} else if (type === 'touchmove') {
10774			this._addPointerMove(obj, handler, id);
10775
10776		} else if (type === 'touchend') {
10777			this._addPointerEnd(obj, handler, id);
10778		}
10779
10780		return this;
10781	},
10782
10783	removePointerListener: function (obj, type, id) {
10784		var handler = obj['_leaflet_' + type + id];
10785
10786		if (type === 'touchstart') {
10787			obj.removeEventListener(this.POINTER_DOWN, handler, false);
10788
10789		} else if (type === 'touchmove') {
10790			obj.removeEventListener(this.POINTER_MOVE, handler, false);
10791
10792		} else if (type === 'touchend') {
10793			obj.removeEventListener(this.POINTER_UP, handler, false);
10794			obj.removeEventListener(this.POINTER_CANCEL, handler, false);
10795		}
10796
10797		return this;
10798	},
10799
10800	_addPointerStart: function (obj, handler, id) {
10801		var onDown = L.bind(function (e) {
10802			if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
10803				// In IE11, some touch events needs to fire for form controls, or
10804				// the controls will stop working. We keep a whitelist of tag names that
10805				// need these events. For other target tags, we prevent default on the event.
10806				if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
10807					L.DomEvent.preventDefault(e);
10808				} else {
10809					return;
10810				}
10811			}
10812
10813			this._handlePointer(e, handler);
10814		}, this);
10815
10816		obj['_leaflet_touchstart' + id] = onDown;
10817		obj.addEventListener(this.POINTER_DOWN, onDown, false);
10818
10819		// need to keep track of what pointers and how many are active to provide e.touches emulation
10820		if (!this._pointerDocListener) {
10821			var pointerUp = L.bind(this._globalPointerUp, this);
10822
10823			// we listen documentElement as any drags that end by moving the touch off the screen get fired there
10824			document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
10825			document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
10826			document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
10827			document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
10828
10829			this._pointerDocListener = true;
10830		}
10831	},
10832
10833	_globalPointerDown: function (e) {
10834		this._pointers[e.pointerId] = e;
10835		this._pointersCount++;
10836	},
10837
10838	_globalPointerMove: function (e) {
10839		if (this._pointers[e.pointerId]) {
10840			this._pointers[e.pointerId] = e;
10841		}
10842	},
10843
10844	_globalPointerUp: function (e) {
10845		delete this._pointers[e.pointerId];
10846		this._pointersCount--;
10847	},
10848
10849	_handlePointer: function (e, handler) {
10850		e.touches = [];
10851		for (var i in this._pointers) {
10852			e.touches.push(this._pointers[i]);
10853		}
10854		e.changedTouches = [e];
10855
10856		handler(e);
10857	},
10858
10859	_addPointerMove: function (obj, handler, id) {
10860		var onMove = L.bind(function (e) {
10861			// don't fire touch moves when mouse isn't down
10862			if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
10863
10864			this._handlePointer(e, handler);
10865		}, this);
10866
10867		obj['_leaflet_touchmove' + id] = onMove;
10868		obj.addEventListener(this.POINTER_MOVE, onMove, false);
10869	},
10870
10871	_addPointerEnd: function (obj, handler, id) {
10872		var onUp = L.bind(function (e) {
10873			this._handlePointer(e, handler);
10874		}, this);
10875
10876		obj['_leaflet_touchend' + id] = onUp;
10877		obj.addEventListener(this.POINTER_UP, onUp, false);
10878		obj.addEventListener(this.POINTER_CANCEL, onUp, false);
10879	}
10880});
10881
10882
10883
10884/*
10885 * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
10886 */
10887
10888// @namespace Map
10889// @section Interaction Options
10890L.Map.mergeOptions({
10891	// @section Touch interaction options
10892	// @option touchZoom: Boolean|String = *
10893	// Whether the map can be zoomed by touch-dragging with two fingers. If
10894	// passed `'center'`, it will zoom to the center of the view regardless of
10895	// where the touch events (fingers) were. Enabled for touch-capable web
10896	// browsers except for old Androids.
10897	touchZoom: L.Browser.touch && !L.Browser.android23,
10898
10899	// @option bounceAtZoomLimits: Boolean = true
10900	// Set it to false if you don't want the map to zoom beyond min/max zoom
10901	// and then bounce back when pinch-zooming.
10902	bounceAtZoomLimits: true
10903});
10904
10905L.Map.TouchZoom = L.Handler.extend({
10906	addHooks: function () {
10907		L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
10908		L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
10909	},
10910
10911	removeHooks: function () {
10912		L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
10913		L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
10914	},
10915
10916	_onTouchStart: function (e) {
10917		var map = this._map;
10918		if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
10919
10920		var p1 = map.mouseEventToContainerPoint(e.touches[0]),
10921		    p2 = map.mouseEventToContainerPoint(e.touches[1]);
10922
10923		this._centerPoint = map.getSize()._divideBy(2);
10924		this._startLatLng = map.containerPointToLatLng(this._centerPoint);
10925		if (map.options.touchZoom !== 'center') {
10926			this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
10927		}
10928
10929		this._startDist = p1.distanceTo(p2);
10930		this._startZoom = map.getZoom();
10931
10932		this._moved = false;
10933		this._zooming = true;
10934
10935		map._stop();
10936
10937		L.DomEvent
10938		    .on(document, 'touchmove', this._onTouchMove, this)
10939		    .on(document, 'touchend', this._onTouchEnd, this);
10940
10941		L.DomEvent.preventDefault(e);
10942	},
10943
10944	_onTouchMove: function (e) {
10945		if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
10946
10947		var map = this._map,
10948		    p1 = map.mouseEventToContainerPoint(e.touches[0]),
10949		    p2 = map.mouseEventToContainerPoint(e.touches[1]),
10950		    scale = p1.distanceTo(p2) / this._startDist;
10951
10952
10953		this._zoom = map.getScaleZoom(scale, this._startZoom);
10954
10955		if (!map.options.bounceAtZoomLimits && (
10956			(this._zoom < map.getMinZoom() && scale < 1) ||
10957			(this._zoom > map.getMaxZoom() && scale > 1))) {
10958			this._zoom = map._limitZoom(this._zoom);
10959		}
10960
10961		if (map.options.touchZoom === 'center') {
10962			this._center = this._startLatLng;
10963			if (scale === 1) { return; }
10964		} else {
10965			// Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
10966			var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
10967			if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
10968			this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
10969		}
10970
10971		if (!this._moved) {
10972			map._moveStart(true);
10973			this._moved = true;
10974		}
10975
10976		L.Util.cancelAnimFrame(this._animRequest);
10977
10978		var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
10979		this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
10980
10981		L.DomEvent.preventDefault(e);
10982	},
10983
10984	_onTouchEnd: function () {
10985		if (!this._moved || !this._zooming) {
10986			this._zooming = false;
10987			return;
10988		}
10989
10990		this._zooming = false;
10991		L.Util.cancelAnimFrame(this._animRequest);
10992
10993		L.DomEvent
10994		    .off(document, 'touchmove', this._onTouchMove)
10995		    .off(document, 'touchend', this._onTouchEnd);
10996
10997		// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
10998		if (this._map.options.zoomAnimation) {
10999			this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
11000		} else {
11001			this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11002		}
11003	}
11004});
11005
11006// @section Handlers
11007// @property touchZoom: Handler
11008// Touch zoom handler.
11009L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11010
11011
11012
11013/*
11014 * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11015 */
11016
11017// @namespace Map
11018// @section Interaction Options
11019L.Map.mergeOptions({
11020	// @section Touch interaction options
11021	// @option tap: Boolean = true
11022	// Enables mobile hacks for supporting instant taps (fixing 200ms click
11023	// delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11024	tap: true,
11025
11026	// @option tapTolerance: Number = 15
11027	// The max number of pixels a user can shift his finger during touch
11028	// for it to be considered a valid tap.
11029	tapTolerance: 15
11030});
11031
11032L.Map.Tap = L.Handler.extend({
11033	addHooks: function () {
11034		L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11035	},
11036
11037	removeHooks: function () {
11038		L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11039	},
11040
11041	_onDown: function (e) {
11042		if (!e.touches) { return; }
11043
11044		L.DomEvent.preventDefault(e);
11045
11046		this._fireClick = true;
11047
11048		// don't simulate click or track longpress if more than 1 touch
11049		if (e.touches.length > 1) {
11050			this._fireClick = false;
11051			clearTimeout(this._holdTimeout);
11052			return;
11053		}
11054
11055		var first = e.touches[0],
11056		    el = first.target;
11057
11058		this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11059
11060		// if touching a link, highlight it
11061		if (el.tagName && el.tagName.toLowerCase() === 'a') {
11062			L.DomUtil.addClass(el, 'leaflet-active');
11063		}
11064
11065		// simulate long hold but setting a timeout
11066		this._holdTimeout = setTimeout(L.bind(function () {
11067			if (this._isTapValid()) {
11068				this._fireClick = false;
11069				this._onUp();
11070				this._simulateEvent('contextmenu', first);
11071			}
11072		}, this), 1000);
11073
11074		this._simulateEvent('mousedown', first);
11075
11076		L.DomEvent.on(document, {
11077			touchmove: this._onMove,
11078			touchend: this._onUp
11079		}, this);
11080	},
11081
11082	_onUp: function (e) {
11083		clearTimeout(this._holdTimeout);
11084
11085		L.DomEvent.off(document, {
11086			touchmove: this._onMove,
11087			touchend: this._onUp
11088		}, this);
11089
11090		if (this._fireClick && e && e.changedTouches) {
11091
11092			var first = e.changedTouches[0],
11093			    el = first.target;
11094
11095			if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11096				L.DomUtil.removeClass(el, 'leaflet-active');
11097			}
11098
11099			this._simulateEvent('mouseup', first);
11100
11101			// simulate click if the touch didn't move too much
11102			if (this._isTapValid()) {
11103				this._simulateEvent('click', first);
11104			}
11105		}
11106	},
11107
11108	_isTapValid: function () {
11109		return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11110	},
11111
11112	_onMove: function (e) {
11113		var first = e.touches[0];
11114		this._newPos = new L.Point(first.clientX, first.clientY);
11115		this._simulateEvent('mousemove', first);
11116	},
11117
11118	_simulateEvent: function (type, e) {
11119		var simulatedEvent = document.createEvent('MouseEvents');
11120
11121		simulatedEvent._simulated = true;
11122		e.target._simulatedClick = true;
11123
11124		simulatedEvent.initMouseEvent(
11125		        type, true, true, window, 1,
11126		        e.screenX, e.screenY,
11127		        e.clientX, e.clientY,
11128		        false, false, false, false, 0, null);
11129
11130		e.target.dispatchEvent(simulatedEvent);
11131	}
11132});
11133
11134// @section Handlers
11135// @property tap: Handler
11136// Mobile touch hacks (quick tap and touch hold) handler.
11137if (L.Browser.touch && !L.Browser.pointer) {
11138	L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11139}
11140
11141
11142
11143/*
11144 * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11145 * (zoom to a selected bounding box), enabled by default.
11146 */
11147
11148// @namespace Map
11149// @section Interaction Options
11150L.Map.mergeOptions({
11151	// @option boxZoom: Boolean = true
11152	// Whether the map can be zoomed to a rectangular area specified by
11153	// dragging the mouse while pressing the shift key.
11154	boxZoom: true
11155});
11156
11157L.Map.BoxZoom = L.Handler.extend({
11158	initialize: function (map) {
11159		this._map = map;
11160		this._container = map._container;
11161		this._pane = map._panes.overlayPane;
11162	},
11163
11164	addHooks: function () {
11165		L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11166	},
11167
11168	removeHooks: function () {
11169		L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11170	},
11171
11172	moved: function () {
11173		return this._moved;
11174	},
11175
11176	_resetState: function () {
11177		this._moved = false;
11178	},
11179
11180	_onMouseDown: function (e) {
11181		if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11182
11183		this._resetState();
11184
11185		L.DomUtil.disableTextSelection();
11186		L.DomUtil.disableImageDrag();
11187
11188		this._startPoint = this._map.mouseEventToContainerPoint(e);
11189
11190		L.DomEvent.on(document, {
11191			contextmenu: L.DomEvent.stop,
11192			mousemove: this._onMouseMove,
11193			mouseup: this._onMouseUp,
11194			keydown: this._onKeyDown
11195		}, this);
11196	},
11197
11198	_onMouseMove: function (e) {
11199		if (!this._moved) {
11200			this._moved = true;
11201
11202			this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11203			L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11204
11205			this._map.fire('boxzoomstart');
11206		}
11207
11208		this._point = this._map.mouseEventToContainerPoint(e);
11209
11210		var bounds = new L.Bounds(this._point, this._startPoint),
11211		    size = bounds.getSize();
11212
11213		L.DomUtil.setPosition(this._box, bounds.min);
11214
11215		this._box.style.width  = size.x + 'px';
11216		this._box.style.height = size.y + 'px';
11217	},
11218
11219	_finish: function () {
11220		if (this._moved) {
11221			L.DomUtil.remove(this._box);
11222			L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11223		}
11224
11225		L.DomUtil.enableTextSelection();
11226		L.DomUtil.enableImageDrag();
11227
11228		L.DomEvent.off(document, {
11229			contextmenu: L.DomEvent.stop,
11230			mousemove: this._onMouseMove,
11231			mouseup: this._onMouseUp,
11232			keydown: this._onKeyDown
11233		}, this);
11234	},
11235
11236	_onMouseUp: function (e) {
11237		if ((e.which !== 1) && (e.button !== 1)) { return; }
11238
11239		this._finish();
11240
11241		if (!this._moved) { return; }
11242		// Postpone to next JS tick so internal click event handling
11243		// still see it as "moved".
11244		setTimeout(L.bind(this._resetState, this), 0);
11245
11246		var bounds = new L.LatLngBounds(
11247		        this._map.containerPointToLatLng(this._startPoint),
11248		        this._map.containerPointToLatLng(this._point));
11249
11250		this._map
11251			.fitBounds(bounds)
11252			.fire('boxzoomend', {boxZoomBounds: bounds});
11253	},
11254
11255	_onKeyDown: function (e) {
11256		if (e.keyCode === 27) {
11257			this._finish();
11258		}
11259	}
11260});
11261
11262// @section Handlers
11263// @property boxZoom: Handler
11264// Box (shift-drag with mouse) zoom handler.
11265L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11266
11267
11268
11269/*
11270 * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11271 */
11272
11273// @namespace Map
11274// @section Keyboard Navigation Options
11275L.Map.mergeOptions({
11276	// @option keyboard: Boolean = true
11277	// Makes the map focusable and allows users to navigate the map with keyboard
11278	// arrows and `+`/`-` keys.
11279	keyboard: true,
11280
11281	// @option keyboardPanDelta: Number = 80
11282	// Amount of pixels to pan when pressing an arrow key.
11283	keyboardPanDelta: 80
11284});
11285
11286L.Map.Keyboard = L.Handler.extend({
11287
11288	keyCodes: {
11289		left:    [37],
11290		right:   [39],
11291		down:    [40],
11292		up:      [38],
11293		zoomIn:  [187, 107, 61, 171],
11294		zoomOut: [189, 109, 54, 173]
11295	},
11296
11297	initialize: function (map) {
11298		this._map = map;
11299
11300		this._setPanDelta(map.options.keyboardPanDelta);
11301		this._setZoomDelta(map.options.zoomDelta);
11302	},
11303
11304	addHooks: function () {
11305		var container = this._map._container;
11306
11307		// make the container focusable by tabbing
11308		if (container.tabIndex <= 0) {
11309			container.tabIndex = '0';
11310		}
11311
11312		L.DomEvent.on(container, {
11313			focus: this._onFocus,
11314			blur: this._onBlur,
11315			mousedown: this._onMouseDown
11316		}, this);
11317
11318		this._map.on({
11319			focus: this._addHooks,
11320			blur: this._removeHooks
11321		}, this);
11322	},
11323
11324	removeHooks: function () {
11325		this._removeHooks();
11326
11327		L.DomEvent.off(this._map._container, {
11328			focus: this._onFocus,
11329			blur: this._onBlur,
11330			mousedown: this._onMouseDown
11331		}, this);
11332
11333		this._map.off({
11334			focus: this._addHooks,
11335			blur: this._removeHooks
11336		}, this);
11337	},
11338
11339	_onMouseDown: function () {
11340		if (this._focused) { return; }
11341
11342		var body = document.body,
11343		    docEl = document.documentElement,
11344		    top = body.scrollTop || docEl.scrollTop,
11345		    left = body.scrollLeft || docEl.scrollLeft;
11346
11347		this._map._container.focus();
11348
11349		window.scrollTo(left, top);
11350	},
11351
11352	_onFocus: function () {
11353		this._focused = true;
11354		this._map.fire('focus');
11355	},
11356
11357	_onBlur: function () {
11358		this._focused = false;
11359		this._map.fire('blur');
11360	},
11361
11362	_setPanDelta: function (panDelta) {
11363		var keys = this._panKeys = {},
11364		    codes = this.keyCodes,
11365		    i, len;
11366
11367		for (i = 0, len = codes.left.length; i < len; i++) {
11368			keys[codes.left[i]] = [-1 * panDelta, 0];
11369		}
11370		for (i = 0, len = codes.right.length; i < len; i++) {
11371			keys[codes.right[i]] = [panDelta, 0];
11372		}
11373		for (i = 0, len = codes.down.length; i < len; i++) {
11374			keys[codes.down[i]] = [0, panDelta];
11375		}
11376		for (i = 0, len = codes.up.length; i < len; i++) {
11377			keys[codes.up[i]] = [0, -1 * panDelta];
11378		}
11379	},
11380
11381	_setZoomDelta: function (zoomDelta) {
11382		var keys = this._zoomKeys = {},
11383		    codes = this.keyCodes,
11384		    i, len;
11385
11386		for (i = 0, len = codes.zoomIn.length; i < len; i++) {
11387			keys[codes.zoomIn[i]] = zoomDelta;
11388		}
11389		for (i = 0, len = codes.zoomOut.length; i < len; i++) {
11390			keys[codes.zoomOut[i]] = -zoomDelta;
11391		}
11392	},
11393
11394	_addHooks: function () {
11395		L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
11396	},
11397
11398	_removeHooks: function () {
11399		L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
11400	},
11401
11402	_onKeyDown: function (e) {
11403		if (e.altKey || e.ctrlKey || e.metaKey) { return; }
11404
11405		var key = e.keyCode,
11406		    map = this._map,
11407		    offset;
11408
11409		if (key in this._panKeys) {
11410
11411			if (map._panAnim && map._panAnim._inProgress) { return; }
11412
11413			offset = this._panKeys[key];
11414			if (e.shiftKey) {
11415				offset = L.point(offset).multiplyBy(3);
11416			}
11417
11418			map.panBy(offset);
11419
11420			if (map.options.maxBounds) {
11421				map.panInsideBounds(map.options.maxBounds);
11422			}
11423
11424		} else if (key in this._zoomKeys) {
11425			map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
11426
11427		} else if (key === 27) {
11428			map.closePopup();
11429
11430		} else {
11431			return;
11432		}
11433
11434		L.DomEvent.stop(e);
11435	}
11436});
11437
11438// @section Handlers
11439// @section Handlers
11440// @property keyboard: Handler
11441// Keyboard navigation handler.
11442L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
11443
11444
11445
11446/*
11447 * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
11448 */
11449
11450
11451/* @namespace Marker
11452 * @section Interaction handlers
11453 *
11454 * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
11455 *
11456 * ```js
11457 * marker.dragging.disable();
11458 * ```
11459 *
11460 * @property dragging: Handler
11461 * Marker dragging handler (by both mouse and touch).
11462 */
11463
11464L.Handler.MarkerDrag = L.Handler.extend({
11465	initialize: function (marker) {
11466		this._marker = marker;
11467	},
11468
11469	addHooks: function () {
11470		var icon = this._marker._icon;
11471
11472		if (!this._draggable) {
11473			this._draggable = new L.Draggable(icon, icon, true);
11474		}
11475
11476		this._draggable.on({
11477			dragstart: this._onDragStart,
11478			drag: this._onDrag,
11479			dragend: this._onDragEnd
11480		}, this).enable();
11481
11482		L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
11483	},
11484
11485	removeHooks: function () {
11486		this._draggable.off({
11487			dragstart: this._onDragStart,
11488			drag: this._onDrag,
11489			dragend: this._onDragEnd
11490		}, this).disable();
11491
11492		if (this._marker._icon) {
11493			L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
11494		}
11495	},
11496
11497	moved: function () {
11498		return this._draggable && this._draggable._moved;
11499	},
11500
11501	_onDragStart: function () {
11502		// @section Dragging events
11503		// @event dragstart: Event
11504		// Fired when the user starts dragging the marker.
11505
11506		// @event movestart: Event
11507		// Fired when the marker starts moving (because of dragging).
11508
11509		this._oldLatLng = this._marker.getLatLng();
11510		this._marker
11511		    .closePopup()
11512		    .fire('movestart')
11513		    .fire('dragstart');
11514	},
11515
11516	_onDrag: function (e) {
11517		var marker = this._marker,
11518		    shadow = marker._shadow,
11519		    iconPos = L.DomUtil.getPosition(marker._icon),
11520		    latlng = marker._map.layerPointToLatLng(iconPos);
11521
11522		// update shadow position
11523		if (shadow) {
11524			L.DomUtil.setPosition(shadow, iconPos);
11525		}
11526
11527		marker._latlng = latlng;
11528		e.latlng = latlng;
11529		e.oldLatLng = this._oldLatLng;
11530
11531		// @event drag: Event
11532		// Fired repeatedly while the user drags the marker.
11533		marker
11534		    .fire('move', e)
11535		    .fire('drag', e);
11536	},
11537
11538	_onDragEnd: function (e) {
11539		// @event dragend: DragEndEvent
11540		// Fired when the user stops dragging the marker.
11541
11542		// @event moveend: Event
11543		// Fired when the marker stops moving (because of dragging).
11544		delete this._oldLatLng;
11545		this._marker
11546		    .fire('moveend')
11547		    .fire('dragend', e);
11548	}
11549});
11550
11551
11552
11553/*
11554 * @class Control
11555 * @aka L.Control
11556 *
11557 * L.Control is a base class for implementing map controls. Handles positioning.
11558 * All other controls extend from this class.
11559 */
11560
11561L.Control = L.Class.extend({
11562	// @section
11563	// @aka Control options
11564	options: {
11565		// @option position: String = 'topright'
11566		// The position of the control (one of the map corners). Possible values are `'topleft'`,
11567		// `'topright'`, `'bottomleft'` or `'bottomright'`
11568		position: 'topright'
11569	},
11570
11571	initialize: function (options) {
11572		L.setOptions(this, options);
11573	},
11574
11575	/* @section
11576	 * Classes extending L.Control will inherit the following methods:
11577	 *
11578	 * @method getPosition: string
11579	 * Returns the position of the control.
11580	 */
11581	getPosition: function () {
11582		return this.options.position;
11583	},
11584
11585	// @method setPosition(position: string): this
11586	// Sets the position of the control.
11587	setPosition: function (position) {
11588		var map = this._map;
11589
11590		if (map) {
11591			map.removeControl(this);
11592		}
11593
11594		this.options.position = position;
11595
11596		if (map) {
11597			map.addControl(this);
11598		}
11599
11600		return this;
11601	},
11602
11603	// @method getContainer: HTMLElement
11604	// Returns the HTMLElement that contains the control.
11605	getContainer: function () {
11606		return this._container;
11607	},
11608
11609	// @method addTo(map: Map): this
11610	// Adds the control to the given map.
11611	addTo: function (map) {
11612		this.remove();
11613		this._map = map;
11614
11615		var container = this._container = this.onAdd(map),
11616		    pos = this.getPosition(),
11617		    corner = map._controlCorners[pos];
11618
11619		L.DomUtil.addClass(container, 'leaflet-control');
11620
11621		if (pos.indexOf('bottom') !== -1) {
11622			corner.insertBefore(container, corner.firstChild);
11623		} else {
11624			corner.appendChild(container);
11625		}
11626
11627		return this;
11628	},
11629
11630	// @method remove: this
11631	// Removes the control from the map it is currently active on.
11632	remove: function () {
11633		if (!this._map) {
11634			return this;
11635		}
11636
11637		L.DomUtil.remove(this._container);
11638
11639		if (this.onRemove) {
11640			this.onRemove(this._map);
11641		}
11642
11643		this._map = null;
11644
11645		return this;
11646	},
11647
11648	_refocusOnMap: function (e) {
11649		// if map exists and event is not a keyboard event
11650		if (this._map && e && e.screenX > 0 && e.screenY > 0) {
11651			this._map.getContainer().focus();
11652		}
11653	}
11654});
11655
11656L.control = function (options) {
11657	return new L.Control(options);
11658};
11659
11660/* @section Extension methods
11661 * @uninheritable
11662 *
11663 * Every control should extend from `L.Control` and (re-)implement the following methods.
11664 *
11665 * @method onAdd(map: Map): HTMLElement
11666 * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
11667 *
11668 * @method onRemove(map: Map)
11669 * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
11670 */
11671
11672/* @namespace Map
11673 * @section Methods for Layers and Controls
11674 */
11675L.Map.include({
11676	// @method addControl(control: Control): this
11677	// Adds the given control to the map
11678	addControl: function (control) {
11679		control.addTo(this);
11680		return this;
11681	},
11682
11683	// @method removeControl(control: Control): this
11684	// Removes the given control from the map
11685	removeControl: function (control) {
11686		control.remove();
11687		return this;
11688	},
11689
11690	_initControlPos: function () {
11691		var corners = this._controlCorners = {},
11692		    l = 'leaflet-',
11693		    container = this._controlContainer =
11694		            L.DomUtil.create('div', l + 'control-container', this._container);
11695
11696		function createCorner(vSide, hSide) {
11697			var className = l + vSide + ' ' + l + hSide;
11698
11699			corners[vSide + hSide] = L.DomUtil.create('div', className, container);
11700		}
11701
11702		createCorner('top', 'left');
11703		createCorner('top', 'right');
11704		createCorner('bottom', 'left');
11705		createCorner('bottom', 'right');
11706	},
11707
11708	_clearControlPos: function () {
11709		L.DomUtil.remove(this._controlContainer);
11710	}
11711});
11712
11713
11714
11715/*
11716 * @class Control.Zoom
11717 * @aka L.Control.Zoom
11718 * @inherits Control
11719 *
11720 * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
11721 */
11722
11723L.Control.Zoom = L.Control.extend({
11724	// @section
11725	// @aka Control.Zoom options
11726	options: {
11727		position: 'topleft',
11728
11729		// @option zoomInText: String = '+'
11730		// The text set on the 'zoom in' button.
11731		zoomInText: '+',
11732
11733		// @option zoomInTitle: String = 'Zoom in'
11734		// The title set on the 'zoom in' button.
11735		zoomInTitle: 'Zoom in',
11736
11737		// @option zoomOutText: String = '-'
11738		// The text set on the 'zoom out' button.
11739		zoomOutText: '-',
11740
11741		// @option zoomOutTitle: String = 'Zoom out'
11742		// The title set on the 'zoom out' button.
11743		zoomOutTitle: 'Zoom out'
11744	},
11745
11746	onAdd: function (map) {
11747		var zoomName = 'leaflet-control-zoom',
11748		    container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
11749		    options = this.options;
11750
11751		this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
11752		        zoomName + '-in',  container, this._zoomIn);
11753		this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
11754		        zoomName + '-out', container, this._zoomOut);
11755
11756		this._updateDisabled();
11757		map.on('zoomend zoomlevelschange', this._updateDisabled, this);
11758
11759		return container;
11760	},
11761
11762	onRemove: function (map) {
11763		map.off('zoomend zoomlevelschange', this._updateDisabled, this);
11764	},
11765
11766	disable: function () {
11767		this._disabled = true;
11768		this._updateDisabled();
11769		return this;
11770	},
11771
11772	enable: function () {
11773		this._disabled = false;
11774		this._updateDisabled();
11775		return this;
11776	},
11777
11778	_zoomIn: function (e) {
11779		if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
11780			this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11781		}
11782	},
11783
11784	_zoomOut: function (e) {
11785		if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
11786			this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11787		}
11788	},
11789
11790	_createButton: function (html, title, className, container, fn) {
11791		var link = L.DomUtil.create('a', className, container);
11792		link.innerHTML = html;
11793		link.href = '#';
11794		link.title = title;
11795
11796		L.DomEvent
11797		    .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
11798		    .on(link, 'click', L.DomEvent.stop)
11799		    .on(link, 'click', fn, this)
11800		    .on(link, 'click', this._refocusOnMap, this);
11801
11802		return link;
11803	},
11804
11805	_updateDisabled: function () {
11806		var map = this._map,
11807		    className = 'leaflet-disabled';
11808
11809		L.DomUtil.removeClass(this._zoomInButton, className);
11810		L.DomUtil.removeClass(this._zoomOutButton, className);
11811
11812		if (this._disabled || map._zoom === map.getMinZoom()) {
11813			L.DomUtil.addClass(this._zoomOutButton, className);
11814		}
11815		if (this._disabled || map._zoom === map.getMaxZoom()) {
11816			L.DomUtil.addClass(this._zoomInButton, className);
11817		}
11818	}
11819});
11820
11821// @namespace Map
11822// @section Control options
11823// @option zoomControl: Boolean = true
11824// Whether a [zoom control](#control-zoom) is added to the map by default.
11825L.Map.mergeOptions({
11826	zoomControl: true
11827});
11828
11829L.Map.addInitHook(function () {
11830	if (this.options.zoomControl) {
11831		this.zoomControl = new L.Control.Zoom();
11832		this.addControl(this.zoomControl);
11833	}
11834});
11835
11836// @namespace Control.Zoom
11837// @factory L.control.zoom(options: Control.Zoom options)
11838// Creates a zoom control
11839L.control.zoom = function (options) {
11840	return new L.Control.Zoom(options);
11841};
11842
11843
11844
11845/*
11846 * @class Control.Attribution
11847 * @aka L.Control.Attribution
11848 * @inherits Control
11849 *
11850 * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
11851 */
11852
11853L.Control.Attribution = L.Control.extend({
11854	// @section
11855	// @aka Control.Attribution options
11856	options: {
11857		position: 'bottomright',
11858
11859		// @option prefix: String = 'Leaflet'
11860		// The HTML text shown before the attributions. Pass `false` to disable.
11861		prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
11862	},
11863
11864	initialize: function (options) {
11865		L.setOptions(this, options);
11866
11867		this._attributions = {};
11868	},
11869
11870	onAdd: function (map) {
11871		map.attributionControl = this;
11872		this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
11873		if (L.DomEvent) {
11874			L.DomEvent.disableClickPropagation(this._container);
11875		}
11876
11877		// TODO ugly, refactor
11878		for (var i in map._layers) {
11879			if (map._layers[i].getAttribution) {
11880				this.addAttribution(map._layers[i].getAttribution());
11881			}
11882		}
11883
11884		this._update();
11885
11886		return this._container;
11887	},
11888
11889	// @method setPrefix(prefix: String): this
11890	// Sets the text before the attributions.
11891	setPrefix: function (prefix) {
11892		this.options.prefix = prefix;
11893		this._update();
11894		return this;
11895	},
11896
11897	// @method addAttribution(text: String): this
11898	// Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
11899	addAttribution: function (text) {
11900		if (!text) { return this; }
11901
11902		if (!this._attributions[text]) {
11903			this._attributions[text] = 0;
11904		}
11905		this._attributions[text]++;
11906
11907		this._update();
11908
11909		return this;
11910	},
11911
11912	// @method removeAttribution(text: String): this
11913	// Removes an attribution text.
11914	removeAttribution: function (text) {
11915		if (!text) { return this; }
11916
11917		if (this._attributions[text]) {
11918			this._attributions[text]--;
11919			this._update();
11920		}
11921
11922		return this;
11923	},
11924
11925	_update: function () {
11926		if (!this._map) { return; }
11927
11928		var attribs = [];
11929
11930		for (var i in this._attributions) {
11931			if (this._attributions[i]) {
11932				attribs.push(i);
11933			}
11934		}
11935
11936		var prefixAndAttribs = [];
11937
11938		if (this.options.prefix) {
11939			prefixAndAttribs.push(this.options.prefix);
11940		}
11941		if (attribs.length) {
11942			prefixAndAttribs.push(attribs.join(', '));
11943		}
11944
11945		this._container.innerHTML = prefixAndAttribs.join(' | ');
11946	}
11947});
11948
11949// @namespace Map
11950// @section Control options
11951// @option attributionControl: Boolean = true
11952// Whether a [attribution control](#control-attribution) is added to the map by default.
11953L.Map.mergeOptions({
11954	attributionControl: true
11955});
11956
11957L.Map.addInitHook(function () {
11958	if (this.options.attributionControl) {
11959		new L.Control.Attribution().addTo(this);
11960	}
11961});
11962
11963// @namespace Control.Attribution
11964// @factory L.control.attribution(options: Control.Attribution options)
11965// Creates an attribution control.
11966L.control.attribution = function (options) {
11967	return new L.Control.Attribution(options);
11968};
11969
11970
11971
11972/*
11973 * @class Control.Scale
11974 * @aka L.Control.Scale
11975 * @inherits Control
11976 *
11977 * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
11978 *
11979 * @example
11980 *
11981 * ```js
11982 * L.control.scale().addTo(map);
11983 * ```
11984 */
11985
11986L.Control.Scale = L.Control.extend({
11987	// @section
11988	// @aka Control.Scale options
11989	options: {
11990		position: 'bottomleft',
11991
11992		// @option maxWidth: Number = 100
11993		// Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
11994		maxWidth: 100,
11995
11996		// @option metric: Boolean = True
11997		// Whether to show the metric scale line (m/km).
11998		metric: true,
11999
12000		// @option imperial: Boolean = True
12001		// Whether to show the imperial scale line (mi/ft).
vendor: 4,344 bytes, lines 12002-12166
12002		imperial: true
12003
12004		// @option updateWhenIdle: Boolean = false
12005		// If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12006	},
12007
12008	onAdd: function (map) {
12009		var className = 'leaflet-control-scale',
12010		    container = L.DomUtil.create('div', className),
12011		    options = this.options;
12012
12013		this._addScales(options, className + '-line', container);
12014
12015		map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12016		map.whenReady(this._update, this);
12017
12018		return container;
12019	},
12020
12021	onRemove: function (map) {
12022		map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12023	},
12024
12025	_addScales: function (options, className, container) {
12026		if (options.metric) {
12027			this._mScale = L.DomUtil.create('div', className, container);
12028		}
12029		if (options.imperial) {
12030			this._iScale = L.DomUtil.create('div', className, container);
12031		}
12032	},
12033
12034	_update: function () {
12035		var map = this._map,
12036		    y = map.getSize().y / 2;
12037
12038		var maxMeters = map.distance(
12039				map.containerPointToLatLng([0, y]),
12040				map.containerPointToLatLng([this.options.maxWidth, y]));
12041
12042		this._updateScales(maxMeters);
12043	},
12044
12045	_updateScales: function (maxMeters) {
12046		if (this.options.metric && maxMeters) {
12047			this._updateMetric(maxMeters);
12048		}
12049		if (this.options.imperial && maxMeters) {
12050			this._updateImperial(maxMeters);
12051		}
12052	},
12053
12054	_updateMetric: function (maxMeters) {
12055		var meters = this._getRoundNum(maxMeters),
12056		    label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12057
12058		this._updateScale(this._mScale, label, meters / maxMeters);
12059	},
12060
12061	_updateImperial: function (maxMeters) {
12062		var maxFeet = maxMeters * 3.2808399,
12063		    maxMiles, miles, feet;
12064
12065		if (maxFeet > 5280) {
12066			maxMiles = maxFeet / 5280;
12067			miles = this._getRoundNum(maxMiles);
12068			this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12069
12070		} else {
12071			feet = this._getRoundNum(maxFeet);
12072			this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12073		}
12074	},
12075
12076	_updateScale: function (scale, text, ratio) {
12077		scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12078		scale.innerHTML = text;
12079	},
12080
12081	_getRoundNum: function (num) {
12082		var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12083		    d = num / pow10;
12084
12085		d = d >= 10 ? 10 :
12086		    d >= 5 ? 5 :
12087		    d >= 3 ? 3 :
12088		    d >= 2 ? 2 : 1;
12089
12090		return pow10 * d;
12091	}
12092});
12093
12094
12095// @factory L.control.scale(options?: Control.Scale options)
12096// Creates an scale control with the given options.
12097L.control.scale = function (options) {
12098	return new L.Control.Scale(options);
12099};
12100
12101
12102
12103/*
12104 * @class Control.Layers
12105 * @aka L.Control.Layers
12106 * @inherits Control
12107 *
12108 * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control.html)). Extends `Control`.
12109 *
12110 * @example
12111 *
12112 * ```js
12113 * var baseLayers = {
12114 * 	"Mapbox": mapbox,
12115 * 	"OpenStreetMap": osm
12116 * };
12117 *
12118 * var overlays = {
12119 * 	"Marker": marker,
12120 * 	"Roads": roadsLayer
12121 * };
12122 *
12123 * L.control.layers(baseLayers, overlays).addTo(map);
12124 * ```
12125 *
12126 * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12127 *
12128 * ```js
12129 * {
12130 *     "<someName1>": layer1,
12131 *     "<someName2>": layer2
12132 * }
12133 * ```
12134 *
12135 * The layer names can contain HTML, which allows you to add additional styling to the items:
12136 *
12137 * ```js
12138 * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12139 * ```
12140 */
12141
12142
12143L.Control.Layers = L.Control.extend({
12144	// @section
12145	// @aka Control.Layers options
12146	options: {
12147		// @option collapsed: Boolean = true
12148		// If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12149		collapsed: true,
12150		position: 'topright',
12151
12152		// @option autoZIndex: Boolean = true
12153		// If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
12154		autoZIndex: true,
12155
12156		// @option hideSingleBase: Boolean = false
12157		// If `true`, the base layers in the control will be hidden when there is only one.
12158		hideSingleBase: false
12159	},
12160
12161	initialize: function (baseLayers, overlays, options) {
12162		L.setOptions(this, options);
12163
12164		this._layers = [];
12165		this._lastZIndex = 0;
12166		this._handlingClick = false;
12167
12168		for (var i in baseLayers) {
12169			this._addLayer(baseLayers[i], i);
12170		}
12171
12172		for (i in overlays) {
12173			this._addLayer(overlays[i], i, true);
12174		}
12175	},
12176
12177	onAdd: function (map) {
12178		this._initLayout();
12179		this._update();
12180
12181		this._map = map;
12182		map.on('zoomend', this._checkDisabledLayers, this);
12183
12184		return this._container;
12185	},
12186
12187	onRemove: function () {
12188		this._map.off('zoomend', this._checkDisabledLayers, this);
12189
12190		for (var i = 0; i < this._layers.length; i++) {
12191			this._layers[i].layer.off('add remove', this._onLayerChange, this);
12192		}
12193	},
12194
12195	// @method addBaseLayer(layer: Layer, name: String): this
12196	// Adds a base layer (radio button entry) with the given name to the control.
12197	addBaseLayer: function (layer, name) {
12198		this._addLayer(layer, name);
12199		return (this._map) ? this._update() : this;
12200	},
12201
12202	// @method addOverlay(layer: Layer, name: String): this
12203	// Adds an overlay (checkbox entry) with the given name to the control.
12204	addOverlay: function (layer, name) {
12205		this._addLayer(layer, name, true);
12206		return (this._map) ? this._update() : this;
12207	},
12208
12209	// @method removeLayer(layer: Layer): this
12210	// Remove the given layer from the control.
12211	removeLayer: function (layer) {
12212		layer.off('add remove', this._onLayerChange, this);
12213
12214		var obj = this._getLayer(L.stamp(layer));
12215		if (obj) {
12216			this._layers.splice(this._layers.indexOf(obj), 1);
12217		}
12218		return (this._map) ? this._update() : this;
12219	},
12220
12221	// @method expand(): this
12222	// Expand the control container if collapsed.
12223	expand: function () {
12224		L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12225		this._form.style.height = null;
12226		var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12227		if (acceptableHeight < this._form.clientHeight) {
12228			L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12229			this._form.style.height = acceptableHeight + 'px';
12230		} else {
12231			L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12232		}
12233		this._checkDisabledLayers();
12234		return this;
12235	},
12236
12237	// @method collapse(): this
12238	// Collapse the control container if expanded.
12239	collapse: function () {
12240		L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12241		return this;
12242	},
12243
12244	_initLayout: function () {
12245		var className = 'leaflet-control-layers',
12246		    container = this._container = L.DomUtil.create('div', className);
12247
12248		// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
12249		container.setAttribute('aria-haspopup', true);
12250
12251		L.DomEvent.disableClickPropagation(container);
12252		if (!L.Browser.touch) {
12253			L.DomEvent.disableScrollPropagation(container);
12254		}
12255
12256		var form = this._form = L.DomUtil.create('form', className + '-list');
12257
12258		if (this.options.collapsed) {
12259			if (!L.Browser.android) {
12260				L.DomEvent.on(container, {
12261					mouseenter: this.expand,
12262					mouseleave: this.collapse
12263				}, this);
12264			}
12265
12266			var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
12267			link.href = '#';
12268			link.title = 'Layers';
12269
12270			if (L.Browser.touch) {
12271				L.DomEvent
12272				    .on(link, 'click', L.DomEvent.stop)
12273				    .on(link, 'click', this.expand, this);
12274			} else {
12275				L.DomEvent.on(link, 'focus', this.expand, this);
12276			}
12277
12278			// work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
12279			L.DomEvent.on(form, 'click', function () {
12280				setTimeout(L.bind(this._onInputClick, this), 0);
12281			}, this);
12282
12283			this._map.on('click', this.collapse, this);
12284			// TODO keyboard accessibility
12285		} else {
12286			this.expand();
12287		}
12288
12289		this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
12290		this._separator = L.DomUtil.create('div', className + '-separator', form);
12291		this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
12292
12293		container.appendChild(form);
12294	},
12295
12296	_getLayer: function (id) {
12297		for (var i = 0; i < this._layers.length; i++) {
12298
12299			if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
12300				return this._layers[i];
12301			}
12302		}
12303	},
12304
12305	_addLayer: function (layer, name, overlay) {
12306		layer.on('add remove', this._onLayerChange, this);
12307
12308		this._layers.push({
12309			layer: layer,
12310			name: name,
12311			overlay: overlay
12312		});
12313
12314		if (this.options.autoZIndex && layer.setZIndex) {
12315			this._lastZIndex++;
12316			layer.setZIndex(this._lastZIndex);
12317		}
12318	},
12319
12320	_update: function () {
12321		if (!this._container) { return this; }
12322
12323		L.DomUtil.empty(this._baseLayersList);
12324		L.DomUtil.empty(this._overlaysList);
12325
12326		var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
12327
12328		for (i = 0; i < this._layers.length; i++) {
12329			obj = this._layers[i];
12330			this._addItem(obj);
12331			overlaysPresent = overlaysPresent || obj.overlay;
12332			baseLayersPresent = baseLayersPresent || !obj.overlay;
12333			baseLayersCount += !obj.overlay ? 1 : 0;
12334		}
12335
12336		// Hide base layers section if there's only one layer.
12337		if (this.options.hideSingleBase) {
12338			baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
12339			this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
12340		}
12341
12342		this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
12343
12344		return this;
12345	},
12346
12347	_onLayerChange: function (e) {
12348		if (!this._handlingClick) {
12349			this._update();
12350		}
12351
12352		var obj = this._getLayer(L.stamp(e.target));
12353
12354		// @namespace Map
12355		// @section Layer events
12356		// @event baselayerchange: LayersControlEvent
12357		// Fired when the base layer is changed through the [layer control](#control-layers).
12358		// @event overlayadd: LayersControlEvent
12359		// Fired when an overlay is selected through the [layer control](#control-layers).
12360		// @event overlayremove: LayersControlEvent
12361		// Fired when an overlay is deselected through the [layer control](#control-layers).
12362		// @namespace Control.Layers
12363		var type = obj.overlay ?
12364			(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
12365			(e.type === 'add' ? 'baselayerchange' : null);
12366
12367		if (type) {
12368			this._map.fire(type, obj);
12369		}
12370	},
12371
12372	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
12373	_createRadioElement: function (name, checked) {
12374
12375		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
12376				name + '"' + (checked ? ' checked="checked"' : '') + '/>';
12377
12378		var radioFragment = document.createElement('div');
12379		radioFragment.innerHTML = radioHtml;
12380
12381		return radioFragment.firstChild;
12382	},
12383
12384	_addItem: function (obj) {
12385		var label = document.createElement('label'),
12386		    checked = this._map.hasLayer(obj.layer),
12387		    input;
12388
12389		if (obj.overlay) {
12390			input = document.createElement('input');
12391			input.type = 'checkbox';
12392			input.className = 'leaflet-control-layers-selector';
12393			input.defaultChecked = checked;
12394		} else {
12395			input = this._createRadioElement('leaflet-base-layers', checked);
12396		}
12397
12398		input.layerId = L.stamp(obj.layer);
12399
12400		L.DomEvent.on(input, 'click', this._onInputClick, this);
12401
12402		var name = document.createElement('span');
12403		name.innerHTML = ' ' + obj.name;
12404
12405		// Helps from preventing layer control flicker when checkboxes are disabled
12406		// https://github.com/Leaflet/Leaflet/issues/2771
12407		var holder = document.createElement('div');
12408
12409		label.appendChild(holder);
12410		holder.appendChild(input);
12411		holder.appendChild(name);
12412
12413		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
12414		container.appendChild(label);
12415
12416		this._checkDisabledLayers();
12417		return label;
12418	},
12419
12420	_onInputClick: function () {
12421		var inputs = this._form.getElementsByTagName('input'),
12422		    input, layer, hasLayer;
12423		var addedLayers = [],
12424		    removedLayers = [];
12425
12426		this._handlingClick = true;
12427
12428		for (var i = inputs.length - 1; i >= 0; i--) {
12429			input = inputs[i];
12430			layer = this._getLayer(input.layerId).layer;
12431			hasLayer = this._map.hasLayer(layer);
12432
12433			if (input.checked && !hasLayer) {
12434				addedLayers.push(layer);
12435
12436			} else if (!input.checked && hasLayer) {
12437				removedLayers.push(layer);
12438			}
12439		}
12440
12441		// Bugfix issue 2318: Should remove all old layers before readding new ones
12442		for (i = 0; i < removedLayers.length; i++) {
12443			this._map.removeLayer(removedLayers[i]);
12444		}
12445		for (i = 0; i < addedLayers.length; i++) {
12446			this._map.addLayer(addedLayers[i]);
12447		}
12448
12449		this._handlingClick = false;
12450
12451		this._refocusOnMap();
12452	},
12453
12454	_checkDisabledLayers: function () {
12455		var inputs = this._form.getElementsByTagName('input'),
12456		    input,
12457		    layer,
12458		    zoom = this._map.getZoom();
12459
12460		for (var i = inputs.length - 1; i >= 0; i--) {
12461			input = inputs[i];
12462			layer = this._getLayer(input.layerId).layer;
12463			input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
12464			                 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
12465
12466		}
12467	},
12468
12469	_expand: function () {
12470		// Backward compatibility, remove me in 1.1.
12471		return this.expand();
12472	},
12473
12474	_collapse: function () {
12475		// Backward compatibility, remove me in 1.1.
12476		return this.collapse();
12477	}
12478
12479});
12480
12481
12482// @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
12483// Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
12484L.control.layers = function (baseLayers, overlays, options) {
12485	return new L.Control.Layers(baseLayers, overlays, options);
12486};
12487
12488
12489
12490/*
12491 * @class PosAnimation
12492 * @aka L.PosAnimation
12493 * @inherits Evented
12494 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
12495 *
12496 * @example
12497 * ```js
12498 * var fx = new L.PosAnimation();
12499 * fx.run(el, [300, 500], 0.5);
12500 * ```
12501 *
12502 * @constructor L.PosAnimation()
12503 * Creates a `PosAnimation` object.
12504 *
12505 */
12506
12507L.PosAnimation = L.Evented.extend({
12508
12509	// @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
12510	// Run an animation of a given element to a new position, optionally setting
12511	// duration in seconds (`0.25` by default) and easing linearity factor (3rd
12512	// argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
12513	// `0.5` by default).
12514	run: function (el, newPos, duration, easeLinearity) {
12515		this.stop();
12516
12517		this._el = el;
12518		this._inProgress = true;
12519		this._duration = duration || 0.25;
12520		this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
12521
12522		this._startPos = L.DomUtil.getPosition(el);
12523		this._offset = newPos.subtract(this._startPos);
12524		this._startTime = +new Date();
12525
12526		// @event start: Event
12527		// Fired when the animation starts
12528		this.fire('start');
12529
12530		this._animate();
12531	},
12532
12533	// @method stop()
12534	// Stops the animation (if currently running).
12535	stop: function () {
12536		if (!this._inProgress) { return; }
12537
12538		this._step(true);
12539		this._complete();
12540	},
12541
12542	_animate: function () {
12543		// animation loop
12544		this._animId = L.Util.requestAnimFrame(this._animate, this);
12545		this._step();
12546	},
12547
12548	_step: function (round) {
12549		var elapsed = (+new Date()) - this._startTime,
12550		    duration = this._duration * 1000;
12551
12552		if (elapsed < duration) {
12553			this._runFrame(this._easeOut(elapsed / duration), round);
12554		} else {
12555			this._runFrame(1);
12556			this._complete();
12557		}
12558	},
12559
12560	_runFrame: function (progress, round) {
12561		var pos = this._startPos.add(this._offset.multiplyBy(progress));
12562		if (round) {
12563			pos._round();
12564		}
12565		L.DomUtil.setPosition(this._el, pos);
12566
12567		// @event step: Event
12568		// Fired continuously during the animation.
12569		this.fire('step');
12570	},
12571
12572	_complete: function () {
12573		L.Util.cancelAnimFrame(this._animId);
12574
12575		this._inProgress = false;
12576		// @event end: Event
12577		// Fired when the animation ends.
12578		this.fire('end');
12579	},
12580
12581	_easeOut: function (t) {
12582		return 1 - Math.pow(1 - t, this._easeOutPower);
12583	}
12584});
12585
12586
12587
12588/*
12589 * Extends L.Map to handle panning animations.
12590 */
12591
12592L.Map.include({
12593
12594	setView: function (center, zoom, options) {
12595
12596		zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
12597		center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
12598		options = options || {};
12599
12600		this._stop();
12601
12602		if (this._loaded && !options.reset && options !== true) {
12603
12604			if (options.animate !== undefined) {
12605				options.zoom = L.extend({animate: options.animate}, options.zoom);
12606				options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
12607			}
12608
12609			// try animating pan or zoom
12610			var moved = (this._zoom !== zoom) ?
12611				this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
12612				this._tryAnimatedPan(center, options.pan);
12613
12614			if (moved) {
12615				// prevent resize handler call, the view will refresh after animation anyway
12616				clearTimeout(this._sizeTimer);
12617				return this;
12618			}
12619		}
12620
12621		// animation didn't start, just reset the map view
12622		this._resetView(center, zoom);
12623
12624		return this;
12625	},
12626
12627	panBy: function (offset, options) {
12628		offset = L.point(offset).round();
12629		options = options || {};
12630
12631		if (!offset.x && !offset.y) {
12632			return this.fire('moveend');
12633		}
12634		// If we pan too far, Chrome gets issues with tiles
12635		// and makes them disappear or appear in the wrong place (slightly offset) #2602
12636		if (options.animate !== true && !this.getSize().contains(offset)) {
12637			this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
12638			return this;
12639		}
12640
12641		if (!this._panAnim) {
12642			this._panAnim = new L.PosAnimation();
12643
12644			this._panAnim.on({
12645				'step': this._onPanTransitionStep,
12646				'end': this._onPanTransitionEnd
12647			}, this);
12648		}
12649
12650		// don't fire movestart if animating inertia
12651		if (!options.noMoveStart) {
12652			this.fire('movestart');
12653		}
12654
12655		// animate pan unless animate: false specified
12656		if (options.animate !== false) {
12657			L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
12658
12659			var newPos = this._getMapPanePos().subtract(offset).round();
12660			this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
12661		} else {
12662			this._rawPanBy(offset);
12663			this.fire('move').fire('moveend');
12664		}
12665
12666		return this;
12667	},
12668
12669	_onPanTransitionStep: function () {
12670		this.fire('move');
12671	},
12672
12673	_onPanTransitionEnd: function () {
12674		L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
12675		this.fire('moveend');
12676	},
12677
12678	_tryAnimatedPan: function (center, options) {
12679		// difference between the new and current centers in pixels
12680		var offset = this._getCenterOffset(center)._floor();
12681
12682		// don't animate too far unless animate: true specified in options
12683		if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
12684
12685		this.panBy(offset, options);
12686
12687		return true;
12688	}
12689});
12690
12691
12692
12693/*
12694 * Extends L.Map to handle zoom animations.
12695 */
12696
12697// @namespace Map
12698// @section Animation Options
12699L.Map.mergeOptions({
12700	// @option zoomAnimation: Boolean = true
12701	// Whether the map zoom animation is enabled. By default it's enabled
12702	// in all browsers that support CSS3 Transitions except Android.
12703	zoomAnimation: true,
12704
12705	// @option zoomAnimationThreshold: Number = 4
12706	// Won't animate zoom if the zoom difference exceeds this value.
12707	zoomAnimationThreshold: 4
12708});
12709
12710var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera;
12711
12712if (zoomAnimated) {
12713
12714	L.Map.addInitHook(function () {
12715		// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
12716		this._zoomAnimated = this.options.zoomAnimation;
12717
12718		// zoom transitions run with the same duration for all layers, so if one of transitionend events
12719		// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
12720		if (this._zoomAnimated) {
12721
12722			this._createAnimProxy();
12723
12724			L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
12725		}
12726	});
12727}
12728
12729L.Map.include(!zoomAnimated ? {} : {
12730
12731	_createAnimProxy: function () {
12732
12733		var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
12734		this._panes.mapPane.appendChild(proxy);
12735
12736		this.on('zoomanim', function (e) {
12737			var prop = L.DomUtil.TRANSFORM,
12738			    transform = proxy.style[prop];
12739
12740			L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
12741
12742			// workaround for case when transform is the same and so transitionend event is not fired
12743			if (transform === proxy.style[prop] && this._animatingZoom) {
12744				this._onZoomTransitionEnd();
12745			}
12746		}, this);
12747
12748		this.on('load moveend', function () {
12749			var c = this.getCenter(),
12750			    z = this.getZoom();
12751			L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
12752		}, this);
12753	},
12754
12755	_catchTransitionEnd: function (e) {
12756		if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
12757			this._onZoomTransitionEnd();
12758		}
12759	},
12760
12761	_nothingToAnimate: function () {
12762		return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
12763	},
12764
12765	_tryAnimatedZoom: function (center, zoom, options) {
12766
12767		if (this._animatingZoom) { return true; }
12768
12769		options = options || {};
12770
12771		// don't animate if disabled, not supported or zoom difference is too large
12772		if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
12773		        Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
12774
12775		// offset is the pixel coords of the zoom origin relative to the current center
12776		var scale = this.getZoomScale(zoom),
12777		    offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
12778
12779		// don't animate if the zoom origin isn't within one screen from the current center, unless forced
12780		if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
12781
12782		L.Util.requestAnimFrame(function () {
12783			this
12784			    ._moveStart(true)
12785			    ._animateZoom(center, zoom, true);
12786		}, this);
12787
12788		return true;
12789	},
12790
12791	_animateZoom: function (center, zoom, startAnim, noUpdate) {
12792		if (startAnim) {
12793			this._animatingZoom = true;
12794
12795			// remember what center/zoom to set after animation
12796			this._animateToCenter = center;
12797			this._animateToZoom = zoom;
12798
12799			L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
12800		}
12801
12802		// @event zoomanim: ZoomAnimEvent
12803		// Fired on every frame of a zoom animation
12804		this.fire('zoomanim', {
12805			center: center,
12806			zoom: zoom,
12807			noUpdate: noUpdate
12808		});
12809
12810		// Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
12811		setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
12812	},
12813
12814	_onZoomTransitionEnd: function () {
12815		if (!this._animatingZoom) { return; }
12816
12817		L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
12818
12819		this._animatingZoom = false;
vendor: 3,295 bytes, lines 12820-12941
12820
12821		this._move(this._animateToCenter, this._animateToZoom);
12822
12823		// This anim frame should prevent an obscure iOS webkit tile loading race condition.
12824		L.Util.requestAnimFrame(function () {
12825			this._moveEnd(true);
12826		}, this);
12827	}
12828});
12829
12830
12831
12832// @namespace Map
12833// @section Methods for modifying map state
12834L.Map.include({
12835
12836	// @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
12837	// Sets the view of the map (geographical center and zoom) performing a smooth
12838	// pan-zoom animation.
12839	flyTo: function (targetCenter, targetZoom, options) {
12840
12841		options = options || {};
12842		if (options.animate === false || !L.Browser.any3d) {
12843			return this.setView(targetCenter, targetZoom, options);
12844		}
12845
12846		this._stop();
12847
12848		var from = this.project(this.getCenter()),
12849		    to = this.project(targetCenter),
12850		    size = this.getSize(),
12851		    startZoom = this._zoom;
12852
12853		targetCenter = L.latLng(targetCenter);
12854		targetZoom = targetZoom === undefined ? startZoom : targetZoom;
12855
12856		var w0 = Math.max(size.x, size.y),
12857		    w1 = w0 * this.getZoomScale(startZoom, targetZoom),
12858		    u1 = (to.distanceTo(from)) || 1,
12859		    rho = 1.42,
12860		    rho2 = rho * rho;
12861
12862		function r(i) {
12863			var s1 = i ? -1 : 1,
12864			    s2 = i ? w1 : w0,
12865			    t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
12866			    b1 = 2 * s2 * rho2 * u1,
12867			    b = t1 / b1,
12868			    sq = Math.sqrt(b * b + 1) - b;
12869
12870			    // workaround for floating point precision bug when sq = 0, log = -Infinite,
12871			    // thus triggering an infinite loop in flyTo
12872			    var log = sq < 0.000000001 ? -18 : Math.log(sq);
12873
12874			return log;
12875		}
12876
12877		function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
12878		function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
12879		function tanh(n) { return sinh(n) / cosh(n); }
12880
12881		var r0 = r(0);
12882
12883		function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
12884		function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
12885
12886		function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
12887
12888		var start = Date.now(),
12889		    S = (r(1) - r0) / rho,
12890		    duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
12891
12892		function frame() {
12893			var t = (Date.now() - start) / duration,
12894			    s = easeOut(t) * S;
12895
12896			if (t <= 1) {
12897				this._flyToFrame = L.Util.requestAnimFrame(frame, this);
12898
12899				this._move(
12900					this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
12901					this.getScaleZoom(w0 / w(s), startZoom),
12902					{flyTo: true});
12903
12904			} else {
12905				this
12906					._move(targetCenter, targetZoom)
12907					._moveEnd(true);
12908			}
12909		}
12910
12911		this._moveStart(true);
12912
12913		frame.call(this);
12914		return this;
12915	},
12916
12917	// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
12918	// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
12919	// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
12920	flyToBounds: function (bounds, options) {
12921		var target = this._getBoundsCenterZoom(bounds, options);
12922		return this.flyTo(target.center, target.zoom, options);
12923	}
12924});
12925
12926
12927
12928/*
12929 * Provides L.Map with convenient shortcuts for using browser geolocation features.
12930 */
12931
12932// @namespace Map
12933
12934L.Map.include({
12935	// @section Geolocation methods
12936	_defaultLocateOptions: {
12937		timeout: 10000,
12938		watch: false
12939		// setView: false
12940		// maxZoom: <Number>
12941		// maximumAge: 0
12942		// enableHighAccuracy: false
12943	},
12944
12945	// @method locate(options?: Locate options): this
12946	// Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
12947	// event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
12948	// and optionally sets the map view to the user's location with respect to
12949	// detection accuracy (or to the world view if geolocation failed).
12950	// Note that, if your page doesn't use HTTPS, this method will fail in
12951	// modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
12952	// See `Locate options` for more details.
12953	locate: function (options) {
12954
12955		options = this._locateOptions = L.extend({}, this._defaultLocateOptions, options);
12956
12957		if (!('geolocation' in navigator)) {
12958			this._handleGeolocationError({
12959				code: 0,
12960				message: 'Geolocation not supported.'
12961			});
12962			return this;
12963		}
12964
12965		var onResponse = L.bind(this._handleGeolocationResponse, this),
12966		    onError = L.bind(this._handleGeolocationError, this);
12967
12968		if (options.watch) {
12969			this._locationWatchId =
12970			        navigator.geolocation.watchPosition(onResponse, onError, options);
12971		} else {
12972			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
12973		}
12974		return this;
12975	},
12976
12977	// @method stopLocate(): this
12978	// Stops watching location previously initiated by `map.locate({watch: true})`
12979	// and aborts resetting the map view if map.locate was called with
12980	// `{setView: true}`.
12981	stopLocate: function () {
12982		if (navigator.geolocation && navigator.geolocation.clearWatch) {
12983			navigator.geolocation.clearWatch(this._locationWatchId);
12984		}
12985		if (this._locateOptions) {
12986			this._locateOptions.setView = false;
12987		}
12988		return this;
12989	},
12990
12991	_handleGeolocationError: function (error) {
12992		var c = error.code,
12993		    message = error.message ||
12994		            (c === 1 ? 'permission denied' :
12995		            (c === 2 ? 'position unavailable' : 'timeout'));
12996
12997		if (this._locateOptions.setView && !this._loaded) {
12998			this.fitWorld();
12999		}
13000
13001		// @section Location events
13002		// @event locationerror: ErrorEvent
13003		// Fired when geolocation (using the [`locate`](#map-locate) method) failed.
13004		this.fire('locationerror', {
13005			code: c,
13006			message: 'Geolocation error: ' + message + '.'
13007		});
13008	},
13009
13010	_handleGeolocationResponse: function (pos) {
13011		var lat = pos.coords.latitude,
13012		    lng = pos.coords.longitude,
13013		    latlng = new L.LatLng(lat, lng),
13014		    bounds = latlng.toBounds(pos.coords.accuracy),
13015		    options = this._locateOptions;
13016
13017		if (options.setView) {
13018			var zoom = this.getBoundsZoom(bounds);
13019			this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
13020		}
13021
13022		var data = {
13023			latlng: latlng,
13024			bounds: bounds,
13025			timestamp: pos.timestamp
13026		};
13027
13028		for (var i in pos.coords) {
13029			if (typeof pos.coords[i] === 'number') {
13030				data[i] = pos.coords[i];
13031			}
13032		}
13033
13034		// @event locationfound: LocationEvent
13035		// Fired when geolocation (using the [`locate`](#map-locate) method)
13036		// went successfully.
13037		this.fire('locationfound', data);
13038	}
13039});
13040
13041
13042
13043}(window, document));
13044//# sourceMappingURL=leaflet-src.map
13045/*
13046 Leaflet.markercluster, Provides Beautiful Animated Marker Clustering functionality for Leaflet, a JS library for interactive maps.
13047 https://github.com/Leaflet/Leaflet.markercluster
13048 (c) 2012-2013, Dave Leaver, smartrak
13049*/
13050(function (window, document, undefined) {/*
13051 * L.MarkerClusterGroup extends L.FeatureGroup by clustering the markers contained within
13052 */
13053
13054L.MarkerClusterGroup = L.FeatureGroup.extend({
13055
13056	options: {
13057		maxClusterRadius: 80, //A cluster will cover at most this many pixels from its center
13058		iconCreateFunction: null,
13059
13060		spiderfyOnMaxZoom: true,
13061		showCoverageOnHover: true,
13062		zoomToBoundsOnClick: true,
13063		singleMarkerMode: false,
13064
13065		disableClusteringAtZoom: null,
13066
13067		// Setting this to false prevents the removal of any clusters outside of the viewpoint, which
13068		// is the default behaviour for performance reasons.
13069		removeOutsideVisibleBounds: true,
13070
13071		// Set to false to disable all animations (zoom and spiderfy).
13072		// If false, option animateAddingMarkers below has no effect.
13073		// If L.DomUtil.TRANSITION is falsy, this option has no effect.
13074		animate: true,
13075
13076		//Whether to animate adding markers after adding the MarkerClusterGroup to the map
13077		// If you are adding individual markers set to true, if adding bulk markers leave false for massive performance gains.
13078		animateAddingMarkers: false,
13079
13080		//Increase to increase the distance away that spiderfied markers appear from the center
13081		spiderfyDistanceMultiplier: 1,
13082
13083		// Make it possible to specify a polyline options on a spider leg
13084		spiderLegPolylineOptions: { weight: 1.5, color: '#222', opacity: 0.5 },
vendor: 52,797 bytes, lines 13085-14968
13085
13086		// 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
13087		chunkedLoading: false,
13088		chunkInterval: 200, // process markers for a maximum of ~ n milliseconds (then trigger the chunkProgress callback)
13089		chunkDelay: 50, // at the end of each interval, give n milliseconds back to system/browser
13090		chunkProgress: null, // progress callback: function(processed, total, elapsed) (e.g. for a progress indicator)
13091
13092		//Options to pass to the L.Polygon constructor
13093		polygonOptions: {}
13094	},
13095
13096	initialize: function (options) {
13097		L.Util.setOptions(this, options);
13098		if (!this.options.iconCreateFunction) {
13099			this.options.iconCreateFunction = this._defaultIconCreateFunction;
13100		}
13101
13102		this._featureGroup = L.featureGroup();
13103		this._featureGroup.addEventParent(this);
13104
13105		this._nonPointGroup = L.featureGroup();
13106		this._nonPointGroup.addEventParent(this);
13107
13108		this._inZoomAnimation = 0;
13109		this._needsClustering = [];
13110		this._needsRemoving = []; //Markers removed while we aren't on the map need to be kept track of
13111		//The bounds of the currently shown area (from _getExpandedVisibleBounds) Updated on zoom/move
13112		this._currentShownBounds = null;
13113
13114		this._queue = [];
13115
13116		this._childMarkerEventHandlers = {
13117			'dragstart': this._childMarkerDragStart,
13118			'move': this._childMarkerMoved,
13119			'dragend': this._childMarkerDragEnd,
13120		};
13121
13122		// Hook the appropriate animation methods.
13123		var animate = L.DomUtil.TRANSITION && this.options.animate;
13124		L.extend(this, animate ? this._withAnimation : this._noAnimation);
13125		// Remember which MarkerCluster class to instantiate (animated or not).
13126		this._markerCluster = animate ? L.MarkerCluster : L.MarkerClusterNonAnimated;
13127	},
13128
13129	addLayer: function (layer) {
13130
13131		if (layer instanceof L.LayerGroup) {
13132			return this.addLayers([layer]);
13133		}
13134
13135		//Don't cluster non point data
13136		if (!layer.getLatLng) {
13137			this._nonPointGroup.addLayer(layer);
13138			this.fire('layeradd', { layer: layer });
13139			return this;
13140		}
13141
13142		if (!this._map) {
13143			this._needsClustering.push(layer);
13144			this.fire('layeradd', { layer: layer });
13145			return this;
13146		}
13147
13148		if (this.hasLayer(layer)) {
13149			return this;
13150		}
13151
13152
13153		//If we have already clustered we'll need to add this one to a cluster
13154
13155		if (this._unspiderfy) {
13156			this._unspiderfy();
13157		}
13158
13159		this._addLayer(layer, this._maxZoom);
13160		this.fire('layeradd', { layer: layer });
13161
13162		// Refresh bounds and weighted positions.
13163		this._topClusterLevel._recalculateBounds();
13164
13165		this._refreshClustersIcons();
13166
13167		//Work out what is visible
13168		var visibleLayer = layer,
13169		    currentZoom = this._zoom;
13170		if (layer.__parent) {
13171			while (visibleLayer.__parent._zoom >= currentZoom) {
13172				visibleLayer = visibleLayer.__parent;
13173			}
13174		}
13175
13176		if (this._currentShownBounds.contains(visibleLayer.getLatLng())) {
13177			if (this.options.animateAddingMarkers) {
13178				this._animationAddLayer(layer, visibleLayer);
13179			} else {
13180				this._animationAddLayerNonAnimated(layer, visibleLayer);
13181			}
13182		}
13183		return this;
13184	},
13185
13186	removeLayer: function (layer) {
13187
13188		if (layer instanceof L.LayerGroup) {
13189			return this.removeLayers([layer]);
13190		}
13191
13192		//Non point layers
13193		if (!layer.getLatLng) {
13194			this._nonPointGroup.removeLayer(layer);
13195			this.fire('layerremove', { layer: layer });
13196			return this;
13197		}
13198
13199		if (!this._map) {
13200			if (!this._arraySplice(this._needsClustering, layer) && this.hasLayer(layer)) {
13201				this._needsRemoving.push({ layer: layer, latlng: layer._latlng });
13202			}
13203			this.fire('layerremove', { layer: layer });
13204			return this;
13205		}
13206
13207		if (!layer.__parent) {
13208			return this;
13209		}
13210
13211		if (this._unspiderfy) {
13212			this._unspiderfy();
13213			this._unspiderfyLayer(layer);
13214		}
13215
13216		//Remove the marker from clusters
13217		this._removeLayer(layer, true);
13218		this.fire('layerremove', { layer: layer });
13219
13220		// Refresh bounds and weighted positions.
13221		this._topClusterLevel._recalculateBounds();
13222
13223		this._refreshClustersIcons();
13224
13225		layer.off(this._childMarkerEventHandlers, this);
13226
13227		if (this._featureGroup.hasLayer(layer)) {
13228			this._featureGroup.removeLayer(layer);
13229			if (layer.clusterShow) {
13230				layer.clusterShow();
13231			}
13232		}
13233
13234		return this;
13235	},
13236
13237	//Takes an array of markers and adds them in bulk
13238	addLayers: function (layersArray, skipLayerAddEvent) {
13239		if (!L.Util.isArray(layersArray)) {
13240			return this.addLayer(layersArray);
13241		}
13242
13243		var fg = this._featureGroup,
13244		    npg = this._nonPointGroup,
13245		    chunked = this.options.chunkedLoading,
13246		    chunkInterval = this.options.chunkInterval,
13247		    chunkProgress = this.options.chunkProgress,
13248		    l = layersArray.length,
13249		    offset = 0,
13250		    originalArray = true,
13251		    m;
13252
13253		if (this._map) {
13254			var started = (new Date()).getTime();
13255			var process = L.bind(function () {
13256				var start = (new Date()).getTime();
13257				for (; offset < l; offset++) {
13258					if (chunked && offset % 200 === 0) {
13259						// every couple hundred markers, instrument the time elapsed since processing started:
13260						var elapsed = (new Date()).getTime() - start;
13261						if (elapsed > chunkInterval) {
13262							break; // been working too hard, time to take a break :-)
13263						}
13264					}
13265
13266					m = layersArray[offset];
13267
13268					// Group of layers, append children to layersArray and skip.
13269					// Side effects:
13270					// - Total increases, so chunkProgress ratio jumps backward.
13271					// - Groups are not included in this group, only their non-group child layers (hasLayer).
13272					// Changing array length while looping does not affect performance in current browsers:
13273					// http://jsperf.com/for-loop-changing-length/6
13274					if (m instanceof L.LayerGroup) {
13275						if (originalArray) {
13276							layersArray = layersArray.slice();
13277							originalArray = false;
13278						}
13279						this._extractNonGroupLayers(m, layersArray);
13280						l = layersArray.length;
13281						continue;
13282					}
13283
13284					//Not point data, can't be clustered
13285					if (!m.getLatLng) {
13286						npg.addLayer(m);
13287						if (!skipLayerAddEvent) {
13288							this.fire('layeradd', { layer: m });
13289						}
13290						continue;
13291					}
13292
13293					if (this.hasLayer(m)) {
13294						continue;
13295					}
13296
13297					this._addLayer(m, this._maxZoom);
13298					if (!skipLayerAddEvent) {
13299						this.fire('layeradd', { layer: m });
13300					}
13301
13302					//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
13303					if (m.__parent) {
13304						if (m.__parent.getChildCount() === 2) {
13305							var markers = m.__parent.getAllChildMarkers(),
13306							    otherMarker = markers[0] === m ? markers[1] : markers[0];
13307							fg.removeLayer(otherMarker);
13308						}
13309					}
13310				}
13311
13312				if (chunkProgress) {
13313					// report progress and time elapsed:
13314					chunkProgress(offset, l, (new Date()).getTime() - started);
13315				}
13316
13317				// Completed processing all markers.
13318				if (offset === l) {
13319
13320					// Refresh bounds and weighted positions.
13321					this._topClusterLevel._recalculateBounds();
13322
13323					this._refreshClustersIcons();
13324
13325					this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
13326				} else {
13327					setTimeout(process, this.options.chunkDelay);
13328				}
13329			}, this);
13330
13331			process();
13332		} else {
13333			var needsClustering = this._needsClustering;
13334
13335			for (; offset < l; offset++) {
13336				m = layersArray[offset];
13337
13338				// Group of layers, append children to layersArray and skip.
13339				if (m instanceof L.LayerGroup) {
13340					if (originalArray) {
13341						layersArray = layersArray.slice();
13342						originalArray = false;
13343					}
13344					this._extractNonGroupLayers(m, layersArray);
13345					l = layersArray.length;
13346					continue;
13347				}
13348
13349				//Not point data, can't be clustered
13350				if (!m.getLatLng) {
13351					npg.addLayer(m);
13352					continue;
13353				}
13354
13355				if (this.hasLayer(m)) {
13356					continue;
13357				}
13358
13359				needsClustering.push(m);
13360			}
13361		}
13362		return this;
13363	},
13364
13365	//Takes an array of markers and removes them in bulk
13366	removeLayers: function (layersArray) {
13367		var i, m,
13368		    l = layersArray.length,
13369		    fg = this._featureGroup,
13370		    npg = this._nonPointGroup,
13371		    originalArray = true;
13372
13373		if (!this._map) {
13374			for (i = 0; i < l; i++) {
13375				m = layersArray[i];
13376
13377				// Group of layers, append children to layersArray and skip.
13378				if (m instanceof L.LayerGroup) {
13379					if (originalArray) {
13380						layersArray = layersArray.slice();
13381						originalArray = false;
13382					}
13383					this._extractNonGroupLayers(m, layersArray);
13384					l = layersArray.length;
13385					continue;
13386				}
13387
13388				this._arraySplice(this._needsClustering, m);
13389				npg.removeLayer(m);
13390				if (this.hasLayer(m)) {
13391					this._needsRemoving.push({ layer: m, latlng: m._latlng });
13392				}
13393				this.fire('layerremove', { layer: m });
13394			}
13395			return this;
13396		}
13397
13398		if (this._unspiderfy) {
13399			this._unspiderfy();
13400
13401			// Work on a copy of the array, so that next loop is not affected.
13402			var layersArray2 = layersArray.slice(),
13403			    l2 = l;
13404			for (i = 0; i < l2; i++) {
13405				m = layersArray2[i];
13406
13407				// Group of layers, append children to layersArray and skip.
13408				if (m instanceof L.LayerGroup) {
13409					this._extractNonGroupLayers(m, layersArray2);
13410					l2 = layersArray2.length;
13411					continue;
13412				}
13413
13414				this._unspiderfyLayer(m);
13415			}
13416		}
13417
13418		for (i = 0; i < l; i++) {
13419			m = layersArray[i];
13420
13421			// Group of layers, append children to layersArray and skip.
13422			if (m instanceof L.LayerGroup) {
13423				if (originalArray) {
13424					layersArray = layersArray.slice();
13425					originalArray = false;
13426				}
13427				this._extractNonGroupLayers(m, layersArray);
13428				l = layersArray.length;
13429				continue;
13430			}
13431
13432			if (!m.__parent) {
13433				npg.removeLayer(m);
13434				this.fire('layerremove', { layer: m });
13435				continue;
13436			}
13437
13438			this._removeLayer(m, true, true);
13439			this.fire('layerremove', { layer: m });
13440
13441			if (fg.hasLayer(m)) {
13442				fg.removeLayer(m);
13443				if (m.clusterShow) {
13444					m.clusterShow();
13445				}
13446			}
13447		}
13448
13449		// Refresh bounds and weighted positions.
13450		this._topClusterLevel._recalculateBounds();
13451
13452		this._refreshClustersIcons();
13453
13454		//Fix up the clusters and markers on the map
13455		this._topClusterLevel._recursivelyAddChildrenToMap(null, this._zoom, this._currentShownBounds);
13456
13457		return this;
13458	},
13459
13460	//Removes all layers from the MarkerClusterGroup
13461	clearLayers: function () {
13462		//Need our own special implementation as the LayerGroup one doesn't work for us
13463
13464		//If we aren't on the map (yet), blow away the markers we know of
13465		if (!this._map) {
13466			this._needsClustering = [];
13467			delete this._gridClusters;
13468			delete this._gridUnclustered;
13469		}
13470
13471		if (this._noanimationUnspiderfy) {
13472			this._noanimationUnspiderfy();
13473		}
13474
13475		//Remove all the visible layers
13476		this._featureGroup.clearLayers();
13477		this._nonPointGroup.clearLayers();
13478
13479		this.eachLayer(function (marker) {
13480			marker.off(this._childMarkerEventHandlers, this);
13481			delete marker.__parent;
13482		}, this);
13483
13484		if (this._map) {
13485			//Reset _topClusterLevel and the DistanceGrids
13486			this._generateInitialClusters();
13487		}
13488
13489		return this;
13490	},
13491
13492	//Override FeatureGroup.getBounds as it doesn't work
13493	getBounds: function () {
13494		var bounds = new L.LatLngBounds();
13495
13496		if (this._topClusterLevel) {
13497			bounds.extend(this._topClusterLevel._bounds);
13498		}
13499
13500		for (var i = this._needsClustering.length - 1; i >= 0; i--) {
13501			bounds.extend(this._needsClustering[i].getLatLng());
13502		}
13503
13504		bounds.extend(this._nonPointGroup.getBounds());
13505
13506		return bounds;
13507	},
13508
13509	//Overrides LayerGroup.eachLayer
13510	eachLayer: function (method, context) {
13511		var markers = this._needsClustering.slice(),
13512			needsRemoving = this._needsRemoving,
13513			thisNeedsRemoving, i, j;
13514
13515		if (this._topClusterLevel) {
13516			this._topClusterLevel.getAllChildMarkers(markers);
13517		}
13518
13519		for (i = markers.length - 1; i >= 0; i--) {
13520			thisNeedsRemoving = true;
13521
13522			for (j = needsRemoving.length - 1; j >= 0; j--) {
13523				if (needsRemoving[j].layer === markers[i]) {
13524					thisNeedsRemoving = false;
13525					break;
13526				}
13527			}
13528
13529			if (thisNeedsRemoving) {
13530				method.call(context, markers[i]);
13531			}
13532		}
13533
13534		this._nonPointGroup.eachLayer(method, context);
13535	},
13536
13537	//Overrides LayerGroup.getLayers
13538	getLayers: function () {
13539		var layers = [];
13540		this.eachLayer(function (l) {
13541			layers.push(l);
13542		});
13543		return layers;
13544	},
13545
13546	//Overrides LayerGroup.getLayer, WARNING: Really bad performance
13547	getLayer: function (id) {
13548		var result = null;
13549		
13550		id = parseInt(id, 10);
13551
13552		this.eachLayer(function (l) {
13553			if (L.stamp(l) === id) {
13554				result = l;
13555			}
13556		});
13557
13558		return result;
13559	},
13560
13561	//Returns true if the given layer is in this MarkerClusterGroup
13562	hasLayer: function (layer) {
13563		if (!layer) {
13564			return false;
13565		}
13566
13567		var i, anArray = this._needsClustering;
13568
13569		for (i = anArray.length - 1; i >= 0; i--) {
13570			if (anArray[i] === layer) {
13571				return true;
13572			}
13573		}
13574
13575		anArray = this._needsRemoving;
13576		for (i = anArray.length - 1; i >= 0; i--) {
13577			if (anArray[i].layer === layer) {
13578				return false;
13579			}
13580		}
13581
13582		return !!(layer.__parent && layer.__parent._group === this) || this._nonPointGroup.hasLayer(layer);
13583	},
13584
13585	//Zoom down to show the given layer (spiderfying if necessary) then calls the callback
13586	zoomToShowLayer: function (layer, callback) {
13587
13588		if (typeof callback !== 'function') {
13589			callback = function () {};
13590		}
13591
13592		var showMarker = function () {
13593			if ((layer._icon || layer.__parent._icon) && !this._inZoomAnimation) {
13594				this._map.off('moveend', showMarker, this);
13595				this.off('animationend', showMarker, this);
13596
13597				if (layer._icon) {
13598					callback();
13599				} else if (layer.__parent._icon) {
13600					this.once('spiderfied', callback, this);
13601					layer.__parent.spiderfy();
13602				}
13603			}
13604		};
13605
13606		if (layer._icon && this._map.getBounds().contains(layer.getLatLng())) {
13607			//Layer is visible ond on screen, immediate return
13608			callback();
13609		} else if (layer.__parent._zoom < Math.round(this._map._zoom)) {
13610			//Layer should be visible at this zoom level. It must not be on screen so just pan over to it
13611			this._map.on('moveend', showMarker, this);
13612			this._map.panTo(layer.getLatLng());
13613		} else {
13614			this._map.on('moveend', showMarker, this);
13615			this.on('animationend', showMarker, this);
13616			layer.__parent.zoomToBounds();
13617		}
13618	},
13619
13620	//Overrides FeatureGroup.onAdd
13621	onAdd: function (map) {
13622		this._map = map;
13623		var i, l, layer;
13624
13625		if (!isFinite(this._map.getMaxZoom())) {
13626			throw "Map has no maxZoom specified";
13627		}
13628
13629		this._featureGroup.addTo(map);
13630		this._nonPointGroup.addTo(map);
13631
13632		if (!this._gridClusters) {
13633			this._generateInitialClusters();
13634		}
13635
13636		this._maxLat = map.options.crs.projection.MAX_LATITUDE;
13637
13638		//Restore all the positions as they are in the MCG before removing them
13639		for (i = 0, l = this._needsRemoving.length; i < l; i++) {
13640			layer = this._needsRemoving[i];
13641			layer.newlatlng = layer.layer._latlng;
13642			layer.layer._latlng = layer.latlng;
13643		}
13644		//Remove them, then restore their new positions
13645		for (i = 0, l = this._needsRemoving.length; i < l; i++) {
13646			layer = this._needsRemoving[i];
13647			this._removeLayer(layer.layer, true);
13648			layer.layer._latlng = layer.newlatlng;
13649		}
13650		this._needsRemoving = [];
13651
13652		//Remember the current zoom level and bounds
13653		this._zoom = Math.round(this._map._zoom);
13654		this._currentShownBounds = this._getExpandedVisibleBounds();
13655
13656		this._map.on('zoomend', this._zoomEnd, this);
13657		this._map.on('moveend', this._moveEnd, this);
13658
13659		if (this._spiderfierOnAdd) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
13660			this._spiderfierOnAdd();
13661		}
13662
13663		this._bindEvents();
13664
13665		//Actually add our markers to the map:
13666		l = this._needsClustering;
13667		this._needsClustering = [];
13668		this.addLayers(l, true);
13669	},
13670
13671	//Overrides FeatureGroup.onRemove
13672	onRemove: function (map) {
13673		map.off('zoomend', this._zoomEnd, this);
13674		map.off('moveend', this._moveEnd, this);
13675
13676		this._unbindEvents();
13677
13678		//In case we are in a cluster animation
13679		this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
13680
13681		if (this._spiderfierOnRemove) { //TODO FIXME: Not sure how to have spiderfier add something on here nicely
13682			this._spiderfierOnRemove();
13683		}
13684
13685		delete this._maxLat;
13686
13687		//Clean up all the layers we added to the map
13688		this._hideCoverage();
13689		this._featureGroup.remove();
13690		this._nonPointGroup.remove();
13691
13692		this._featureGroup.clearLayers();
13693
13694		this._map = null;
13695	},
13696
13697	getVisibleParent: function (marker) {
13698		var vMarker = marker;
13699		while (vMarker && !vMarker._icon) {
13700			vMarker = vMarker.__parent;
13701		}
13702		return vMarker || null;
13703	},
13704
13705	//Remove the given object from the given array
13706	_arraySplice: function (anArray, obj) {
13707		for (var i = anArray.length - 1; i >= 0; i--) {
13708			if (anArray[i] === obj) {
13709				anArray.splice(i, 1);
13710				return true;
13711			}
13712		}
13713	},
13714
13715	/**
13716	 * Removes a marker from all _gridUnclustered zoom levels, starting at the supplied zoom.
13717	 * @param marker to be removed from _gridUnclustered.
13718	 * @param z integer bottom start zoom level (included)
13719	 * @private
13720	 */
13721	_removeFromGridUnclustered: function (marker, z) {
13722		var map = this._map,
13723		    gridUnclustered = this._gridUnclustered,
13724			minZoom = this._map.getMinZoom();
13725
13726		for (; z >= minZoom; z--) {
13727			if (!gridUnclustered[z].removeObject(marker, map.project(marker.getLatLng(), z))) {
13728				break;
13729			}
13730		}
13731	},
13732
13733	_childMarkerDragStart: function (e) {
13734		e.target.__dragStart = e.target._latlng;
13735	},
13736
13737	_childMarkerMoved: function (e) {
13738		if (!this._ignoreMove && !e.target.__dragStart) {
13739			var isPopupOpen = e.target._popup && e.target._popup.isOpen();
13740
13741			this._moveChild(e.target, e.oldLatLng, e.latlng);
13742
13743			if (isPopupOpen) {
13744				e.target.openPopup();
13745			}
13746		}
13747	},
13748
13749	_moveChild: function (layer, from, to) {
13750		layer._latlng = from;
13751		this.removeLayer(layer);
13752
13753		layer._latlng = to;
13754		this.addLayer(layer);
13755	},
13756
13757	_childMarkerDragEnd: function (e) {
13758		if (e.target.__dragStart) {
13759			this._moveChild(e.target, e.target.__dragStart, e.target._latlng);
13760		}
13761		delete e.target.__dragStart;
13762	},
13763	
13764
13765	//Internal function for removing a marker from everything.
13766	//dontUpdateMap: set to true if you will handle updating the map manually (for bulk functions)
13767	_removeLayer: function (marker, removeFromDistanceGrid, dontUpdateMap) {
13768		var gridClusters = this._gridClusters,
13769			gridUnclustered = this._gridUnclustered,
13770			fg = this._featureGroup,
13771			map = this._map,
13772			minZoom = this._map.getMinZoom();
13773
13774		//Remove the marker from distance clusters it might be in
13775		if (removeFromDistanceGrid) {
13776			this._removeFromGridUnclustered(marker, this._maxZoom);
13777		}
13778
13779		//Work our way up the clusters removing them as we go if required
13780		var cluster = marker.__parent,
13781			markers = cluster._markers,
13782			otherMarker;
13783
13784		//Remove the marker from the immediate parents marker list
13785		this._arraySplice(markers, marker);
13786
13787		while (cluster) {
13788			cluster._childCount--;
13789			cluster._boundsNeedUpdate = true;
13790
13791			if (cluster._zoom < minZoom) {
13792				//Top level, do nothing
13793				break;
13794			} else if (removeFromDistanceGrid && cluster._childCount <= 1) { //Cluster no longer required
13795				//We need to push the other marker up to the parent
13796				otherMarker = cluster._markers[0] === marker ? cluster._markers[1] : cluster._markers[0];
13797
13798				//Update distance grid
13799				gridClusters[cluster._zoom].removeObject(cluster, map.project(cluster._cLatLng, cluster._zoom));
13800				gridUnclustered[cluster._zoom].addObject(otherMarker, map.project(otherMarker.getLatLng(), cluster._zoom));
13801
13802				//Move otherMarker up to parent
13803				this._arraySplice(cluster.__parent._childClusters, cluster);
13804				cluster.__parent._markers.push(otherMarker);
13805				otherMarker.__parent = cluster.__parent;
13806
13807				if (cluster._icon) {
13808					//Cluster is currently on the map, need to put the marker on the map instead
13809					fg.removeLayer(cluster);
13810					if (!dontUpdateMap) {
13811						fg.addLayer(otherMarker);
13812					}
13813				}
13814			} else {
13815				cluster._iconNeedsUpdate = true;
13816			}
13817
13818			cluster = cluster.__parent;
13819		}
13820
13821		delete marker.__parent;
13822	},
13823
13824	_isOrIsParent: function (el, oel) {
13825		while (oel) {
13826			if (el === oel) {
13827				return true;
13828			}
13829			oel = oel.parentNode;
13830		}
13831		return false;
13832	},
13833
13834	//Override L.Evented.fire
13835	fire: function (type, data, propagate) {
13836		if (data && data.layer instanceof L.MarkerCluster) {
13837			//Prevent multiple clustermouseover/off events if the icon is made up of stacked divs (Doesn't work in ie <= 8, no relatedTarget)
13838			if (data.originalEvent && this._isOrIsParent(data.layer._icon, data.originalEvent.relatedTarget)) {
13839				return;
13840			}
13841			type = 'cluster' + type;
13842		}
13843
13844		L.FeatureGroup.prototype.fire.call(this, type, data, propagate);
13845	},
13846
13847	//Override L.Evented.listens
13848	listens: function (type, propagate) {
13849		return L.FeatureGroup.prototype.listens.call(this, type, propagate) || L.FeatureGroup.prototype.listens.call(this, 'cluster' + type, propagate);
13850	},
13851
13852	//Default functionality
13853	_defaultIconCreateFunction: function (cluster) {
13854		var childCount = cluster.getChildCount();
13855
13856		var c = ' marker-cluster-';
13857		if (childCount < 10) {
13858			c += 'small';
13859		} else if (childCount < 100) {
13860			c += 'medium';
13861		} else {
13862			c += 'large';
13863		}
13864
13865		return new L.DivIcon({ html: '<div><span>' + childCount + '</span></div>', className: 'marker-cluster' + c, iconSize: new L.Point(40, 40) });
13866	},
13867
13868	_bindEvents: function () {
13869		var map = this._map,
13870		    spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
13871		    showCoverageOnHover = this.options.showCoverageOnHover,
13872		    zoomToBoundsOnClick = this.options.zoomToBoundsOnClick;
13873
13874		//Zoom on cluster click or spiderfy if we are at the lowest level
13875		if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
13876			this.on('clusterclick', this._zoomOrSpiderfy, this);
13877		}
13878
13879		//Show convex hull (boundary) polygon on mouse over
13880		if (showCoverageOnHover) {
13881			this.on('clustermouseover', this._showCoverage, this);
13882			this.on('clustermouseout', this._hideCoverage, this);
13883			map.on('zoomend', this._hideCoverage, this);
13884		}
13885	},
13886
13887	_zoomOrSpiderfy: function (e) {
13888		var cluster = e.layer,
13889		    bottomCluster = cluster;
13890
13891		while (bottomCluster._childClusters.length === 1) {
13892			bottomCluster = bottomCluster._childClusters[0];
13893		}
13894
13895		if (bottomCluster._zoom === this._maxZoom &&
13896			bottomCluster._childCount === cluster._childCount &&
13897			this.options.spiderfyOnMaxZoom) {
13898
13899			// All child markers are contained in a single cluster from this._maxZoom to this cluster.
13900			cluster.spiderfy();
13901		} else if (this.options.zoomToBoundsOnClick) {
13902			cluster.zoomToBounds();
13903		}
13904
13905		// Focus the map again for keyboard users.
13906		if (e.originalEvent && e.originalEvent.keyCode === 13) {
13907			this._map._container.focus();
13908		}
13909	},
13910
13911	_showCoverage: function (e) {
13912		var map = this._map;
13913		if (this._inZoomAnimation) {
13914			return;
13915		}
13916		if (this._shownPolygon) {
13917			map.removeLayer(this._shownPolygon);
13918		}
13919		if (e.layer.getChildCount() > 2 && e.layer !== this._spiderfied) {
13920			this._shownPolygon = new L.Polygon(e.layer.getConvexHull(), this.options.polygonOptions);
13921			map.addLayer(this._shownPolygon);
13922		}
13923	},
13924
13925	_hideCoverage: function () {
13926		if (this._shownPolygon) {
13927			this._map.removeLayer(this._shownPolygon);
13928			this._shownPolygon = null;
13929		}
13930	},
13931
13932	_unbindEvents: function () {
13933		var spiderfyOnMaxZoom = this.options.spiderfyOnMaxZoom,
13934			showCoverageOnHover = this.options.showCoverageOnHover,
13935			zoomToBoundsOnClick = this.options.zoomToBoundsOnClick,
13936			map = this._map;
13937
13938		if (spiderfyOnMaxZoom || zoomToBoundsOnClick) {
13939			this.off('clusterclick', this._zoomOrSpiderfy, this);
13940		}
13941		if (showCoverageOnHover) {
13942			this.off('clustermouseover', this._showCoverage, this);
13943			this.off('clustermouseout', this._hideCoverage, this);
13944			map.off('zoomend', this._hideCoverage, this);
13945		}
13946	},
13947
13948	_zoomEnd: function () {
13949		if (!this._map) { //May have been removed from the map by a zoomEnd handler
13950			return;
13951		}
13952		this._mergeSplitClusters();
13953
13954		this._zoom = Math.round(this._map._zoom);
13955		this._currentShownBounds = this._getExpandedVisibleBounds();
13956	},
13957
13958	_moveEnd: function () {
13959		if (this._inZoomAnimation) {
13960			return;
13961		}
13962
13963		var newBounds = this._getExpandedVisibleBounds();
13964
13965		this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), this._zoom, newBounds);
13966		this._topClusterLevel._recursivelyAddChildrenToMap(null, Math.round(this._map._zoom), newBounds);
13967
13968		this._currentShownBounds = newBounds;
13969		return;
13970	},
13971
13972	_generateInitialClusters: function () {
13973		var maxZoom = this._map.getMaxZoom(),
13974			minZoom = this._map.getMinZoom(),
13975			radius = this.options.maxClusterRadius,
13976			radiusFn = radius;
13977	
13978		//If we just set maxClusterRadius to a single number, we need to create
13979		//a simple function to return that number. Otherwise, we just have to
13980		//use the function we've passed in.
13981		if (typeof radius !== "function") {
13982			radiusFn = function () { return radius; };
13983		}
13984
13985		if (this.options.disableClusteringAtZoom !== null) {
13986			maxZoom = this.options.disableClusteringAtZoom - 1;
13987		}
13988		this._maxZoom = maxZoom;
13989		this._gridClusters = {};
13990		this._gridUnclustered = {};
13991	
13992		//Set up DistanceGrids for each zoom
13993		for (var zoom = maxZoom; zoom >= minZoom; zoom--) {
13994			this._gridClusters[zoom] = new L.DistanceGrid(radiusFn(zoom));
13995			this._gridUnclustered[zoom] = new L.DistanceGrid(radiusFn(zoom));
13996		}
13997
13998		// Instantiate the appropriate L.MarkerCluster class (animated or not).
13999		this._topClusterLevel = new this._markerCluster(this, minZoom - 1);
14000	},
14001
14002	//Zoom: Zoom to start adding at (Pass this._maxZoom to start at the bottom)
14003	_addLayer: function (layer, zoom) {
14004		var gridClusters = this._gridClusters,
14005		    gridUnclustered = this._gridUnclustered,
14006			minZoom = this._map.getMinZoom(),
14007		    markerPoint, z;
14008
14009		if (this.options.singleMarkerMode) {
14010			this._overrideMarkerIcon(layer);
14011		}
14012
14013		layer.on(this._childMarkerEventHandlers, this);
14014
14015		//Find the lowest zoom level to slot this one in
14016		for (; zoom >= minZoom; zoom--) {
14017			markerPoint = this._map.project(layer.getLatLng(), zoom); // calculate pixel position
14018
14019			//Try find a cluster close by
14020			var closest = gridClusters[zoom].getNearObject(markerPoint);
14021			if (closest) {
14022				closest._addChild(layer);
14023				layer.__parent = closest;
14024				return;
14025			}
14026
14027			//Try find a marker close by to form a new cluster with
14028			closest = gridUnclustered[zoom].getNearObject(markerPoint);
14029			if (closest) {
14030				var parent = closest.__parent;
14031				if (parent) {
14032					this._removeLayer(closest, false);
14033				}
14034
14035				//Create new cluster with these 2 in it
14036
14037				var newCluster = new this._markerCluster(this, zoom, closest, layer);
14038				gridClusters[zoom].addObject(newCluster, this._map.project(newCluster._cLatLng, zoom));
14039				closest.__parent = newCluster;
14040				layer.__parent = newCluster;
14041
14042				//First create any new intermediate parent clusters that don't exist
14043				var lastParent = newCluster;
14044				for (z = zoom - 1; z > parent._zoom; z--) {
14045					lastParent = new this._markerCluster(this, z, lastParent);
14046					gridClusters[z].addObject(lastParent, this._map.project(closest.getLatLng(), z));
14047				}
14048				parent._addChild(lastParent);
14049
14050				//Remove closest from this zoom level and any above that it is in, replace with newCluster
14051				this._removeFromGridUnclustered(closest, zoom);
14052
14053				return;
14054			}
14055
14056			//Didn't manage to cluster in at this zoom, record us as a marker here and continue upwards
14057			gridUnclustered[zoom].addObject(layer, markerPoint);
14058		}
14059
14060		//Didn't get in anything, add us to the top
14061		this._topClusterLevel._addChild(layer);
14062		layer.__parent = this._topClusterLevel;
14063		return;
14064	},
14065
14066	/**
14067	 * Refreshes the icon of all "dirty" visible clusters.
14068	 * Non-visible "dirty" clusters will be updated when they are added to the map.
14069	 * @private
14070	 */
14071	_refreshClustersIcons: function () {
14072		this._featureGroup.eachLayer(function (c) {
14073			if (c instanceof L.MarkerCluster && c._iconNeedsUpdate) {
14074				c._updateIcon();
14075			}
14076		});
14077	},
14078
14079	//Enqueue code to fire after the marker expand/contract has happened
14080	_enqueue: function (fn) {
14081		this._queue.push(fn);
14082		if (!this._queueTimeout) {
14083			this._queueTimeout = setTimeout(L.bind(this._processQueue, this), 300);
14084		}
14085	},
14086	_processQueue: function () {
14087		for (var i = 0; i < this._queue.length; i++) {
14088			this._queue[i].call(this);
14089		}
14090		this._queue.length = 0;
14091		clearTimeout(this._queueTimeout);
14092		this._queueTimeout = null;
14093	},
14094
14095	//Merge and split any existing clusters that are too big or small
14096	_mergeSplitClusters: function () {
14097		var mapZoom = Math.round(this._map._zoom);
14098
14099		//In case we are starting to split before the animation finished
14100		this._processQueue();
14101
14102		if (this._zoom < mapZoom && this._currentShownBounds.intersects(this._getExpandedVisibleBounds())) { //Zoom in, split
14103			this._animationStart();
14104			//Remove clusters now off screen
14105			this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), this._zoom, this._getExpandedVisibleBounds());
14106
14107			this._animationZoomIn(this._zoom, mapZoom);
14108
14109		} else if (this._zoom > mapZoom) { //Zoom out, merge
14110			this._animationStart();
14111
14112			this._animationZoomOut(this._zoom, mapZoom);
14113		} else {
14114			this._moveEnd();
14115		}
14116	},
14117
14118	//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)
14119	_getExpandedVisibleBounds: function () {
14120		if (!this.options.removeOutsideVisibleBounds) {
14121			return this._mapBoundsInfinite;
14122		} else if (L.Browser.mobile) {
14123			return this._checkBoundsMaxLat(this._map.getBounds());
14124		}
14125
14126		return this._checkBoundsMaxLat(this._map.getBounds().pad(1)); // Padding expands the bounds by its own dimensions but scaled with the given factor.
14127	},
14128
14129	/**
14130	 * Expands the latitude to Infinity (or -Infinity) if the input bounds reach the map projection maximum defined latitude
14131	 * (in the case of Web/Spherical Mercator, it is 85.0511287798 / see https://en.wikipedia.org/wiki/Web_Mercator#Formulas).
14132	 * Otherwise, the removeOutsideVisibleBounds option will remove markers beyond that limit, whereas the same markers without
14133	 * this option (or outside MCG) will have their position floored (ceiled) by the projection and rendered at that limit,
14134	 * making the user think that MCG "eats" them and never displays them again.
14135	 * @param bounds L.LatLngBounds
14136	 * @returns {L.LatLngBounds}
14137	 * @private
14138	 */
14139	_checkBoundsMaxLat: function (bounds) {
14140		var maxLat = this._maxLat;
14141
14142		if (maxLat !== undefined) {
14143			if (bounds.getNorth() >= maxLat) {
14144				bounds._northEast.lat = Infinity;
14145			}
14146			if (bounds.getSouth() <= -maxLat) {
14147				bounds._southWest.lat = -Infinity;
14148			}
14149		}
14150
14151		return bounds;
14152	},
14153
14154	//Shared animation code
14155	_animationAddLayerNonAnimated: function (layer, newCluster) {
14156		if (newCluster === layer) {
14157			this._featureGroup.addLayer(layer);
14158		} else if (newCluster._childCount === 2) {
14159			newCluster._addToMap();
14160
14161			var markers = newCluster.getAllChildMarkers();
14162			this._featureGroup.removeLayer(markers[0]);
14163			this._featureGroup.removeLayer(markers[1]);
14164		} else {
14165			newCluster._updateIcon();
14166		}
14167	},
14168
14169	/**
14170	 * Extracts individual (i.e. non-group) layers from a Layer Group.
14171	 * @param group to extract layers from.
14172	 * @param output {Array} in which to store the extracted layers.
14173	 * @returns {*|Array}
14174	 * @private
14175	 */
14176	_extractNonGroupLayers: function (group, output) {
14177		var layers = group.getLayers(),
14178		    i = 0,
14179		    layer;
14180
14181		output = output || [];
14182
14183		for (; i < layers.length; i++) {
14184			layer = layers[i];
14185
14186			if (layer instanceof L.LayerGroup) {
14187				this._extractNonGroupLayers(layer, output);
14188				continue;
14189			}
14190
14191			output.push(layer);
14192		}
14193
14194		return output;
14195	},
14196
14197	/**
14198	 * Implements the singleMarkerMode option.
14199	 * @param layer Marker to re-style using the Clusters iconCreateFunction.
14200	 * @returns {L.Icon} The newly created icon.
14201	 * @private
14202	 */
14203	_overrideMarkerIcon: function (layer) {
14204		var icon = layer.options.icon = this.options.iconCreateFunction({
14205			getChildCount: function () {
14206				return 1;
14207			},
14208			getAllChildMarkers: function () {
14209				return [layer];
14210			}
14211		});
14212
14213		return icon;
14214	}
14215});
14216
14217// Constant bounds used in case option "removeOutsideVisibleBounds" is set to false.
14218L.MarkerClusterGroup.include({
14219	_mapBoundsInfinite: new L.LatLngBounds(new L.LatLng(-Infinity, -Infinity), new L.LatLng(Infinity, Infinity))
14220});
14221
14222L.MarkerClusterGroup.include({
14223	_noAnimation: {
14224		//Non Animated versions of everything
14225		_animationStart: function () {
14226			//Do nothing...
14227		},
14228		_animationZoomIn: function (previousZoomLevel, newZoomLevel) {
14229			this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), previousZoomLevel);
14230			this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
14231
14232			//We didn't actually animate, but we use this event to mean "clustering animations have finished"
14233			this.fire('animationend');
14234		},
14235		_animationZoomOut: function (previousZoomLevel, newZoomLevel) {
14236			this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), previousZoomLevel);
14237			this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
14238
14239			//We didn't actually animate, but we use this event to mean "clustering animations have finished"
14240			this.fire('animationend');
14241		},
14242		_animationAddLayer: function (layer, newCluster) {
14243			this._animationAddLayerNonAnimated(layer, newCluster);
14244		}
14245	},
14246
14247	_withAnimation: {
14248		//Animated versions here
14249		_animationStart: function () {
14250			this._map._mapPane.className += ' leaflet-cluster-anim';
14251			this._inZoomAnimation++;
14252		},
14253
14254		_animationZoomIn: function (previousZoomLevel, newZoomLevel) {
14255			var bounds = this._getExpandedVisibleBounds(),
14256			    fg = this._featureGroup,
14257				minZoom = this._map.getMinZoom(),
14258			    i;
14259
14260			this._ignoreMove = true;
14261
14262			//Add all children of current clusters to map and remove those clusters from map
14263			this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
14264				var startPos = c._latlng,
14265				    markers  = c._markers,
14266				    m;
14267
14268				if (!bounds.contains(startPos)) {
14269					startPos = null;
14270				}
14271
14272				if (c._isSingleParent() && previousZoomLevel + 1 === newZoomLevel) { //Immediately add the new child and remove us
14273					fg.removeLayer(c);
14274					c._recursivelyAddChildrenToMap(null, newZoomLevel, bounds);
14275				} else {
14276					//Fade out old cluster
14277					c.clusterHide();
14278					c._recursivelyAddChildrenToMap(startPos, newZoomLevel, bounds);
14279				}
14280
14281				//Remove all markers that aren't visible any more
14282				//TODO: Do we actually need to do this on the higher levels too?
14283				for (i = markers.length - 1; i >= 0; i--) {
14284					m = markers[i];
14285					if (!bounds.contains(m._latlng)) {
14286						fg.removeLayer(m);
14287					}
14288				}
14289
14290			});
14291
14292			this._forceLayout();
14293
14294			//Update opacities
14295			this._topClusterLevel._recursivelyBecomeVisible(bounds, newZoomLevel);
14296			//TODO Maybe? Update markers in _recursivelyBecomeVisible
14297			fg.eachLayer(function (n) {
14298				if (!(n instanceof L.MarkerCluster) && n._icon) {
14299					n.clusterShow();
14300				}
14301			});
14302
14303			//update the positions of the just added clusters/markers
14304			this._topClusterLevel._recursively(bounds, previousZoomLevel, newZoomLevel, function (c) {
14305				c._recursivelyRestoreChildPositions(newZoomLevel);
14306			});
14307
14308			this._ignoreMove = false;
14309
14310			//Remove the old clusters and close the zoom animation
14311			this._enqueue(function () {
14312				//update the positions of the just added clusters/markers
14313				this._topClusterLevel._recursively(bounds, previousZoomLevel, minZoom, function (c) {
14314					fg.removeLayer(c);
14315					c.clusterShow();
14316				});
14317
14318				this._animationEnd();
14319			});
14320		},
14321
14322		_animationZoomOut: function (previousZoomLevel, newZoomLevel) {
14323			this._animationZoomOutSingle(this._topClusterLevel, previousZoomLevel - 1, newZoomLevel);
14324
14325			//Need to add markers for those that weren't on the map before but are now
14326			this._topClusterLevel._recursivelyAddChildrenToMap(null, newZoomLevel, this._getExpandedVisibleBounds());
14327			//Remove markers that were on the map before but won't be now
14328			this._topClusterLevel._recursivelyRemoveChildrenFromMap(this._currentShownBounds, this._map.getMinZoom(), previousZoomLevel, this._getExpandedVisibleBounds());
14329		},
14330
14331		_animationAddLayer: function (layer, newCluster) {
14332			var me = this,
14333			    fg = this._featureGroup;
14334
14335			fg.addLayer(layer);
14336			if (newCluster !== layer) {
14337				if (newCluster._childCount > 2) { //Was already a cluster
14338
14339					newCluster._updateIcon();
14340					this._forceLayout();
14341					this._animationStart();
14342
14343					layer._setPos(this._map.latLngToLayerPoint(newCluster.getLatLng()));
14344					layer.clusterHide();
14345
14346					this._enqueue(function () {
14347						fg.removeLayer(layer);
14348						layer.clusterShow();
14349
14350						me._animationEnd();
14351					});
14352
14353				} else { //Just became a cluster
14354					this._forceLayout();
14355
14356					me._animationStart();
14357					me._animationZoomOutSingle(newCluster, this._map.getMaxZoom(), this._zoom);
14358				}
14359			}
14360		}
14361	},
14362
14363	// Private methods for animated versions.
14364	_animationZoomOutSingle: function (cluster, previousZoomLevel, newZoomLevel) {
14365		var bounds = this._getExpandedVisibleBounds(),
14366			minZoom = this._map.getMinZoom();
14367
14368		//Animate all of the markers in the clusters to move to their cluster center point
14369		cluster._recursivelyAnimateChildrenInAndAddSelfToMap(bounds, minZoom, previousZoomLevel + 1, newZoomLevel);
14370
14371		var me = this;
14372
14373		//Update the opacity (If we immediately set it they won't animate)
14374		this._forceLayout();
14375		cluster._recursivelyBecomeVisible(bounds, newZoomLevel);
14376
14377		//TODO: Maybe use the transition timing stuff to make this more reliable
14378		//When the animations are done, tidy up
14379		this._enqueue(function () {
14380
14381			//This cluster stopped being a cluster before the timeout fired
14382			if (cluster._childCount === 1) {
14383				var m = cluster._markers[0];
14384				//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
14385				this._ignoreMove = true;
14386				m.setLatLng(m.getLatLng());
14387				this._ignoreMove = false;
14388				if (m.clusterShow) {
14389					m.clusterShow();
14390				}
14391			} else {
14392				cluster._recursively(bounds, newZoomLevel, minZoom, function (c) {
14393					c._recursivelyRemoveChildrenFromMap(bounds, minZoom, previousZoomLevel + 1);
14394				});
14395			}
14396			me._animationEnd();
14397		});
14398	},
14399
14400	_animationEnd: function () {
14401		if (this._map) {
14402			this._map._mapPane.className = this._map._mapPane.className.replace(' leaflet-cluster-anim', '');
14403		}
14404		this._inZoomAnimation--;
14405		this.fire('animationend');
14406	},
14407
14408	//Force a browser layout of stuff in the map
14409	// Should apply the current opacity and location to all elements so we can update them again for an animation
14410	_forceLayout: function () {
14411		//In my testing this works, infact offsetWidth of any element seems to work.
14412		//Could loop all this._layers and do this for each _icon if it stops working
14413
14414		L.Util.falseFn(document.body.offsetWidth);
14415	}
14416});
14417
14418L.markerClusterGroup = function (options) {
14419	return new L.MarkerClusterGroup(options);
14420};
14421
14422
14423L.MarkerCluster = L.Marker.extend({
14424	initialize: function (group, zoom, a, b) {
14425
14426		L.Marker.prototype.initialize.call(this, a ? (a._cLatLng || a.getLatLng()) : new L.LatLng(0, 0), { icon: this });
14427
14428
14429		this._group = group;
14430		this._zoom = zoom;
14431
14432		this._markers = [];
14433		this._childClusters = [];
14434		this._childCount = 0;
14435		this._iconNeedsUpdate = true;
14436		this._boundsNeedUpdate = true;
14437
14438		this._bounds = new L.LatLngBounds();
14439
14440		if (a) {
14441			this._addChild(a);
14442		}
14443		if (b) {
14444			this._addChild(b);
14445		}
14446	},
14447
14448	//Recursively retrieve all child markers of this cluster
14449	getAllChildMarkers: function (storageArray) {
14450		storageArray = storageArray || [];
14451
14452		for (var i = this._childClusters.length - 1; i >= 0; i--) {
14453			this._childClusters[i].getAllChildMarkers(storageArray);
14454		}
14455
14456		for (var j = this._markers.length - 1; j >= 0; j--) {
14457			storageArray.push(this._markers[j]);
14458		}
14459
14460		return storageArray;
14461	},
14462
14463	//Returns the count of how many child markers we have
14464	getChildCount: function () {
14465		return this._childCount;
14466	},
14467
14468	//Zoom to the minimum of showing all of the child markers, or the extents of this cluster
14469	zoomToBounds: function (fitBoundsOptions) {
14470		var childClusters = this._childClusters.slice(),
14471			map = this._group._map,
14472			boundsZoom = map.getBoundsZoom(this._bounds),
14473			zoom = this._zoom + 1,
14474			mapZoom = map.getZoom(),
14475			i;
14476
14477		//calculate how far we need to zoom down to see all of the markers
14478		while (childClusters.length > 0 && boundsZoom > zoom) {
14479			zoom++;
14480			var newClusters = [];
14481			for (i = 0; i < childClusters.length; i++) {
14482				newClusters = newClusters.concat(childClusters[i]._childClusters);
14483			}
14484			childClusters = newClusters;
14485		}
14486
14487		if (boundsZoom > zoom) {
14488			this._group._map.setView(this._latlng, zoom);
14489		} else if (boundsZoom <= mapZoom) { //If fitBounds wouldn't zoom us down, zoom us down instead
14490			this._group._map.setView(this._latlng, mapZoom + 1);
14491		} else {
14492			this._group._map.fitBounds(this._bounds, fitBoundsOptions);
14493		}
14494	},
14495
14496	getBounds: function () {
14497		var bounds = new L.LatLngBounds();
14498		bounds.extend(this._bounds);
14499		return bounds;
14500	},
14501
14502	_updateIcon: function () {
14503		this._iconNeedsUpdate = true;
14504		if (this._icon) {
14505			this.setIcon(this);
14506		}
14507	},
14508
14509	//Cludge for Icon, we pretend to be an icon for performance
14510	createIcon: function () {
14511		if (this._iconNeedsUpdate) {
14512			this._iconObj = this._group.options.iconCreateFunction(this);
14513			this._iconNeedsUpdate = false;
14514		}
14515		return this._iconObj.createIcon();
14516	},
14517	createShadow: function () {
14518		return this._iconObj.createShadow();
14519	},
14520
14521
14522	_addChild: function (new1, isNotificationFromChild) {
14523
14524		this._iconNeedsUpdate = true;
14525
14526		this._boundsNeedUpdate = true;
14527		this._setClusterCenter(new1);
14528
14529		if (new1 instanceof L.MarkerCluster) {
14530			if (!isNotificationFromChild) {
14531				this._childClusters.push(new1);
14532				new1.__parent = this;
14533			}
14534			this._childCount += new1._childCount;
14535		} else {
14536			if (!isNotificationFromChild) {
14537				this._markers.push(new1);
14538			}
14539			this._childCount++;
14540		}
14541
14542		if (this.__parent) {
14543			this.__parent._addChild(new1, true);
14544		}
14545	},
14546
14547	/**
14548	 * Makes sure the cluster center is set. If not, uses the child center if it is a cluster, or the marker position.
14549	 * @param child L.MarkerCluster|L.Marker that will be used as cluster center if not defined yet.
14550	 * @private
14551	 */
14552	_setClusterCenter: function (child) {
14553		if (!this._cLatLng) {
14554			// when clustering, take position of the first point as the cluster center
14555			this._cLatLng = child._cLatLng || child._latlng;
14556		}
14557	},
14558
14559	/**
14560	 * Assigns impossible bounding values so that the next extend entirely determines the new bounds.
14561	 * This method avoids having to trash the previous L.LatLngBounds object and to create a new one, which is much slower for this class.
14562	 * As long as the bounds are not extended, most other methods would probably fail, as they would with bounds initialized but not extended.
14563	 * @private
14564	 */
14565	_resetBounds: function () {
14566		var bounds = this._bounds;
14567
14568		if (bounds._southWest) {
14569			bounds._southWest.lat = Infinity;
14570			bounds._southWest.lng = Infinity;
14571		}
14572		if (bounds._northEast) {
14573			bounds._northEast.lat = -Infinity;
14574			bounds._northEast.lng = -Infinity;
14575		}
14576	},
14577
14578	_recalculateBounds: function () {
14579		var markers = this._markers,
14580		    childClusters = this._childClusters,
14581		    latSum = 0,
14582		    lngSum = 0,
14583		    totalCount = this._childCount,
14584		    i, child, childLatLng, childCount;
14585
14586		// Case where all markers are removed from the map and we are left with just an empty _topClusterLevel.
14587		if (totalCount === 0) {
14588			return;
14589		}
14590
14591		// Reset rather than creating a new object, for performance.
14592		this._resetBounds();
14593
14594		// Child markers.
14595		for (i = 0; i < markers.length; i++) {
14596			childLatLng = markers[i]._latlng;
14597
14598			this._bounds.extend(childLatLng);
14599
14600			latSum += childLatLng.lat;
14601			lngSum += childLatLng.lng;
14602		}
14603
14604		// Child clusters.
14605		for (i = 0; i < childClusters.length; i++) {
14606			child = childClusters[i];
14607
14608			// Re-compute child bounds and weighted position first if necessary.
14609			if (child._boundsNeedUpdate) {
14610				child._recalculateBounds();
14611			}
14612
14613			this._bounds.extend(child._bounds);
14614
14615			childLatLng = child._wLatLng;
14616			childCount = child._childCount;
14617
14618			latSum += childLatLng.lat * childCount;
14619			lngSum += childLatLng.lng * childCount;
14620		}
14621
14622		this._latlng = this._wLatLng = new L.LatLng(latSum / totalCount, lngSum / totalCount);
14623
14624		// Reset dirty flag.
14625		this._boundsNeedUpdate = false;
14626	},
14627
14628	//Set our markers position as given and add it to the map
14629	_addToMap: function (startPos) {
14630		if (startPos) {
14631			this._backupLatlng = this._latlng;
14632			this.setLatLng(startPos);
14633		}
14634		this._group._featureGroup.addLayer(this);
14635	},
14636
14637	_recursivelyAnimateChildrenIn: function (bounds, center, maxZoom) {
14638		this._recursively(bounds, this._group._map.getMinZoom(), maxZoom - 1,
14639			function (c) {
14640				var markers = c._markers,
14641					i, m;
14642				for (i = markers.length - 1; i >= 0; i--) {
14643					m = markers[i];
14644
14645					//Only do it if the icon is still on the map
14646					if (m._icon) {
14647						m._setPos(center);
14648						m.clusterHide();
14649					}
14650				}
14651			},
14652			function (c) {
14653				var childClusters = c._childClusters,
14654					j, cm;
14655				for (j = childClusters.length - 1; j >= 0; j--) {
14656					cm = childClusters[j];
14657					if (cm._icon) {
14658						cm._setPos(center);
14659						cm.clusterHide();
14660					}
14661				}
14662			}
14663		);
14664	},
14665
14666	_recursivelyAnimateChildrenInAndAddSelfToMap: function (bounds, mapMinZoom, previousZoomLevel, newZoomLevel) {
14667		this._recursively(bounds, newZoomLevel, mapMinZoom,
14668			function (c) {
14669				c._recursivelyAnimateChildrenIn(bounds, c._group._map.latLngToLayerPoint(c.getLatLng()).round(), previousZoomLevel);
14670
14671				//TODO: depthToAnimateIn affects _isSingleParent, if there is a multizoom we may/may not be.
14672				//As a hack we only do a animation free zoom on a single level zoom, if someone does multiple levels then we always animate
14673				if (c._isSingleParent() && previousZoomLevel - 1 === newZoomLevel) {
14674					c.clusterShow();
14675					c._recursivelyRemoveChildrenFromMap(bounds, mapMinZoom, previousZoomLevel); //Immediately remove our children as we are replacing them. TODO previousBounds not bounds
14676				} else {
14677					c.clusterHide();
14678				}
14679
14680				c._addToMap();
14681			}
14682		);
14683	},
14684
14685	_recursivelyBecomeVisible: function (bounds, zoomLevel) {
14686		this._recursively(bounds, this._group._map.getMinZoom(), zoomLevel, null, function (c) {
14687			c.clusterShow();
14688		});
14689	},
14690
14691	_recursivelyAddChildrenToMap: function (startPos, zoomLevel, bounds) {
14692		this._recursively(bounds, this._group._map.getMinZoom() - 1, zoomLevel,
14693			function (c) {
14694				if (zoomLevel === c._zoom) {
14695					return;
14696				}
14697
14698				//Add our child markers at startPos (so they can be animated out)
14699				for (var i = c._markers.length - 1; i >= 0; i--) {
14700					var nm = c._markers[i];
14701
14702					if (!bounds.contains(nm._latlng)) {
14703						continue;
14704					}
14705
14706					if (startPos) {
14707						nm._backupLatlng = nm.getLatLng();
14708
14709						nm.setLatLng(startPos);
14710						if (nm.clusterHide) {
14711							nm.clusterHide();
14712						}
14713					}
14714
14715					c._group._featureGroup.addLayer(nm);
14716				}
14717			},
14718			function (c) {
14719				c._addToMap(startPos);
14720			}
14721		);
14722	},
14723
14724	_recursivelyRestoreChildPositions: function (zoomLevel) {
14725		//Fix positions of child markers
14726		for (var i = this._markers.length - 1; i >= 0; i--) {
14727			var nm = this._markers[i];
14728			if (nm._backupLatlng) {
14729				nm.setLatLng(nm._backupLatlng);
14730				delete nm._backupLatlng;
14731			}
14732		}
14733
14734		if (zoomLevel - 1 === this._zoom) {
14735			//Reposition child clusters
14736			for (var j = this._childClusters.length - 1; j >= 0; j--) {
14737				this._childClusters[j]._restorePosition();
14738			}
14739		} else {
14740			for (var k = this._childClusters.length - 1; k >= 0; k--) {
14741				this._childClusters[k]._recursivelyRestoreChildPositions(zoomLevel);
14742			}
14743		}
14744	},
14745
14746	_restorePosition: function () {
14747		if (this._backupLatlng) {
14748			this.setLatLng(this._backupLatlng);
14749			delete this._backupLatlng;
14750		}
14751	},
14752
14753	//exceptBounds: If set, don't remove any markers/clusters in it
14754	_recursivelyRemoveChildrenFromMap: function (previousBounds, mapMinZoom, zoomLevel, exceptBounds) {
14755		var m, i;
14756		this._recursively(previousBounds, mapMinZoom - 1, zoomLevel - 1,
14757			function (c) {
14758				//Remove markers at every level
14759				for (i = c._markers.length - 1; i >= 0; i--) {
14760					m = c._markers[i];
14761					if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
14762						c._group._featureGroup.removeLayer(m);
14763						if (m.clusterShow) {
14764							m.clusterShow();
14765						}
14766					}
14767				}
14768			},
14769			function (c) {
14770				//Remove child clusters at just the bottom level
14771				for (i = c._childClusters.length - 1; i >= 0; i--) {
14772					m = c._childClusters[i];
14773					if (!exceptBounds || !exceptBounds.contains(m._latlng)) {
14774						c._group._featureGroup.removeLayer(m);
14775						if (m.clusterShow) {
14776							m.clusterShow();
14777						}
14778					}
14779				}
14780			}
14781		);
14782	},
14783
14784	//Run the given functions recursively to this and child clusters
14785	// boundsToApplyTo: a L.LatLngBounds representing the bounds of what clusters to recurse in to
14786	// zoomLevelToStart: zoom level to start running functions (inclusive)
14787	// zoomLevelToStop: zoom level to stop running functions (inclusive)
14788	// runAtEveryLevel: function that takes an L.MarkerCluster as an argument that should be applied on every level
14789	// runAtBottomLevel: function that takes an L.MarkerCluster as an argument that should be applied at only the bottom level
14790	_recursively: function (boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel) {
14791		var childClusters = this._childClusters,
14792		    zoom = this._zoom,
14793		    i, c;
14794
14795		if (zoomLevelToStart <= zoom) {
14796			if (runAtEveryLevel) {
14797				runAtEveryLevel(this);
14798			}
14799			if (runAtBottomLevel && zoom === zoomLevelToStop) {
14800				runAtBottomLevel(this);
14801			}
14802		}
14803
14804		if (zoom < zoomLevelToStart || zoom < zoomLevelToStop) {
14805			for (i = childClusters.length - 1; i >= 0; i--) {
14806				c = childClusters[i];
14807				if (boundsToApplyTo.intersects(c._bounds)) {
14808					c._recursively(boundsToApplyTo, zoomLevelToStart, zoomLevelToStop, runAtEveryLevel, runAtBottomLevel);
14809				}
14810			}
14811		}
14812	},
14813
14814	//Returns true if we are the parent of only one cluster and that cluster is the same as us
14815	_isSingleParent: function () {
14816		//Don't need to check this._markers as the rest won't work if there are any
14817		return this._childClusters.length > 0 && this._childClusters[0]._childCount === this._childCount;
14818	}
14819});
14820
14821
14822
14823/*
14824* Extends L.Marker to include two extra methods: clusterHide and clusterShow.
14825* 
14826* They work as setOpacity(0) and setOpacity(1) respectively, but
14827* they will remember the marker's opacity when hiding and showing it again.
14828* 
14829*/
14830
14831
14832L.Marker.include({
14833	
14834	clusterHide: function () {
14835		this.options.opacityWhenUnclustered = this.options.opacity || 1;
14836		return this.setOpacity(0);
14837	},
14838	
14839	clusterShow: function () {
14840		var ret = this.setOpacity(this.options.opacity || this.options.opacityWhenUnclustered);
14841		delete this.options.opacityWhenUnclustered;
14842		return ret;
14843	}
14844	
14845});
14846
14847
14848
14849
14850
14851L.DistanceGrid = function (cellSize) {
14852	this._cellSize = cellSize;
14853	this._sqCellSize = cellSize * cellSize;
14854	this._grid = {};
14855	this._objectPoint = { };
14856};
14857
14858L.DistanceGrid.prototype = {
14859
14860	addObject: function (obj, point) {
14861		var x = this._getCoord(point.x),
14862		    y = this._getCoord(point.y),
14863		    grid = this._grid,
14864		    row = grid[y] = grid[y] || {},
14865		    cell = row[x] = row[x] || [],
14866		    stamp = L.Util.stamp(obj);
14867
14868		this._objectPoint[stamp] = point;
14869
14870		cell.push(obj);
14871	},
14872
14873	updateObject: function (obj, point) {
14874		this.removeObject(obj);
14875		this.addObject(obj, point);
14876	},
14877
14878	//Returns true if the object was found
14879	removeObject: function (obj, point) {
14880		var x = this._getCoord(point.x),
14881		    y = this._getCoord(point.y),
14882		    grid = this._grid,
14883		    row = grid[y] = grid[y] || {},
14884		    cell = row[x] = row[x] || [],
14885		    i, len;
14886
14887		delete this._objectPoint[L.Util.stamp(obj)];
14888
14889		for (i = 0, len = cell.length; i < len; i++) {
14890			if (cell[i] === obj) {
14891
14892				cell.splice(i, 1);
14893
14894				if (len === 1) {
14895					delete row[x];
14896				}
14897
14898				return true;
14899			}
14900		}
14901
14902	},
14903
14904	eachObject: function (fn, context) {
14905		var i, j, k, len, row, cell, removed,
14906		    grid = this._grid;
14907
14908		for (i in grid) {
14909			row = grid[i];
14910
14911			for (j in row) {
14912				cell = row[j];
14913
14914				for (k = 0, len = cell.length; k < len; k++) {
14915					removed = fn.call(context, cell[k]);
14916					if (removed) {
14917						k--;
14918						len--;
14919					}
14920				}
14921			}
14922		}
14923	},
14924
14925	getNearObject: function (point) {
14926		var x = this._getCoord(point.x),
14927		    y = this._getCoord(point.y),
14928		    i, j, k, row, cell, len, obj, dist,
14929		    objectPoint = this._objectPoint,
14930		    closestDistSq = this._sqCellSize,
14931		    closest = null;
14932
14933		for (i = y - 1; i <= y + 1; i++) {
14934			row = this._grid[i];
14935			if (row) {
14936
14937				for (j = x - 1; j <= x + 1; j++) {
14938					cell = row[j];
14939					if (cell) {
14940
14941						for (k = 0, len = cell.length; k < len; k++) {
14942							obj = cell[k];
14943							dist = this._sqDist(objectPoint[L.Util.stamp(obj)], point);
14944							if (dist < closestDistSq) {
14945								closestDistSq = dist;
14946								closest = obj;
14947							}
14948						}
14949					}
14950				}
14951			}
14952		}
14953		return closest;
14954	},
14955
14956	_getCoord: function (x) {
14957		return Math.floor(x / this._cellSize);
14958	},
14959
14960	_sqDist: function (p, p2) {
14961		var dx = p2.x - p.x,
14962		    dy = p2.y - p.y;
14963		return dx * dx + dy * dy;
14964	}
14965};
14966
14967
14968/* Copyright (c) 2012 the authors listed at the following URL, and/or
14969the authors of referenced articles or incorporated external code:
14970http://en.literateprograms.org/Quickhull_(Javascript)?action=history&offset=20120410175256
14971
14972Permission is hereby granted, free of charge, to any person obtaining
14973a copy of this software and associated documentation files (the
14974"Software"), to deal in the Software without restriction, including
14975without limitation the rights to use, copy, modify, merge, publish,
14976distribute, sublicense, and/or sell copies of the Software, and to
14977permit persons to whom the Software is furnished to do so, subject to
14978the following conditions:
14979
14980The above copyright notice and this permission notice shall be
14981included in all copies or substantial portions of the Software.
14982
14983THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14984EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14985MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
14986IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
14987CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
14988TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
14989SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
14990
14991Retrieved from: http://en.literateprograms.org/Quickhull_(Javascript)?oldid=18434
14992*/
14993
14994(function () {
14995	L.QuickHull = {
14996
14997		/*
14998		 * @param {Object} cpt a point to be measured from the baseline
14999		 * @param {Array} bl the baseline, as represented by a two-element
15000		 *   array of latlng objects.
15001		 * @returns {Number} an approximate distance measure
15002		 */
15003		getDistant: function (cpt, bl) {
15004			var vY = bl[1].lat - bl[0].lat,
15005				vX = bl[0].lng - bl[1].lng;
15006			return (vX * (cpt.lat - bl[0].lat) + vY * (cpt.lng - bl[0].lng));
15007		},
15008
15009		/*
15010		 * @param {Array} baseLine a two-element array of latlng objects
15011		 *   representing the baseline to project from
15012		 * @param {Array} latLngs an array of latlng objects
15013		 * @returns {Object} the maximum point and all new points to stay
15014		 *   in consideration for the hull.
15015		 */
15016		findMostDistantPointFromBaseLine: function (baseLine, latLngs) {
15017			var maxD = 0,
15018				maxPt = null,
15019				newPoints = [],
15020				i, pt, d;
15021
15022			for (i = latLngs.length - 1; i >= 0; i--) {
15023				pt = latLngs[i];
15024				d = this.getDistant(pt, baseLine);
15025
15026				if (d > 0) {
15027					newPoints.push(pt);
15028				} else {
15029					continue;
15030				}
15031
15032				if (d > maxD) {
15033					maxD = d;
15034					maxPt = pt;
15035				}
15036			}
15037
15038			return { maxPoint: maxPt, newPoints: newPoints };
15039		},
15040
15041
15042		/*
15043		 * Given a baseline, compute the convex hull of latLngs as an array
15044		 * of latLngs.
15045		 *
15046		 * @param {Array} latLngs
15047		 * @returns {Array}
15048		 */
15049		buildConvexHull: function (baseLine, latLngs) {
15050			var convexHullBaseLines = [],
15051				t = this.findMostDistantPointFromBaseLine(baseLine, latLngs);
15052
15053			if (t.maxPoint) { // if there is still a point "outside" the base line
15054				convexHullBaseLines =
15055					convexHullBaseLines.concat(
15056						this.buildConvexHull([baseLine[0], t.maxPoint], t.newPoints)
15057					);
15058				convexHullBaseLines =
15059					convexHullBaseLines.concat(
15060						this.buildConvexHull([t.maxPoint, baseLine[1]], t.newPoints)
15061					);
15062				return convexHullBaseLines;
15063			} else {  // if there is no more point "outside" the base line, the current base line is part of the convex hull
15064				return [baseLine[0]];
15065			}
15066		},
15067
15068		/*
15069		 * Given an array of latlngs, compute a convex hull as an array
15070		 * of latlngs
15071		 *
15072		 * @param {Array} latLngs
15073		 * @returns {Array}
15074		 */
15075		getConvexHull: function (latLngs) {
15076			// find first baseline
15077			var maxLat = false, minLat = false,
15078				maxLng = false, minLng = false,
15079				maxLatPt = null, minLatPt = null,
15080				maxLngPt = null, minLngPt = null,
15081				maxPt = null, minPt = null,
15082				i;
15083
15084			for (i = latLngs.length - 1; i >= 0; i--) {
15085				var pt = latLngs[i];
15086				if (maxLat === false || pt.lat > maxLat) {
15087					maxLatPt = pt;
15088					maxLat = pt.lat;
15089				}
15090				if (minLat === false || pt.lat < minLat) {
15091					minLatPt = pt;
15092					minLat = pt.lat;
15093				}
15094				if (maxLng === false || pt.lng > maxLng) {
15095					maxLngPt = pt;
15096					maxLng = pt.lng;
15097				}
15098				if (minLng === false || pt.lng < minLng) {
15099					minLngPt = pt;
15100					minLng = pt.lng;
15101				}
15102			}
15103			
15104			if (minLat !== maxLat) {
15105				minPt = minLatPt;
15106				maxPt = maxLatPt;
15107			} else {
15108				minPt = minLngPt;
15109				maxPt = maxLngPt;
15110			}
15111
15112			var ch = [].concat(this.buildConvexHull([minPt, maxPt], latLngs),
15113								this.buildConvexHull([maxPt, minPt], latLngs));
15114			return ch;
15115		}
15116	};
15117}());
15118
15119L.MarkerCluster.include({
15120	getConvexHull: function () {
15121		var childMarkers = this.getAllChildMarkers(),
15122			points = [],
15123			p, i;
15124
15125		for (i = childMarkers.length - 1; i >= 0; i--) {
15126			p = childMarkers[i].getLatLng();
15127			points.push(p);
15128		}
15129
15130		return L.QuickHull.getConvexHull(points);
15131	}
15132});
15133
15134
15135//This code is 100% based on https://github.com/jawj/OverlappingMarkerSpiderfier-Leaflet
15136//Huge thanks to jawj for implementing it first to make my job easy :-)
15137
15138L.MarkerCluster.include({
15139
15140	_2PI: Math.PI * 2,
15141	_circleFootSeparation: 25, //related to circumference of circle
15142	_circleStartAngle: Math.PI / 6,
15143
15144	_spiralFootSeparation:  28, //related to size of spiral (experiment!)
15145	_spiralLengthStart: 11,
15146	_spiralLengthFactor: 5,
15147
15148	_circleSpiralSwitchover: 9, //show spiral instead of circle from this marker count upwards.
15149								// 0 -> always spiral; Infinity -> always circle
15150
15151	spiderfy: function () {
15152		if (this._group._spiderfied === this || this._group._inZoomAnimation) {
15153			return;
15154		}
15155
15156		var childMarkers = this.getAllChildMarkers(),
15157			group = this._group,
15158			map = group._map,
15159			center = map.latLngToLayerPoint(this._latlng),
15160			positions;
15161
15162		this._group._unspiderfy();
15163		this._group._spiderfied = this;
15164
15165		//TODO Maybe: childMarkers order by distance to center
15166
15167		if (childMarkers.length >= this._circleSpiralSwitchover) {
15168			positions = this._generatePointsSpiral(childMarkers.length, center);
15169		} else {
15170			center.y += 10; // Otherwise circles look wrong => hack for standard blue icon, renders differently for other icons.
15171			positions = this._generatePointsCircle(childMarkers.length, center);
15172		}
15173
15174		this._animationSpiderfy(childMarkers, positions);
15175	},
15176
15177	unspiderfy: function (zoomDetails) {
15178		/// <param Name="zoomDetails">Argument from zoomanim if being called in a zoom animation or null otherwise</param>
15179		if (this._group._inZoomAnimation) {
15180			return;
15181		}
15182		this._animationUnspiderfy(zoomDetails);
15183
15184		this._group._spiderfied = null;
15185	},
15186
15187	_generatePointsCircle: function (count, centerPt) {
15188		var circumference = this._group.options.spiderfyDistanceMultiplier * this._circleFootSeparation * (2 + count),
vendor: 11,941 bytes, lines 15189-15607
15189			legLength = circumference / this._2PI,  //radius from circumference
15190			angleStep = this._2PI / count,
15191			res = [],
15192			i, angle;
15193
15194		res.length = count;
15195
15196		for (i = count - 1; i >= 0; i--) {
15197			angle = this._circleStartAngle + i * angleStep;
15198			res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
15199		}
15200
15201		return res;
15202	},
15203
15204	_generatePointsSpiral: function (count, centerPt) {
15205		var spiderfyDistanceMultiplier = this._group.options.spiderfyDistanceMultiplier,
15206			legLength = spiderfyDistanceMultiplier * this._spiralLengthStart,
15207			separation = spiderfyDistanceMultiplier * this._spiralFootSeparation,
15208			lengthFactor = spiderfyDistanceMultiplier * this._spiralLengthFactor * this._2PI,
15209			angle = 0,
15210			res = [],
15211			i;
15212
15213		res.length = count;
15214
15215		// Higher index, closer position to cluster center.
15216		for (i = count - 1; i >= 0; i--) {
15217			angle += separation / legLength + i * 0.0005;
15218			res[i] = new L.Point(centerPt.x + legLength * Math.cos(angle), centerPt.y + legLength * Math.sin(angle))._round();
15219			legLength += lengthFactor / angle;
15220		}
15221		return res;
15222	},
15223
15224	_noanimationUnspiderfy: function () {
15225		var group = this._group,
15226			map = group._map,
15227			fg = group._featureGroup,
15228			childMarkers = this.getAllChildMarkers(),
15229			m, i;
15230
15231		group._ignoreMove = true;
15232
15233		this.setOpacity(1);
15234		for (i = childMarkers.length - 1; i >= 0; i--) {
15235			m = childMarkers[i];
15236
15237			fg.removeLayer(m);
15238
15239			if (m._preSpiderfyLatlng) {
15240				m.setLatLng(m._preSpiderfyLatlng);
15241				delete m._preSpiderfyLatlng;
15242			}
15243			if (m.setZIndexOffset) {
15244				m.setZIndexOffset(0);
15245			}
15246
15247			if (m._spiderLeg) {
15248				map.removeLayer(m._spiderLeg);
15249				delete m._spiderLeg;
15250			}
15251		}
15252
15253		group.fire('unspiderfied', {
15254			cluster: this,
15255			markers: childMarkers
15256		});
15257		group._ignoreMove = false;
15258		group._spiderfied = null;
15259	}
15260});
15261
15262//Non Animated versions of everything
15263L.MarkerClusterNonAnimated = L.MarkerCluster.extend({
15264	_animationSpiderfy: function (childMarkers, positions) {
15265		var group = this._group,
15266			map = group._map,
15267			fg = group._featureGroup,
15268			legOptions = this._group.options.spiderLegPolylineOptions,
15269			i, m, leg, newPos;
15270
15271		group._ignoreMove = true;
15272
15273		// Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
15274		// The reverse order trick no longer improves performance on modern browsers.
15275		for (i = 0; i < childMarkers.length; i++) {
15276			newPos = map.layerPointToLatLng(positions[i]);
15277			m = childMarkers[i];
15278
15279			// Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
15280			leg = new L.Polyline([this._latlng, newPos], legOptions);
15281			map.addLayer(leg);
15282			m._spiderLeg = leg;
15283
15284			// Now add the marker.
15285			m._preSpiderfyLatlng = m._latlng;
15286			m.setLatLng(newPos);
15287			if (m.setZIndexOffset) {
15288				m.setZIndexOffset(1000000); //Make these appear on top of EVERYTHING
15289			}
15290
15291			fg.addLayer(m);
15292		}
15293		this.setOpacity(0.3);
15294
15295		group._ignoreMove = false;
15296		group.fire('spiderfied', {
15297			cluster: this,
15298			markers: childMarkers
15299		});
15300	},
15301
15302	_animationUnspiderfy: function () {
15303		this._noanimationUnspiderfy();
15304	}
15305});
15306
15307//Animated versions here
15308L.MarkerCluster.include({
15309
15310	_animationSpiderfy: function (childMarkers, positions) {
15311		var me = this,
15312			group = this._group,
15313			map = group._map,
15314			fg = group._featureGroup,
15315			thisLayerLatLng = this._latlng,
15316			thisLayerPos = map.latLngToLayerPoint(thisLayerLatLng),
15317			svg = L.Path.SVG,
15318			legOptions = L.extend({}, this._group.options.spiderLegPolylineOptions), // Copy the options so that we can modify them for animation.
15319			finalLegOpacity = legOptions.opacity,
15320			i, m, leg, legPath, legLength, newPos;
15321
15322		if (finalLegOpacity === undefined) {
15323			finalLegOpacity = L.MarkerClusterGroup.prototype.options.spiderLegPolylineOptions.opacity;
15324		}
15325
15326		if (svg) {
15327			// If the initial opacity of the spider leg is not 0 then it appears before the animation starts.
15328			legOptions.opacity = 0;
15329
15330			// Add the class for CSS transitions.
15331			legOptions.className = (legOptions.className || '') + ' leaflet-cluster-spider-leg';
15332		} else {
15333			// Make sure we have a defined opacity.
15334			legOptions.opacity = finalLegOpacity;
15335		}
15336
15337		group._ignoreMove = true;
15338
15339		// Add markers and spider legs to map, hidden at our center point.
15340		// Traverse in ascending order to make sure that inner circleMarkers are on top of further legs. Normal markers are re-ordered by newPosition.
15341		// The reverse order trick no longer improves performance on modern browsers.
15342		for (i = 0; i < childMarkers.length; i++) {
15343			m = childMarkers[i];
15344
15345			newPos = map.layerPointToLatLng(positions[i]);
15346
15347			// Add the leg before the marker, so that in case the latter is a circleMarker, the leg is behind it.
15348			leg = new L.Polyline([thisLayerLatLng, newPos], legOptions);
15349			map.addLayer(leg);
15350			m._spiderLeg = leg;
15351
15352			// Explanations: https://jakearchibald.com/2013/animated-line-drawing-svg/
15353			// In our case the transition property is declared in the CSS file.
15354			if (svg) {
15355				legPath = leg._path;
15356				legLength = legPath.getTotalLength() + 0.1; // Need a small extra length to avoid remaining dot in Firefox.
15357				legPath.style.strokeDasharray = legLength; // Just 1 length is enough, it will be duplicated.
15358				legPath.style.strokeDashoffset = legLength;
15359			}
15360
15361			// If it is a marker, add it now and we'll animate it out
15362			if (m.setZIndexOffset) {
15363				m.setZIndexOffset(1000000); // Make normal markers appear on top of EVERYTHING
15364			}
15365			if (m.clusterHide) {
15366				m.clusterHide();
15367			}
15368			
15369			// Vectors just get immediately added
15370			fg.addLayer(m);
15371
15372			if (m._setPos) {
15373				m._setPos(thisLayerPos);
15374			}
15375		}
15376
15377		group._forceLayout();
15378		group._animationStart();
15379
15380		// Reveal markers and spider legs.
15381		for (i = childMarkers.length - 1; i >= 0; i--) {
15382			newPos = map.layerPointToLatLng(positions[i]);
15383			m = childMarkers[i];
15384
15385			//Move marker to new position
15386			m._preSpiderfyLatlng = m._latlng;
15387			m.setLatLng(newPos);
15388			
15389			if (m.clusterShow) {
15390				m.clusterShow();
15391			}
15392
15393			// Animate leg (animation is actually delegated to CSS transition).
15394			if (svg) {
15395				leg = m._spiderLeg;
15396				legPath = leg._path;
15397				legPath.style.strokeDashoffset = 0;
15398				//legPath.style.strokeOpacity = finalLegOpacity;
15399				leg.setStyle({opacity: finalLegOpacity});
15400			}
15401		}
15402		this.setOpacity(0.3);
15403
15404		group._ignoreMove = false;
15405
15406		setTimeout(function () {
15407			group._animationEnd();
15408			group.fire('spiderfied', {
15409				cluster: me,
15410				markers: childMarkers
15411			});
15412		}, 200);
15413	},
15414
15415	_animationUnspiderfy: function (zoomDetails) {
15416		var me = this,
15417			group = this._group,
15418			map = group._map,
15419			fg = group._featureGroup,
15420			thisLayerPos = zoomDetails ? map._latLngToNewLayerPoint(this._latlng, zoomDetails.zoom, zoomDetails.center) : map.latLngToLayerPoint(this._latlng),
15421			childMarkers = this.getAllChildMarkers(),
15422			svg = L.Path.SVG,
15423			m, i, leg, legPath, legLength, nonAnimatable;
15424
15425		group._ignoreMove = true;
15426		group._animationStart();
15427
15428		//Make us visible and bring the child markers back in
15429		this.setOpacity(1);
15430		for (i = childMarkers.length - 1; i >= 0; i--) {
15431			m = childMarkers[i];
15432
15433			//Marker was added to us after we were spiderfied
15434			if (!m._preSpiderfyLatlng) {
15435				continue;
15436			}
15437
15438			//Close any popup on the marker first, otherwise setting the location of the marker will make the map scroll
15439			m.closePopup();
15440
15441			//Fix up the location to the real one
15442			m.setLatLng(m._preSpiderfyLatlng);
15443			delete m._preSpiderfyLatlng;
15444
15445			//Hack override the location to be our center
15446			nonAnimatable = true;
15447			if (m._setPos) {
15448				m._setPos(thisLayerPos);
15449				nonAnimatable = false;
15450			}
15451			if (m.clusterHide) {
15452				m.clusterHide();
15453				nonAnimatable = false;
15454			}
15455			if (nonAnimatable) {
15456				fg.removeLayer(m);
15457			}
15458
15459			// Animate the spider leg back in (animation is actually delegated to CSS transition).
15460			if (svg) {
15461				leg = m._spiderLeg;
15462				legPath = leg._path;
15463				legLength = legPath.getTotalLength() + 0.1;
15464				legPath.style.strokeDashoffset = legLength;
15465				leg.setStyle({opacity: 0});
15466			}
15467		}
15468
15469		group._ignoreMove = false;
15470
15471		setTimeout(function () {
15472			//If we have only <= one child left then that marker will be shown on the map so don't remove it!
15473			var stillThereChildCount = 0;
15474			for (i = childMarkers.length - 1; i >= 0; i--) {
15475				m = childMarkers[i];
15476				if (m._spiderLeg) {
15477					stillThereChildCount++;
15478				}
15479			}
15480
15481
15482			for (i = childMarkers.length - 1; i >= 0; i--) {
15483				m = childMarkers[i];
15484
15485				if (!m._spiderLeg) { //Has already been unspiderfied
15486					continue;
15487				}
15488
15489				if (m.clusterShow) {
15490					m.clusterShow();
15491				}
15492				if (m.setZIndexOffset) {
15493					m.setZIndexOffset(0);
15494				}
15495
15496				if (stillThereChildCount > 1) {
15497					fg.removeLayer(m);
15498				}
15499
15500				map.removeLayer(m._spiderLeg);
15501				delete m._spiderLeg;
15502			}
15503			group._animationEnd();
15504			group.fire('unspiderfied', {
15505				cluster: me,
15506				markers: childMarkers
15507			});
15508		}, 200);
15509	}
15510});
15511
15512
15513L.MarkerClusterGroup.include({
15514	//The MarkerCluster currently spiderfied (if any)
15515	_spiderfied: null,
15516
15517	unspiderfy: function () {
15518		this._unspiderfy.apply(this, arguments);
15519	},
15520
15521	_spiderfierOnAdd: function () {
15522		this._map.on('click', this._unspiderfyWrapper, this);
15523
15524		if (this._map.options.zoomAnimation) {
15525			this._map.on('zoomstart', this._unspiderfyZoomStart, this);
15526		}
15527		//Browsers without zoomAnimation or a big zoom don't fire zoomstart
15528		this._map.on('zoomend', this._noanimationUnspiderfy, this);
15529
15530		if (!L.Browser.touch) {
15531			this._map.getRenderer(this);
15532			//Needs to happen in the pageload, not after, or animations don't work in webkit
15533			//  http://stackoverflow.com/questions/8455200/svg-animate-with-dynamically-added-elements
15534			//Disable on touch browsers as the animation messes up on a touch zoom and isn't very noticable
15535		}
15536	},
15537
15538	_spiderfierOnRemove: function () {
15539		this._map.off('click', this._unspiderfyWrapper, this);
15540		this._map.off('zoomstart', this._unspiderfyZoomStart, this);
15541		this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
15542		this._map.off('zoomend', this._noanimationUnspiderfy, this);
15543
15544		//Ensure that markers are back where they should be
15545		// Use no animation to avoid a sticky leaflet-cluster-anim class on mapPane
15546		this._noanimationUnspiderfy();
15547	},
15548
15549	//On zoom start we add a zoomanim handler so that we are guaranteed to be last (after markers are animated)
15550	//This means we can define the animation they do rather than Markers doing an animation to their actual location
15551	_unspiderfyZoomStart: function () {
15552		if (!this._map) { //May have been removed from the map by a zoomEnd handler
15553			return;
15554		}
15555
15556		this._map.on('zoomanim', this._unspiderfyZoomAnim, this);
15557	},
15558
15559	_unspiderfyZoomAnim: function (zoomDetails) {
15560		//Wait until the first zoomanim after the user has finished touch-zooming before running the animation
15561		if (L.DomUtil.hasClass(this._map._mapPane, 'leaflet-touching')) {
15562			return;
15563		}
15564
15565		this._map.off('zoomanim', this._unspiderfyZoomAnim, this);
15566		this._unspiderfy(zoomDetails);
15567	},
15568
15569	_unspiderfyWrapper: function () {
15570		/// <summary>_unspiderfy but passes no arguments</summary>
15571		this._unspiderfy();
15572	},
15573
15574	_unspiderfy: function (zoomDetails) {
15575		if (this._spiderfied) {
15576			this._spiderfied.unspiderfy(zoomDetails);
15577		}
15578	},
15579
15580	_noanimationUnspiderfy: function () {
15581		if (this._spiderfied) {
15582			this._spiderfied._noanimationUnspiderfy();
15583		}
15584	},
15585
15586	//If the given layer is currently being spiderfied then we unspiderfy it so it isn't on the map anymore etc
15587	_unspiderfyLayer: function (layer) {
15588		if (layer._spiderLeg) {
15589			this._featureGroup.removeLayer(layer);
15590
15591			if (layer.clusterShow) {
15592				layer.clusterShow();
15593			}
15594				//Position will be fixed up immediately in _animationUnspiderfy
15595			if (layer.setZIndexOffset) {
15596				layer.setZIndexOffset(0);
15597			}
15598
15599			this._map.removeLayer(layer._spiderLeg);
15600			delete layer._spiderLeg;
15601		}
15602	}
15603});
15604
15605
15606/**
15607 * Adds 1 public method to MCG and 1 to L.Marker to facilitate changing
15608 * markers' icon options and refreshing their icon and their parent clusters
15609 * accordingly (case where their iconCreateFunction uses data of childMarkers
15610 * to make up the cluster icon).
15611 */
15612
15613
15614L.MarkerClusterGroup.include({
15615	/**
15616	 * Updates the icon of all clusters which are parents of the given marker(s).
15617	 * In singleMarkerMode, also updates the given marker(s) icon.
15618	 * @param layers L.MarkerClusterGroup|L.LayerGroup|Array(L.Marker)|Map(L.Marker)|
15619	 * L.MarkerCluster|L.Marker (optional) list of markers (or single marker) whose parent
15620	 * clusters need to be updated. If not provided, retrieves all child markers of this.
15621	 * @returns {L.MarkerClusterGroup}
15622	 */
15623	refreshClusters: function (layers) {
15624		if (!layers) {
15625			layers = this._topClusterLevel.getAllChildMarkers();
15626		} else if (layers instanceof L.MarkerClusterGroup) {
15627			layers = layers._topClusterLevel.getAllChildMarkers();
15628		} else if (layers instanceof L.LayerGroup) {
15629			layers = layers._layers;
15630		} else if (layers instanceof L.MarkerCluster) {
15631			layers = layers.getAllChildMarkers();
15632		} else if (layers instanceof L.Marker) {
15633			layers = [layers];
15634		} // else: must be an Array(L.Marker)|Map(L.Marker)
15635		this._flagParentsIconsNeedUpdate(layers);
15636		this._refreshClustersIcons();
15637
15638		// In case of singleMarkerMode, also re-draw the markers.
15639		if (this.options.singleMarkerMode) {
15640			this._refreshSingleMarkerModeMarkers(layers);
15641		}
15642
15643		return this;
15644	},
15645
15646	/**
15647	 * Simply flags all parent clusters of the given markers as having a "dirty" icon.
15648	 * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
15649	 * @private
15650	 */
15651	_flagParentsIconsNeedUpdate: function (layers) {
15652		var id, parent;
15653
15654		// Assumes layers is an Array or an Object whose prototype is non-enumerable.
15655		for (id in layers) {
15656			// Flag parent clusters' icon as "dirty", all the way up.
15657			// Dumb process that flags multiple times upper parents, but still
15658			// much more efficient than trying to be smart and make short lists,
15659			// at least in the case of a hierarchy following a power law:
15660			// http://jsperf.com/flag-nodes-in-power-hierarchy/2
15661			parent = layers[id].__parent;
15662			while (parent) {
15663				parent._iconNeedsUpdate = true;
15664				parent = parent.__parent;
15665			}
15666		}
15667	},
15668
15669	/**
15670	 * Re-draws the icon of the supplied markers.
15671	 * To be used in singleMarkerMode only.
15672	 * @param layers Array(L.Marker)|Map(L.Marker) list of markers.
15673	 * @private
15674	 */
15675	_refreshSingleMarkerModeMarkers: function (layers) {
15676		var id, layer;
15677
15678		for (id in layers) {
15679			layer = layers[id];
15680
15681			// Make sure we do not override markers that do not belong to THIS group.
15682			if (this.hasLayer(layer)) {
15683				// Need to re-create the icon first, then re-draw the marker.
15684				layer.setIcon(this._overrideMarkerIcon(layer));
15685			}
15686		}
15687	}
15688});
15689
15690L.Marker.include({
15691	/**
15692	 * Updates the given options in the marker's icon and refreshes the marker.
15693	 * @param options map object of icon options.
15694	 * @param directlyRefreshClusters boolean (optional) true to trigger
15695	 * MCG.refreshClustersOf() right away with this single marker.
15696	 * @returns {L.Marker}
15697	 */
15698	refreshIconOptions: function (options, directlyRefreshClusters) {
15699		var icon = this.options.icon;
15700
15701		L.setOptions(icon, options);
15702
15703		this.setIcon(icon);
15704
15705		// Shortcut to refresh the associated MCG clusters right away.
15706		// To be used when refreshing a single marker.
15707		// Otherwise, better use MCG.refreshClusters() once at the end with
15708		// the list of modified markers.
15709		if (directlyRefreshClusters && this.__parent) {
15710			this.__parent._group.refreshClusters(this);
15711		}
15712
15713		return this;
15714	}
15715});
15716
15717
15718}(window, document));
15719// Based on https://github.com/shramov/leaflet-plugins
15720// GridLayer like https://avinmathew.com/leaflet-and-google-maps/ , but using MutationObserver instead of jQuery
15721
15722
15723// 🍂class GridLayer.GoogleMutant
15724// 🍂extends GridLayer
15725L.GridLayer.GoogleMutant = L.GridLayer.extend({
15726	options: {
15727		minZoom: 0,
15728		maxZoom: 23,
15729		tileSize: 256,
15730		subdomains: 'abc',
15731		errorTileUrl: '',
15732		attribution: '',	// The mutant container will add its own attribution anyways.
15733		opacity: 1,
15734		continuousWorld: false,
15735		noWrap: false,
15736		// 🍂option type: String = 'roadmap'
15737		// Google's map type. Valid values are 'roadmap', 'satellite' or 'terrain'. 'hybrid' is not really supported.
15738		type: 'roadmap',
15739		maxNativeZoom: 21
15740	},
15741
15742	initialize: function (options) {
15743		L.GridLayer.prototype.initialize.call(this, options);
15744
15745		this._ready = !!window.google && !!window.google.maps && !!window.google.maps.Map;
15746
15747		this._GAPIPromise = this._ready ? Promise.resolve(window.google) : new Promise(function (resolve, reject) {
15748			var checkCounter = 0;
15749			var intervalId = null;
15750			intervalId = setInterval(function () {
15751				if (checkCounter >= 10) {
15752					clearInterval(intervalId);
15753					return reject(new Error('window.google not found after 10 attempts'));
15754				}
15755				if (!!window.google && !!window.google.maps && !!window.google.maps.Map) {
15756					clearInterval(intervalId);
15757					return resolve(window.google);
15758				}
15759				checkCounter++;
15760			}, 500);
15761		});
15762
15763		// Couple data structures indexed by tile key
15764		this._tileCallbacks = {};	// Callbacks for promises for tiles that are expected
15765		this._freshTiles = {};	// Tiles from the mutant which haven't been requested yet
15766
15767		this._imagesPerTile = (this.options.type === 'hybrid') ? 2 : 1;
15768	},
15769
15770	onAdd: function (map) {
15771		L.GridLayer.prototype.onAdd.call(this, map);
15772		this._initMutantContainer();
15773
15774		this._GAPIPromise.then(function () {
15775			this._ready = true;
15776			this._map = map;
15777
15778			this._initMutant();
15779
15780			map.on('viewreset', this._reset, this);
15781			map.on('move', this._update, this);
15782			map.on('zoomend', this._handleZoomAnim, this);
15783			map.on('resize', this._resize, this);
15784
15785			//handle layer being added to a map for which there are no Google tiles at the given zoom
15786			google.maps.event.addListenerOnce(this._mutant, 'idle', function () {
15787				this._checkZoomLevels();
15788				this._mutantIsReady = true;
15789			}.bind(this));
15790
15791			//20px instead of 1em to avoid a slight overlap with google's attribution
15792			map._controlCorners.bottomright.style.marginBottom = '20px';
15793			map._controlCorners.bottomleft.style.marginBottom = '20px';
15794
15795			this._reset();
15796			this._update();
15797
15798			if (this._subLayers) {
15799				//restore previously added google layers
15800				for (var layerName in this._subLayers) {
15801					this._subLayers[layerName].setMap(this._mutant);
15802				}
15803			}
15804		}.bind(this));
15805	},
15806
15807	onRemove: function (map) {
15808		L.GridLayer.prototype.onRemove.call(this, map);
15809		map._container.removeChild(this._mutantContainer);
15810		this._mutantContainer = undefined;
15811
15812		google.maps.event.clearListeners(map, 'idle');
15813		google.maps.event.clearListeners(this._mutant, 'idle');
15814		map.off('viewreset', this._reset, this);
15815		map.off('move', this._update, this);
15816		map.off('zoomend', this._handleZoomAnim, this);
15817		map.off('resize', this._resize, this);
15818
15819		if (map._controlCorners) {
15820			map._controlCorners.bottomright.style.marginBottom = '0em';
15821			map._controlCorners.bottomleft.style.marginBottom = '0em';
15822		}
15823	},
15824
15825	getAttribution: function () {
15826		return this.options.attribution;
15827	},
15828
15829	setOpacity: function (opacity) {
15830		this.options.opacity = opacity;
15831		if (opacity < 1) {
15832			L.DomUtil.setOpacity(this._mutantContainer, opacity);
15833		}
15834	},
15835
15836	setElementSize: function (e, size) {
15837		e.style.width = size.x + 'px';
15838		e.style.height = size.y + 'px';
15839	},
15840
15841
15842	addGoogleLayer: function (googleLayerName, options) {
15843		if (!this._subLayers) this._subLayers = {};
15844		return this._GAPIPromise.then(function () {
15845			var Constructor = google.maps[googleLayerName];
15846			var googleLayer = new Constructor(options);
15847			googleLayer.setMap(this._mutant);
15848			this._subLayers[googleLayerName] = googleLayer;
15849			return googleLayer;
15850		}.bind(this));
15851	},
15852
15853	removeGoogleLayer: function (googleLayerName) {
15854		var googleLayer = this._subLayers && this._subLayers[googleLayerName];
15855		if (!googleLayer) return;
15856
15857		googleLayer.setMap(null);
15858		delete this._subLayers[googleLayerName];
15859	},
15860
15861
15862	_initMutantContainer: function () {
15863		if (!this._mutantContainer) {
15864			this._mutantContainer = L.DomUtil.create('div', 'leaflet-google-mutant leaflet-top leaflet-left');
15865			this._mutantContainer.id = '_MutantContainer_' + L.Util.stamp(this._mutantContainer);
15866			this._mutantContainer.style.zIndex = '800'; //leaflet map pane at 400, controls at 1000
15867			this._mutantContainer.style.pointerEvents = 'none';
15868
15869			this._map.getContainer().appendChild(this._mutantContainer);
15870		}
15871
15872		this.setOpacity(this.options.opacity);
15873		this.setElementSize(this._mutantContainer, this._map.getSize());
15874
15875		this._attachObserver(this._mutantContainer);
15876	},
15877
15878	_initMutant: function () {
15879		if (!this._ready || !this._mutantContainer) return;
15880		this._mutantCenter = new google.maps.LatLng(0, 0);
15881
15882		var map = new google.maps.Map(this._mutantContainer, {
15883			center: this._mutantCenter,
15884			zoom: 0,
15885			tilt: 0,
15886			mapTypeId: this.options.type,
15887			disableDefaultUI: true,
15888			keyboardShortcuts: false,
15889			draggable: false,
15890			disableDoubleClickZoom: true,
15891			scrollwheel: false,
15892			streetViewControl: false,
15893			styles: this.options.styles || {},
15894			backgroundColor: 'transparent'
15895		});
15896
15897		this._mutant = map;
15898
15899		google.maps.event.addListenerOnce(map, 'idle', function () {
15900			var nodes = this._mutantContainer.querySelectorAll('a');
15901			for (var i = 0; i < nodes.length; i++) {
15902				nodes[i].style.pointerEvents = 'auto';
15903			}
15904		}.bind(this));
15905
15906		// 🍂event spawned
15907		// Fired when the mutant has been created.
15908		this.fire('spawned', {mapObject: map});
15909	},
15910
15911	_attachObserver: function _attachObserver (node) {
15912// 		console.log('Gonna observe', node);
15913
15914		var observer = new MutationObserver(this._onMutations.bind(this));
15915
15916		// pass in the target node, as well as the observer options
15917		observer.observe(node, { childList: true, subtree: true });
15918	},
15919
15920	_onMutations: function _onMutations (mutations) {
15921		for (var i = 0; i < mutations.length; ++i) {
15922			var mutation = mutations[i];
15923			for (var j = 0; j < mutation.addedNodes.length; ++j) {
15924				var node = mutation.addedNodes[j];
15925
15926				if (node instanceof HTMLImageElement) {
15927					this._onMutatedImage(node);
15928				} else if (node instanceof HTMLElement) {
15929					Array.prototype.forEach.call(node.querySelectorAll('img'), this._onMutatedImage.bind(this));
15930				}
15931			}
15932		}
15933	},
15934
15935	// Only images which 'src' attrib match this will be considered for moving around.
15936	// Looks like some kind of string-based protobuf, maybe??
15937	// Only the roads (and terrain, and vector-based stuff) match this pattern
15938	_roadRegexp: /!1i(\d+)!2i(\d+)!3i(\d+)!/,
15939
15940	// On the other hand, raster imagery matches this other pattern
15941	_satRegexp: /x=(\d+)&y=(\d+)&z=(\d+)/,
15942
15943	// On small viewports, when zooming in/out, a static image is requested
15944	// This will not be moved around, just removed from the DOM.
15945	_staticRegExp: /StaticMapService\.GetMapImage/,
15946
15947	_onMutatedImage: function _onMutatedImage (imgNode) {
15948// 		if (imgNode.src) {
15949// 			console.log('caught mutated image: ', imgNode.src);
15950// 		}
15951
15952		var coords;
15953		var match = imgNode.src.match(this._roadRegexp);
15954		var sublayer = 0;
15955
15956		if (match) {
15957			coords = {
15958				z: match[1],
15959				x: match[2],
15960				y: match[3]
15961			};
15962			if (this._imagesPerTile > 1) { 
15963				imgNode.style.zIndex = 1;
15964				sublayer = 1;
15965			}
15966		} else {
15967			match = imgNode.src.match(this._satRegexp);
15968			if (match) {
15969				coords = {
15970					x: match[1],
15971					y: match[2],
15972					z: match[3]
15973				};
15974			}
15975// 			imgNode.style.zIndex = 0;
15976			sublayer = 0;
15977		}
15978
15979		if (coords) {
15980			var tileKey = this._tileCoordsToKey(coords);
15981			imgNode.style.position = 'absolute';
15982			imgNode.style.visibility = 'hidden';
15983
15984			var key = tileKey + '/' + sublayer;
15985			// console.log('mutation for tile', key)
15986			//store img so it can also be used in subsequent tile requests
15987			this._freshTiles[key] = imgNode;
15988
15989			if (key in this._tileCallbacks && this._tileCallbacks[key]) {
15990// console.log('Fullfilling callback ', key);
15991				//fullfill most recent tileCallback because there maybe callbacks that will never get a 
15992				//corresponding mutation (because map moved to quickly...)
15993				this._tileCallbacks[key].pop()(imgNode); 
15994				if (!this._tileCallbacks[key].length) { delete this._tileCallbacks[key]; }
15995			} else {
15996				if (this._tiles[tileKey]) {
15997					//we already have a tile in this position (mutation is probably a google layer being added)
15998					//replace it
15999					var c = this._tiles[tileKey].el;
16000					var oldImg = (sublayer === 0) ? c.firstChild : c.firstChild.nextSibling;
16001					var cloneImgNode = this._clone(imgNode);
16002					c.replaceChild(cloneImgNode, oldImg);
16003				}
16004			}
16005		} else if (imgNode.src.match(this._staticRegExp)) {
16006			imgNode.style.visibility = 'hidden';
16007		}
16008	},
16009
16010
16011	createTile: function (coords, done) {
16012		var key = this._tileCoordsToKey(coords);
16013
16014		var tileContainer = L.DomUtil.create('div');
16015		tileContainer.dataset.pending = this._imagesPerTile;
16016		done = done.bind(this, null, tileContainer);
16017
16018		for (var i = 0; i < this._imagesPerTile; i++) {
16019			var key2 = key + '/' + i;
16020			if (key2 in this._freshTiles) {
16021				var imgNode = this._freshTiles[key2];
16022				tileContainer.appendChild(this._clone(imgNode));
16023				tileContainer.dataset.pending--;
16024// 				console.log('Got ', key2, ' from _freshTiles');
16025			} else {
16026				this._tileCallbacks[key2] = this._tileCallbacks[key2] || [];
16027				this._tileCallbacks[key2].push( (function (c/*, k2*/) {
16028					return function (imgNode) {
16029						c.appendChild(this._clone(imgNode));
16030						c.dataset.pending--;
16031						if (!parseInt(c.dataset.pending)) { done(); }
16032// 						console.log('Sent ', k2, ' to _tileCallbacks, still ', c.dataset.pending, ' images to go');
16033					}.bind(this);
16034				}.bind(this))(tileContainer/*, key2*/) );
16035			}
16036		}
16037
16038		if (!parseInt(tileContainer.dataset.pending)) {
16039			L.Util.requestAnimFrame(done);
16040		}
16041		return tileContainer;
16042	},
16043
16044	_clone: function (imgNode) {
16045		var clonedImgNode = imgNode.cloneNode(true);
16046		clonedImgNode.style.visibility = 'visible';
16047		return clonedImgNode;
16048	},
16049
16050	_checkZoomLevels: function () {
16051		//setting the zoom level on the Google map may result in a different zoom level than the one requested
16052		//(it won't go beyond the level for which they have data).
16053		var zoomLevel = this._map.getZoom();
16054		var gMapZoomLevel = this._mutant.getZoom();
16055		if (!zoomLevel || !gMapZoomLevel) return;
16056
16057
16058		if ((gMapZoomLevel !== zoomLevel) || //zoom levels are out of sync, Google doesn't have data
16059			(gMapZoomLevel > this.options.maxNativeZoom)) { //at current location, Google does have data (contrary to maxNativeZoom)
16060			//Update maxNativeZoom
16061			this._setMaxNativeZoom(gMapZoomLevel);
16062		}
16063	},
16064
16065	_setMaxNativeZoom: function (zoomLevel) {
16066		if (zoomLevel != this.options.maxNativeZoom) {
16067			this.options.maxNativeZoom = zoomLevel;
16068			this._resetView();
16069		}
16070	},
16071
16072	_reset: function () {
16073		this._initContainer();
16074	},
16075
16076	_update: function () {
16077		// zoom level check needs to happen before super's implementation (tile addition/creation)
16078		// otherwise tiles may be missed if maxNativeZoom is not yet correctly determined
16079		if (this._mutant) {
16080			var center = this._map.getCenter();
16081			var _center = new google.maps.LatLng(center.lat, center.lng);
16082
16083			this._mutant.setCenter(_center);
16084			var zoom = this._map.getZoom();
16085			var fractionalLevel = zoom !== Math.round(zoom);
16086			var mutantZoom = this._mutant.getZoom();
16087
16088			//ignore fractional zoom levels
16089			if (!fractionalLevel && (zoom != mutantZoom)) {
16090				this._mutant.setZoom(zoom);
16091							
16092				if (this._mutantIsReady) this._checkZoomLevels();
16093				//else zoom level check will be done later by 'idle' handler
16094			}
16095		}
16096
16097		L.GridLayer.prototype._update.call(this);
16098	},
16099
16100	_resize: function () {
16101		var size = this._map.getSize();
16102		if (this._mutantContainer.style.width === size.x &&
16103			this._mutantContainer.style.height === size.y)
16104			return;
16105		this.setElementSize(this._mutantContainer, size);
16106		if (!this._mutant) return;
16107		google.maps.event.trigger(this._mutant, 'resize');
16108	},
16109
16110	_handleZoomAnim: function () {
16111		if (!this._mutant) return;
16112		var center = this._map.getCenter();
16113		var _center = new google.maps.LatLng(center.lat, center.lng);
16114
16115		this._mutant.setCenter(_center);
16116		this._mutant.setZoom(Math.round(this._map.getZoom()));
16117	},
16118
16119	// Agressively prune _freshtiles when a tile with the same key is removed,
16120	// this prevents a problem where Leaflet keeps a loaded tile longer than
16121	// GMaps, so that GMaps makes two requests but Leaflet only consumes one,
16122	// polluting _freshTiles with stale data.
16123	_removeTile: function (key) {
16124		if (!this._mutant) return;
16125
16126		//give time for animations to finish before checking it tile should be pruned
16127		setTimeout(this._pruneTile.bind(this, key), 1000);
16128
16129
16130		return L.GridLayer.prototype._removeTile.call(this, key);
16131	},
16132
16133	_pruneTile: function (key) {
16134		var gZoom = this._mutant.getZoom();
16135		var tileZoom = key.split(':')[2];
16136		var googleBounds = this._mutant.getBounds();
16137		var sw = googleBounds.getSouthWest();
16138		var ne = googleBounds.getNorthEast();
16139		var gMapBounds = L.latLngBounds([[sw.lat(), sw.lng()], [ne.lat(), ne.lng()]]);
16140
16141		for (var i=0; i<this._imagesPerTile; i++) {
16142			var key2 = key + '/' + i;
16143			if (key2 in this._freshTiles) { 
16144				var tileBounds = this._map && this._keyToBounds(key);
16145				var stillVisible = this._map && tileBounds.overlaps(gMapBounds) && (tileZoom == gZoom);
16146
16147				if (!stillVisible) delete this._freshTiles[key2]; 
16148//				console.log('Prunning of ', key, (!stillVisible))
16149			}
16150		}
16151	}
16152});
16153
16154
16155// 🍂factory gridLayer.googleMutant(options)
16156// Returns a new `GridLayer.GoogleMutant` given its options
16157L.gridLayer.googleMutant = function (options) {
16158	return new L.GridLayer.GoogleMutant(options);
16159};

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.