View | Details | Raw Unified | Return to bug 18066
Collapse All | Expand All

(-)a/koha-tmpl/intranet-tmpl/lib/leaflet/leaflet-src.js (+13251 lines)
Line 0 Link Here
1
/*
2
 Leaflet 1.0.3+ed36a04, 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) {
6
var L = {
7
	version: "1.0.3+ed36a04"
8
};
9
10
function 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
22
if (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
31
if (typeof window !== 'undefined') {
32
	expose();
33
}
34
35
36
37
/*
38
 * @namespace Util
39
 *
40
 * Various utility functions, used by Leaflet internally.
41
 */
42
43
L.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
283
L.extend = L.Util.extend;
284
L.bind = L.Util.bind;
285
L.stamp = L.Util.stamp;
286
L.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
299
L.Class = function () {};
300
301
L.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.
375
L.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.
382
L.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.
389
L.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
429
L.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
	},
520
521
	_off: function (type, fn, context) {
522
		var listeners,
523
		    i,
524
		    len;
525
526
		if (!this._events) { return; }
527
528
		listeners = this._events[type];
529
530
		if (!listeners) {
531
			return;
532
		}
533
534
		if (!fn) {
535
			// Set all removed listeners to noop so they are not called if remove happens in fire
536
			for (i = 0, len = listeners.length; i < len; i++) {
537
				listeners[i].fn = L.Util.falseFn;
538
			}
539
			// clear all listeners for a type if function isn't specified
540
			delete this._events[type];
541
			return;
542
		}
543
544
		if (context === this) {
545
			context = undefined;
546
		}
547
548
		if (listeners) {
549
550
			// find fn and remove it
551
			for (i = 0, len = listeners.length; i < len; i++) {
552
				var l = listeners[i];
553
				if (l.ctx !== context) { continue; }
554
				if (l.fn === fn) {
555
556
					// set the removed listener to noop so that's not called if remove happens in fire
557
					l.fn = L.Util.falseFn;
558
559
					if (this._firingCount) {
560
						/* copy array in case events are being fired */
561
						this._events[type] = listeners = listeners.slice();
562
					}
563
					listeners.splice(i, 1);
564
565
					return;
566
				}
567
			}
568
		}
569
	},
570
571
	// @method fire(type: String, data?: Object, propagate?: Boolean): this
572
	// Fires an event of the specified type. You can optionally provide an data
573
	// object — the first argument of the listener function will contain its
574
	// properties. The event can optionally be propagated to event parents.
575
	fire: function (type, data, propagate) {
576
		if (!this.listens(type, propagate)) { return this; }
577
578
		var event = L.Util.extend({}, data, {type: type, target: this});
579
580
		if (this._events) {
581
			var listeners = this._events[type];
582
583
			if (listeners) {
584
				this._firingCount = (this._firingCount + 1) || 1;
585
				for (var i = 0, len = listeners.length; i < len; i++) {
586
					var l = listeners[i];
587
					l.fn.call(l.ctx || this, event);
588
				}
589
590
				this._firingCount--;
591
			}
592
		}
593
594
		if (propagate) {
595
			// propagate the event to parents (set with addEventParent)
596
			this._propagateEvent(event);
597
		}
598
599
		return this;
600
	},
601
602
	// @method listens(type: String): Boolean
603
	// Returns `true` if a particular event type has any listeners attached to it.
604
	listens: function (type, propagate) {
605
		var listeners = this._events && this._events[type];
606
		if (listeners && listeners.length) { return true; }
607
608
		if (propagate) {
609
			// also check parents for listeners if event propagates
610
			for (var id in this._eventParents) {
611
				if (this._eventParents[id].listens(type, propagate)) { return true; }
612
			}
613
		}
614
		return false;
615
	},
616
617
	// @method once(…): this
618
	// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
619
	once: function (types, fn, context) {
620
621
		if (typeof types === 'object') {
622
			for (var type in types) {
623
				this.once(type, types[type], fn);
624
			}
625
			return this;
626
		}
627
628
		var handler = L.bind(function () {
629
			this
630
			    .off(types, fn, context)
631
			    .off(types, handler, context);
632
		}, this);
633
634
		// add a listener that's executed once and removed after that
635
		return this
636
		    .on(types, fn, context)
637
		    .on(types, handler, context);
638
	},
639
640
	// @method addEventParent(obj: Evented): this
641
	// Adds an event parent - an `Evented` that will receive propagated events
642
	addEventParent: function (obj) {
643
		this._eventParents = this._eventParents || {};
644
		this._eventParents[L.stamp(obj)] = obj;
645
		return this;
646
	},
647
648
	// @method removeEventParent(obj: Evented): this
649
	// Removes an event parent, so it will stop receiving propagated events
650
	removeEventParent: function (obj) {
651
		if (this._eventParents) {
652
			delete this._eventParents[L.stamp(obj)];
653
		}
654
		return this;
655
	},
656
657
	_propagateEvent: function (e) {
658
		for (var id in this._eventParents) {
659
			this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
660
		}
661
	}
662
});
663
664
var proto = L.Evented.prototype;
665
666
// aliases; we should ditch those eventually
667
668
// @method addEventListener(…): this
669
// Alias to [`on(…)`](#evented-on)
670
proto.addEventListener = proto.on;
671
672
// @method removeEventListener(…): this
673
// Alias to [`off(…)`](#evented-off)
674
675
// @method clearAllEventListeners(…): this
676
// Alias to [`off()`](#evented-off)
677
proto.removeEventListener = proto.clearAllEventListeners = proto.off;
678
679
// @method addOneTimeEventListener(…): this
680
// Alias to [`once(…)`](#evented-once)
681
proto.addOneTimeEventListener = proto.once;
682
683
// @method fireEvent(…): this
684
// Alias to [`fire(…)`](#evented-fire)
685
proto.fireEvent = proto.fire;
686
687
// @method hasEventListeners(…): Boolean
688
// Alias to [`listens(…)`](#evented-listens)
689
proto.hasEventListeners = proto.listens;
690
691
L.Mixin = {Events: proto};
692
693
694
695
/*
696
 * @namespace Browser
697
 * @aka L.Browser
698
 *
699
 * A namespace with static properties for browser/feature detection used by Leaflet internally.
700
 *
701
 * @example
702
 *
703
 * ```js
704
 * if (L.Browser.ielt9) {
705
 *   alert('Upgrade your browser, dude!');
706
 * }
707
 * ```
708
 */
709
710
(function () {
711
712
	var ua = navigator.userAgent.toLowerCase(),
713
	    doc = document.documentElement,
714
715
	    ie = 'ActiveXObject' in window,
716
717
	    webkit    = ua.indexOf('webkit') !== -1,
718
	    phantomjs = ua.indexOf('phantom') !== -1,
719
	    android23 = ua.search('android [23]') !== -1,
720
	    chrome    = ua.indexOf('chrome') !== -1,
721
	    gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,
722
723
	    win = navigator.platform.indexOf('Win') === 0,
724
725
	    mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
726
	    msPointer = !window.PointerEvent && window.MSPointerEvent,
727
	    pointer = window.PointerEvent || msPointer,
728
729
	    ie3d = ie && ('transition' in doc.style),
730
	    webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
731
	    gecko3d = 'MozPerspective' in doc.style,
732
	    opera12 = 'OTransition' in doc.style;
733
734
735
	var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
736
			(window.DocumentTouch && document instanceof window.DocumentTouch));
737
738
	L.Browser = {
739
740
		// @property ie: Boolean
741
		// `true` for all Internet Explorer versions (not Edge).
742
		ie: ie,
743
744
		// @property ielt9: Boolean
745
		// `true` for Internet Explorer versions less than 9.
746
		ielt9: ie && !document.addEventListener,
747
748
		// @property edge: Boolean
749
		// `true` for the Edge web browser.
750
		edge: 'msLaunchUri' in navigator && !('documentMode' in document),
751
752
		// @property webkit: Boolean
753
		// `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
754
		webkit: webkit,
755
756
		// @property gecko: Boolean
757
		// `true` for gecko-based browsers like Firefox.
758
		gecko: gecko,
759
760
		// @property android: Boolean
761
		// `true` for any browser running on an Android platform.
762
		android: ua.indexOf('android') !== -1,
763
764
		// @property android23: Boolean
765
		// `true` for browsers running on Android 2 or Android 3.
766
		android23: android23,
767
768
		// @property chrome: Boolean
769
		// `true` for the Chrome browser.
770
		chrome: chrome,
771
772
		// @property safari: Boolean
773
		// `true` for the Safari browser.
774
		safari: !chrome && ua.indexOf('safari') !== -1,
775
776
777
		// @property win: Boolean
778
		// `true` when the browser is running in a Windows platform
779
		win: win,
780
781
782
		// @property ie3d: Boolean
783
		// `true` for all Internet Explorer versions supporting CSS transforms.
784
		ie3d: ie3d,
785
786
		// @property webkit3d: Boolean
787
		// `true` for webkit-based browsers supporting CSS transforms.
788
		webkit3d: webkit3d,
789
790
		// @property gecko3d: Boolean
791
		// `true` for gecko-based browsers supporting CSS transforms.
792
		gecko3d: gecko3d,
793
794
		// @property opera12: Boolean
795
		// `true` for the Opera browser supporting CSS transforms (version 12 or later).
796
		opera12: opera12,
797
798
		// @property any3d: Boolean
799
		// `true` for all browsers supporting CSS transforms.
800
		any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
801
802
803
		// @property mobile: Boolean
804
		// `true` for all browsers running in a mobile device.
805
		mobile: mobile,
806
807
		// @property mobileWebkit: Boolean
808
		// `true` for all webkit-based browsers in a mobile device.
809
		mobileWebkit: mobile && webkit,
810
811
		// @property mobileWebkit3d: Boolean
812
		// `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
813
		mobileWebkit3d: mobile && webkit3d,
814
815
		// @property mobileOpera: Boolean
816
		// `true` for the Opera browser in a mobile device.
817
		mobileOpera: mobile && window.opera,
818
819
		// @property mobileGecko: Boolean
820
		// `true` for gecko-based browsers running in a mobile device.
821
		mobileGecko: mobile && gecko,
822
823
824
		// @property touch: Boolean
825
		// `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
826
		// This does not necessarily mean that the browser is running in a computer with
827
		// a touchscreen, it only means that the browser is capable of understanding
828
		// touch events.
829
		touch: !!touch,
830
831
		// @property msPointer: Boolean
832
		// `true` for browsers implementing the Microsoft touch events model (notably IE10).
833
		msPointer: !!msPointer,
834
835
		// @property pointer: Boolean
836
		// `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
837
		pointer: !!pointer,
838
839
840
		// @property retina: Boolean
841
		// `true` for browsers on a high-resolution "retina" screen.
842
		retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
843
	};
844
845
}());
846
847
848
849
/*
850
 * @class Point
851
 * @aka L.Point
852
 *
853
 * Represents a point with `x` and `y` coordinates in pixels.
854
 *
855
 * @example
856
 *
857
 * ```js
858
 * var point = L.point(200, 300);
859
 * ```
860
 *
861
 * 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:
862
 *
863
 * ```js
864
 * map.panBy([200, 300]);
865
 * map.panBy(L.point(200, 300));
866
 * ```
867
 */
868
869
L.Point = function (x, y, round) {
870
	// @property x: Number; The `x` coordinate of the point
871
	this.x = (round ? Math.round(x) : x);
872
	// @property y: Number; The `y` coordinate of the point
873
	this.y = (round ? Math.round(y) : y);
874
};
875
876
L.Point.prototype = {
877
878
	// @method clone(): Point
879
	// Returns a copy of the current point.
880
	clone: function () {
881
		return new L.Point(this.x, this.y);
882
	},
883
884
	// @method add(otherPoint: Point): Point
885
	// Returns the result of addition of the current and the given points.
886
	add: function (point) {
887
		// non-destructive, returns a new point
888
		return this.clone()._add(L.point(point));
889
	},
890
891
	_add: function (point) {
892
		// destructive, used directly for performance in situations where it's safe to modify existing point
893
		this.x += point.x;
894
		this.y += point.y;
895
		return this;
896
	},
897
898
	// @method subtract(otherPoint: Point): Point
899
	// Returns the result of subtraction of the given point from the current.
900
	subtract: function (point) {
901
		return this.clone()._subtract(L.point(point));
902
	},
903
904
	_subtract: function (point) {
905
		this.x -= point.x;
906
		this.y -= point.y;
907
		return this;
908
	},
909
910
	// @method divideBy(num: Number): Point
911
	// Returns the result of division of the current point by the given number.
912
	divideBy: function (num) {
913
		return this.clone()._divideBy(num);
914
	},
915
916
	_divideBy: function (num) {
917
		this.x /= num;
918
		this.y /= num;
919
		return this;
920
	},
921
922
	// @method multiplyBy(num: Number): Point
923
	// Returns the result of multiplication of the current point by the given number.
924
	multiplyBy: function (num) {
925
		return this.clone()._multiplyBy(num);
926
	},
927
928
	_multiplyBy: function (num) {
929
		this.x *= num;
930
		this.y *= num;
931
		return this;
932
	},
933
934
	// @method scaleBy(scale: Point): Point
935
	// Multiply each coordinate of the current point by each coordinate of
936
	// `scale`. In linear algebra terms, multiply the point by the
937
	// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
938
	// defined by `scale`.
939
	scaleBy: function (point) {
940
		return new L.Point(this.x * point.x, this.y * point.y);
941
	},
942
943
	// @method unscaleBy(scale: Point): Point
944
	// Inverse of `scaleBy`. Divide each coordinate of the current point by
945
	// each coordinate of `scale`.
946
	unscaleBy: function (point) {
947
		return new L.Point(this.x / point.x, this.y / point.y);
948
	},
949
950
	// @method round(): Point
951
	// Returns a copy of the current point with rounded coordinates.
952
	round: function () {
953
		return this.clone()._round();
954
	},
955
956
	_round: function () {
957
		this.x = Math.round(this.x);
958
		this.y = Math.round(this.y);
959
		return this;
960
	},
961
962
	// @method floor(): Point
963
	// Returns a copy of the current point with floored coordinates (rounded down).
964
	floor: function () {
965
		return this.clone()._floor();
966
	},
967
968
	_floor: function () {
969
		this.x = Math.floor(this.x);
970
		this.y = Math.floor(this.y);
971
		return this;
972
	},
973
974
	// @method ceil(): Point
975
	// Returns a copy of the current point with ceiled coordinates (rounded up).
976
	ceil: function () {
977
		return this.clone()._ceil();
978
	},
979
980
	_ceil: function () {
981
		this.x = Math.ceil(this.x);
982
		this.y = Math.ceil(this.y);
983
		return this;
984
	},
985
986
	// @method distanceTo(otherPoint: Point): Number
987
	// Returns the cartesian distance between the current and the given points.
988
	distanceTo: function (point) {
989
		point = L.point(point);
990
991
		var x = point.x - this.x,
992
		    y = point.y - this.y;
993
994
		return Math.sqrt(x * x + y * y);
995
	},
996
997
	// @method equals(otherPoint: Point): Boolean
998
	// Returns `true` if the given point has the same coordinates.
999
	equals: function (point) {
1000
		point = L.point(point);
1001
1002
		return point.x === this.x &&
1003
		       point.y === this.y;
1004
	},
1005
1006
	// @method contains(otherPoint: Point): Boolean
1007
	// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1008
	contains: function (point) {
1009
		point = L.point(point);
1010
1011
		return Math.abs(point.x) <= Math.abs(this.x) &&
1012
		       Math.abs(point.y) <= Math.abs(this.y);
1013
	},
1014
1015
	// @method toString(): String
1016
	// Returns a string representation of the point for debugging purposes.
1017
	toString: function () {
1018
		return 'Point(' +
1019
		        L.Util.formatNum(this.x) + ', ' +
1020
		        L.Util.formatNum(this.y) + ')';
1021
	}
1022
};
1023
1024
// @factory L.point(x: Number, y: Number, round?: Boolean)
1025
// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1026
1027
// @alternative
1028
// @factory L.point(coords: Number[])
1029
// Expects an array of the form `[x, y]` instead.
1030
1031
// @alternative
1032
// @factory L.point(coords: Object)
1033
// Expects a plain object of the form `{x: Number, y: Number}` instead.
1034
L.point = function (x, y, round) {
1035
	if (x instanceof L.Point) {
1036
		return x;
1037
	}
1038
	if (L.Util.isArray(x)) {
1039
		return new L.Point(x[0], x[1]);
1040
	}
1041
	if (x === undefined || x === null) {
1042
		return x;
1043
	}
1044
	if (typeof x === 'object' && 'x' in x && 'y' in x) {
1045
		return new L.Point(x.x, x.y);
1046
	}
1047
	return new L.Point(x, y, round);
1048
};
1049
1050
1051
1052
/*
1053
 * @class Bounds
1054
 * @aka L.Bounds
1055
 *
1056
 * Represents a rectangular area in pixel coordinates.
1057
 *
1058
 * @example
1059
 *
1060
 * ```js
1061
 * var p1 = L.point(10, 10),
1062
 * p2 = L.point(40, 60),
1063
 * bounds = L.bounds(p1, p2);
1064
 * ```
1065
 *
1066
 * 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:
1067
 *
1068
 * ```js
1069
 * otherBounds.intersects([[10, 10], [40, 60]]);
1070
 * ```
1071
 */
1072
1073
L.Bounds = function (a, b) {
1074
	if (!a) { return; }
1075
1076
	var points = b ? [a, b] : a;
1077
1078
	for (var i = 0, len = points.length; i < len; i++) {
1079
		this.extend(points[i]);
1080
	}
1081
};
1082
1083
L.Bounds.prototype = {
1084
	// @method extend(point: Point): this
1085
	// Extends the bounds to contain the given point.
1086
	extend: function (point) { // (Point)
1087
		point = L.point(point);
1088
1089
		// @property min: Point
1090
		// The top left corner of the rectangle.
1091
		// @property max: Point
1092
		// The bottom right corner of the rectangle.
1093
		if (!this.min && !this.max) {
1094
			this.min = point.clone();
1095
			this.max = point.clone();
1096
		} else {
1097
			this.min.x = Math.min(point.x, this.min.x);
1098
			this.max.x = Math.max(point.x, this.max.x);
1099
			this.min.y = Math.min(point.y, this.min.y);
1100
			this.max.y = Math.max(point.y, this.max.y);
1101
		}
1102
		return this;
1103
	},
1104
1105
	// @method getCenter(round?: Boolean): Point
1106
	// Returns the center point of the bounds.
1107
	getCenter: function (round) {
1108
		return new L.Point(
1109
		        (this.min.x + this.max.x) / 2,
1110
		        (this.min.y + this.max.y) / 2, round);
1111
	},
1112
1113
	// @method getBottomLeft(): Point
1114
	// Returns the bottom-left point of the bounds.
1115
	getBottomLeft: function () {
1116
		return new L.Point(this.min.x, this.max.y);
1117
	},
1118
1119
	// @method getTopRight(): Point
1120
	// Returns the top-right point of the bounds.
1121
	getTopRight: function () { // -> Point
1122
		return new L.Point(this.max.x, this.min.y);
1123
	},
1124
1125
	// @method getSize(): Point
1126
	// Returns the size of the given bounds
1127
	getSize: function () {
1128
		return this.max.subtract(this.min);
1129
	},
1130
1131
	// @method contains(otherBounds: Bounds): Boolean
1132
	// Returns `true` if the rectangle contains the given one.
1133
	// @alternative
1134
	// @method contains(point: Point): Boolean
1135
	// Returns `true` if the rectangle contains the given point.
1136
	contains: function (obj) {
1137
		var min, max;
1138
1139
		if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1140
			obj = L.point(obj);
1141
		} else {
1142
			obj = L.bounds(obj);
1143
		}
1144
1145
		if (obj instanceof L.Bounds) {
1146
			min = obj.min;
1147
			max = obj.max;
1148
		} else {
1149
			min = max = obj;
1150
		}
1151
1152
		return (min.x >= this.min.x) &&
1153
		       (max.x <= this.max.x) &&
1154
		       (min.y >= this.min.y) &&
1155
		       (max.y <= this.max.y);
1156
	},
1157
1158
	// @method intersects(otherBounds: Bounds): Boolean
1159
	// Returns `true` if the rectangle intersects the given bounds. Two bounds
1160
	// intersect if they have at least one point in common.
1161
	intersects: function (bounds) { // (Bounds) -> Boolean
1162
		bounds = L.bounds(bounds);
1163
1164
		var min = this.min,
1165
		    max = this.max,
1166
		    min2 = bounds.min,
1167
		    max2 = bounds.max,
1168
		    xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1169
		    yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1170
1171
		return xIntersects && yIntersects;
1172
	},
1173
1174
	// @method overlaps(otherBounds: Bounds): Boolean
1175
	// Returns `true` if the rectangle overlaps the given bounds. Two bounds
1176
	// overlap if their intersection is an area.
1177
	overlaps: function (bounds) { // (Bounds) -> Boolean
1178
		bounds = L.bounds(bounds);
1179
1180
		var min = this.min,
1181
		    max = this.max,
1182
		    min2 = bounds.min,
1183
		    max2 = bounds.max,
1184
		    xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1185
		    yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1186
1187
		return xOverlaps && yOverlaps;
1188
	},
1189
1190
	isValid: function () {
1191
		return !!(this.min && this.max);
1192
	}
1193
};
1194
1195
1196
// @factory L.bounds(topLeft: Point, bottomRight: Point)
1197
// Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1198
// @alternative
1199
// @factory L.bounds(points: Point[])
1200
// Creates a Bounds object from the points it contains
1201
L.bounds = function (a, b) {
1202
	if (!a || a instanceof L.Bounds) {
1203
		return a;
1204
	}
1205
	return new L.Bounds(a, b);
1206
};
1207
1208
1209
1210
/*
1211
 * @class Transformation
1212
 * @aka L.Transformation
1213
 *
1214
 * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1215
 * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1216
 * the reverse. Used by Leaflet in its projections code.
1217
 *
1218
 * @example
1219
 *
1220
 * ```js
1221
 * var transformation = new L.Transformation(2, 5, -1, 10),
1222
 * 	p = L.point(1, 2),
1223
 * 	p2 = transformation.transform(p), //  L.point(7, 8)
1224
 * 	p3 = transformation.untransform(p2); //  L.point(1, 2)
1225
 * ```
1226
 */
1227
1228
1229
// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1230
// Creates a `Transformation` object with the given coefficients.
1231
L.Transformation = function (a, b, c, d) {
1232
	this._a = a;
1233
	this._b = b;
1234
	this._c = c;
1235
	this._d = d;
1236
};
1237
1238
L.Transformation.prototype = {
1239
	// @method transform(point: Point, scale?: Number): Point
1240
	// Returns a transformed point, optionally multiplied by the given scale.
1241
	// Only accepts actual `L.Point` instances, not arrays.
1242
	transform: function (point, scale) { // (Point, Number) -> Point
1243
		return this._transform(point.clone(), scale);
1244
	},
1245
1246
	// destructive transform (faster)
1247
	_transform: function (point, scale) {
1248
		scale = scale || 1;
1249
		point.x = scale * (this._a * point.x + this._b);
1250
		point.y = scale * (this._c * point.y + this._d);
1251
		return point;
1252
	},
1253
1254
	// @method untransform(point: Point, scale?: Number): Point
1255
	// Returns the reverse transformation of the given point, optionally divided
1256
	// by the given scale. Only accepts actual `L.Point` instances, not arrays.
1257
	untransform: function (point, scale) {
1258
		scale = scale || 1;
1259
		return new L.Point(
1260
		        (point.x / scale - this._b) / this._a,
1261
		        (point.y / scale - this._d) / this._c);
1262
	}
1263
};
1264
1265
1266
1267
/*
1268
 * @namespace DomUtil
1269
 *
1270
 * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1271
 * tree, used by Leaflet internally.
1272
 *
1273
 * Most functions expecting or returning a `HTMLElement` also work for
1274
 * SVG elements. The only difference is that classes refer to CSS classes
1275
 * in HTML and SVG classes in SVG.
1276
 */
1277
1278
L.DomUtil = {
1279
1280
	// @function get(id: String|HTMLElement): HTMLElement
1281
	// Returns an element given its DOM id, or returns the element itself
1282
	// if it was passed directly.
1283
	get: function (id) {
1284
		return typeof id === 'string' ? document.getElementById(id) : id;
1285
	},
1286
1287
	// @function getStyle(el: HTMLElement, styleAttrib: String): String
1288
	// Returns the value for a certain style attribute on an element,
1289
	// including computed values or values set through CSS.
1290
	getStyle: function (el, style) {
1291
1292
		var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1293
1294
		if ((!value || value === 'auto') && document.defaultView) {
1295
			var css = document.defaultView.getComputedStyle(el, null);
1296
			value = css ? css[style] : null;
1297
		}
1298
1299
		return value === 'auto' ? null : value;
1300
	},
1301
1302
	// @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1303
	// Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1304
	create: function (tagName, className, container) {
1305
1306
		var el = document.createElement(tagName);
1307
		el.className = className || '';
1308
1309
		if (container) {
1310
			container.appendChild(el);
1311
		}
1312
1313
		return el;
1314
	},
1315
1316
	// @function remove(el: HTMLElement)
1317
	// Removes `el` from its parent element
1318
	remove: function (el) {
1319
		var parent = el.parentNode;
1320
		if (parent) {
1321
			parent.removeChild(el);
1322
		}
1323
	},
1324
1325
	// @function empty(el: HTMLElement)
1326
	// Removes all of `el`'s children elements from `el`
1327
	empty: function (el) {
1328
		while (el.firstChild) {
1329
			el.removeChild(el.firstChild);
1330
		}
1331
	},
1332
1333
	// @function toFront(el: HTMLElement)
1334
	// Makes `el` the last children of its parent, so it renders in front of the other children.
1335
	toFront: function (el) {
1336
		el.parentNode.appendChild(el);
1337
	},
1338
1339
	// @function toBack(el: HTMLElement)
1340
	// Makes `el` the first children of its parent, so it renders back from the other children.
1341
	toBack: function (el) {
1342
		var parent = el.parentNode;
1343
		parent.insertBefore(el, parent.firstChild);
1344
	},
1345
1346
	// @function hasClass(el: HTMLElement, name: String): Boolean
1347
	// Returns `true` if the element's class attribute contains `name`.
1348
	hasClass: function (el, name) {
1349
		if (el.classList !== undefined) {
1350
			return el.classList.contains(name);
1351
		}
1352
		var className = L.DomUtil.getClass(el);
1353
		return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1354
	},
1355
1356
	// @function addClass(el: HTMLElement, name: String)
1357
	// Adds `name` to the element's class attribute.
1358
	addClass: function (el, name) {
1359
		if (el.classList !== undefined) {
1360
			var classes = L.Util.splitWords(name);
1361
			for (var i = 0, len = classes.length; i < len; i++) {
1362
				el.classList.add(classes[i]);
1363
			}
1364
		} else if (!L.DomUtil.hasClass(el, name)) {
1365
			var className = L.DomUtil.getClass(el);
1366
			L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1367
		}
1368
	},
1369
1370
	// @function removeClass(el: HTMLElement, name: String)
1371
	// Removes `name` from the element's class attribute.
1372
	removeClass: function (el, name) {
1373
		if (el.classList !== undefined) {
1374
			el.classList.remove(name);
1375
		} else {
1376
			L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1377
		}
1378
	},
1379
1380
	// @function setClass(el: HTMLElement, name: String)
1381
	// Sets the element's class.
1382
	setClass: function (el, name) {
1383
		if (el.className.baseVal === undefined) {
1384
			el.className = name;
1385
		} else {
1386
			// in case of SVG element
1387
			el.className.baseVal = name;
1388
		}
1389
	},
1390
1391
	// @function getClass(el: HTMLElement): String
1392
	// Returns the element's class.
1393
	getClass: function (el) {
1394
		return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1395
	},
1396
1397
	// @function setOpacity(el: HTMLElement, opacity: Number)
1398
	// Set the opacity of an element (including old IE support).
1399
	// `opacity` must be a number from `0` to `1`.
1400
	setOpacity: function (el, value) {
1401
1402
		if ('opacity' in el.style) {
1403
			el.style.opacity = value;
1404
1405
		} else if ('filter' in el.style) {
1406
			L.DomUtil._setOpacityIE(el, value);
1407
		}
1408
	},
1409
1410
	_setOpacityIE: function (el, value) {
1411
		var filter = false,
1412
		    filterName = 'DXImageTransform.Microsoft.Alpha';
1413
1414
		// filters collection throws an error if we try to retrieve a filter that doesn't exist
1415
		try {
1416
			filter = el.filters.item(filterName);
1417
		} catch (e) {
1418
			// don't set opacity to 1 if we haven't already set an opacity,
1419
			// it isn't needed and breaks transparent pngs.
1420
			if (value === 1) { return; }
1421
		}
1422
1423
		value = Math.round(value * 100);
1424
1425
		if (filter) {
1426
			filter.Enabled = (value !== 100);
1427
			filter.Opacity = value;
1428
		} else {
1429
			el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1430
		}
1431
	},
1432
1433
	// @function testProp(props: String[]): String|false
1434
	// Goes through the array of style names and returns the first name
1435
	// that is a valid style name for an element. If no such name is found,
1436
	// it returns false. Useful for vendor-prefixed styles like `transform`.
1437
	testProp: function (props) {
1438
1439
		var style = document.documentElement.style;
1440
1441
		for (var i = 0; i < props.length; i++) {
1442
			if (props[i] in style) {
1443
				return props[i];
1444
			}
1445
		}
1446
		return false;
1447
	},
1448
1449
	// @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1450
	// Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1451
	// and optionally scaled by `scale`. Does not have an effect if the
1452
	// browser doesn't support 3D CSS transforms.
1453
	setTransform: function (el, offset, scale) {
1454
		var pos = offset || new L.Point(0, 0);
1455
1456
		el.style[L.DomUtil.TRANSFORM] =
1457
			(L.Browser.ie3d ?
1458
				'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1459
				'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1460
			(scale ? ' scale(' + scale + ')' : '');
1461
	},
1462
1463
	// @function setPosition(el: HTMLElement, position: Point)
1464
	// Sets the position of `el` to coordinates specified by `position`,
1465
	// using CSS translate or top/left positioning depending on the browser
1466
	// (used by Leaflet internally to position its layers).
1467
	setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1468
1469
		/*eslint-disable */
1470
		el._leaflet_pos = point;
1471
		/*eslint-enable */
1472
1473
		if (L.Browser.any3d) {
1474
			L.DomUtil.setTransform(el, point);
1475
		} else {
1476
			el.style.left = point.x + 'px';
1477
			el.style.top = point.y + 'px';
1478
		}
1479
	},
1480
1481
	// @function getPosition(el: HTMLElement): Point
1482
	// Returns the coordinates of an element previously positioned with setPosition.
1483
	getPosition: function (el) {
1484
		// this method is only used for elements previously positioned using setPosition,
1485
		// so it's safe to cache the position for performance
1486
1487
		return el._leaflet_pos || new L.Point(0, 0);
1488
	}
1489
};
1490
1491
1492
(function () {
1493
	// prefix style property names
1494
1495
	// @property TRANSFORM: String
1496
	// Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1497
	L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1498
			['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1499
1500
1501
	// webkitTransition comes first because some browser versions that drop vendor prefix don't do
1502
	// the same for the transitionend event, in particular the Android 4.1 stock browser
1503
1504
	// @property TRANSITION: String
1505
	// Vendor-prefixed transform style name.
1506
	var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1507
			['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1508
1509
	L.DomUtil.TRANSITION_END =
1510
			transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1511
1512
	// @function disableTextSelection()
1513
	// Prevents the user from generating `selectstart` DOM events, usually generated
1514
	// when the user drags the mouse through a page with text. Used internally
1515
	// by Leaflet to override the behaviour of any click-and-drag interaction on
1516
	// the map. Affects drag interactions on the whole document.
1517
1518
	// @function enableTextSelection()
1519
	// Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1520
	if ('onselectstart' in document) {
1521
		L.DomUtil.disableTextSelection = function () {
1522
			L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1523
		};
1524
		L.DomUtil.enableTextSelection = function () {
1525
			L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1526
		};
1527
1528
	} else {
1529
		var userSelectProperty = L.DomUtil.testProp(
1530
			['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1531
1532
		L.DomUtil.disableTextSelection = function () {
1533
			if (userSelectProperty) {
1534
				var style = document.documentElement.style;
1535
				this._userSelect = style[userSelectProperty];
1536
				style[userSelectProperty] = 'none';
1537
			}
1538
		};
1539
		L.DomUtil.enableTextSelection = function () {
1540
			if (userSelectProperty) {
1541
				document.documentElement.style[userSelectProperty] = this._userSelect;
1542
				delete this._userSelect;
1543
			}
1544
		};
1545
	}
1546
1547
	// @function disableImageDrag()
1548
	// As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1549
	// for `dragstart` DOM events, usually generated when the user drags an image.
1550
	L.DomUtil.disableImageDrag = function () {
1551
		L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1552
	};
1553
1554
	// @function enableImageDrag()
1555
	// Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1556
	L.DomUtil.enableImageDrag = function () {
1557
		L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1558
	};
1559
1560
	// @function preventOutline(el: HTMLElement)
1561
	// Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1562
	// of the element `el` invisible. Used internally by Leaflet to prevent
1563
	// focusable elements from displaying an outline when the user performs a
1564
	// drag interaction on them.
1565
	L.DomUtil.preventOutline = function (element) {
1566
		while (element.tabIndex === -1) {
1567
			element = element.parentNode;
1568
		}
1569
		if (!element || !element.style) { return; }
1570
		L.DomUtil.restoreOutline();
1571
		this._outlineElement = element;
1572
		this._outlineStyle = element.style.outline;
1573
		element.style.outline = 'none';
1574
		L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1575
	};
1576
1577
	// @function restoreOutline()
1578
	// Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1579
	L.DomUtil.restoreOutline = function () {
1580
		if (!this._outlineElement) { return; }
1581
		this._outlineElement.style.outline = this._outlineStyle;
1582
		delete this._outlineElement;
1583
		delete this._outlineStyle;
1584
		L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1585
	};
1586
})();
1587
1588
1589
1590
/* @class LatLng
1591
 * @aka L.LatLng
1592
 *
1593
 * Represents a geographical point with a certain latitude and longitude.
1594
 *
1595
 * @example
1596
 *
1597
 * ```
1598
 * var latlng = L.latLng(50.5, 30.5);
1599
 * ```
1600
 *
1601
 * 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:
1602
 *
1603
 * ```
1604
 * map.panTo([50, 30]);
1605
 * map.panTo({lon: 30, lat: 50});
1606
 * map.panTo({lat: 50, lng: 30});
1607
 * map.panTo(L.latLng(50, 30));
1608
 * ```
1609
 */
1610
1611
L.LatLng = function (lat, lng, alt) {
1612
	if (isNaN(lat) || isNaN(lng)) {
1613
		throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1614
	}
1615
1616
	// @property lat: Number
1617
	// Latitude in degrees
1618
	this.lat = +lat;
1619
1620
	// @property lng: Number
1621
	// Longitude in degrees
1622
	this.lng = +lng;
1623
1624
	// @property alt: Number
1625
	// Altitude in meters (optional)
1626
	if (alt !== undefined) {
1627
		this.alt = +alt;
1628
	}
1629
};
1630
1631
L.LatLng.prototype = {
1632
	// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1633
	// 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.
1634
	equals: function (obj, maxMargin) {
1635
		if (!obj) { return false; }
1636
1637
		obj = L.latLng(obj);
1638
1639
		var margin = Math.max(
1640
		        Math.abs(this.lat - obj.lat),
1641
		        Math.abs(this.lng - obj.lng));
1642
1643
		return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1644
	},
1645
1646
	// @method toString(): String
1647
	// Returns a string representation of the point (for debugging purposes).
1648
	toString: function (precision) {
1649
		return 'LatLng(' +
1650
		        L.Util.formatNum(this.lat, precision) + ', ' +
1651
		        L.Util.formatNum(this.lng, precision) + ')';
1652
	},
1653
1654
	// @method distanceTo(otherLatLng: LatLng): Number
1655
	// Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1656
	distanceTo: function (other) {
1657
		return L.CRS.Earth.distance(this, L.latLng(other));
1658
	},
1659
1660
	// @method wrap(): LatLng
1661
	// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1662
	wrap: function () {
1663
		return L.CRS.Earth.wrapLatLng(this);
1664
	},
1665
1666
	// @method toBounds(sizeInMeters: Number): LatLngBounds
1667
	// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1668
	toBounds: function (sizeInMeters) {
1669
		var latAccuracy = 180 * sizeInMeters / 40075017,
1670
		    lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1671
1672
		return L.latLngBounds(
1673
		        [this.lat - latAccuracy, this.lng - lngAccuracy],
1674
		        [this.lat + latAccuracy, this.lng + lngAccuracy]);
1675
	},
1676
1677
	clone: function () {
1678
		return new L.LatLng(this.lat, this.lng, this.alt);
1679
	}
1680
};
1681
1682
1683
1684
// @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1685
// Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1686
1687
// @alternative
1688
// @factory L.latLng(coords: Array): LatLng
1689
// Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1690
1691
// @alternative
1692
// @factory L.latLng(coords: Object): LatLng
1693
// Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1694
1695
L.latLng = function (a, b, c) {
1696
	if (a instanceof L.LatLng) {
1697
		return a;
1698
	}
1699
	if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1700
		if (a.length === 3) {
1701
			return new L.LatLng(a[0], a[1], a[2]);
1702
		}
1703
		if (a.length === 2) {
1704
			return new L.LatLng(a[0], a[1]);
1705
		}
1706
		return null;
1707
	}
1708
	if (a === undefined || a === null) {
1709
		return a;
1710
	}
1711
	if (typeof a === 'object' && 'lat' in a) {
1712
		return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1713
	}
1714
	if (b === undefined) {
1715
		return null;
1716
	}
1717
	return new L.LatLng(a, b, c);
1718
};
1719
1720
1721
1722
/*
1723
 * @class LatLngBounds
1724
 * @aka L.LatLngBounds
1725
 *
1726
 * Represents a rectangular geographical area on a map.
1727
 *
1728
 * @example
1729
 *
1730
 * ```js
1731
 * var corner1 = L.latLng(40.712, -74.227),
1732
 * corner2 = L.latLng(40.774, -74.125),
1733
 * bounds = L.latLngBounds(corner1, corner2);
1734
 * ```
1735
 *
1736
 * 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:
1737
 *
1738
 * ```js
1739
 * map.fitBounds([
1740
 * 	[40.712, -74.227],
1741
 * 	[40.774, -74.125]
1742
 * ]);
1743
 * ```
1744
 *
1745
 * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
1746
 */
1747
1748
L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1749
	if (!corner1) { return; }
1750
1751
	var latlngs = corner2 ? [corner1, corner2] : corner1;
1752
1753
	for (var i = 0, len = latlngs.length; i < len; i++) {
1754
		this.extend(latlngs[i]);
1755
	}
1756
};
1757
1758
L.LatLngBounds.prototype = {
1759
1760
	// @method extend(latlng: LatLng): this
1761
	// Extend the bounds to contain the given point
1762
1763
	// @alternative
1764
	// @method extend(otherBounds: LatLngBounds): this
1765
	// Extend the bounds to contain the given bounds
1766
	extend: function (obj) {
1767
		var sw = this._southWest,
1768
		    ne = this._northEast,
1769
		    sw2, ne2;
1770
1771
		if (obj instanceof L.LatLng) {
1772
			sw2 = obj;
1773
			ne2 = obj;
1774
1775
		} else if (obj instanceof L.LatLngBounds) {
1776
			sw2 = obj._southWest;
1777
			ne2 = obj._northEast;
1778
1779
			if (!sw2 || !ne2) { return this; }
1780
1781
		} else {
1782
			return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1783
		}
1784
1785
		if (!sw && !ne) {
1786
			this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1787
			this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1788
		} else {
1789
			sw.lat = Math.min(sw2.lat, sw.lat);
1790
			sw.lng = Math.min(sw2.lng, sw.lng);
1791
			ne.lat = Math.max(ne2.lat, ne.lat);
1792
			ne.lng = Math.max(ne2.lng, ne.lng);
1793
		}
1794
1795
		return this;
1796
	},
1797
1798
	// @method pad(bufferRatio: Number): LatLngBounds
1799
	// Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1800
	pad: function (bufferRatio) {
1801
		var sw = this._southWest,
1802
		    ne = this._northEast,
1803
		    heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1804
		    widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1805
1806
		return new L.LatLngBounds(
1807
		        new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1808
		        new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1809
	},
1810
1811
	// @method getCenter(): LatLng
1812
	// Returns the center point of the bounds.
1813
	getCenter: function () {
1814
		return new L.LatLng(
1815
		        (this._southWest.lat + this._northEast.lat) / 2,
1816
		        (this._southWest.lng + this._northEast.lng) / 2);
1817
	},
1818
1819
	// @method getSouthWest(): LatLng
1820
	// Returns the south-west point of the bounds.
1821
	getSouthWest: function () {
1822
		return this._southWest;
1823
	},
1824
1825
	// @method getNorthEast(): LatLng
1826
	// Returns the north-east point of the bounds.
1827
	getNorthEast: function () {
1828
		return this._northEast;
1829
	},
1830
1831
	// @method getNorthWest(): LatLng
1832
	// Returns the north-west point of the bounds.
1833
	getNorthWest: function () {
1834
		return new L.LatLng(this.getNorth(), this.getWest());
1835
	},
1836
1837
	// @method getSouthEast(): LatLng
1838
	// Returns the south-east point of the bounds.
1839
	getSouthEast: function () {
1840
		return new L.LatLng(this.getSouth(), this.getEast());
1841
	},
1842
1843
	// @method getWest(): Number
1844
	// Returns the west longitude of the bounds
1845
	getWest: function () {
1846
		return this._southWest.lng;
1847
	},
1848
1849
	// @method getSouth(): Number
1850
	// Returns the south latitude of the bounds
1851
	getSouth: function () {
1852
		return this._southWest.lat;
1853
	},
1854
1855
	// @method getEast(): Number
1856
	// Returns the east longitude of the bounds
1857
	getEast: function () {
1858
		return this._northEast.lng;
1859
	},
1860
1861
	// @method getNorth(): Number
1862
	// Returns the north latitude of the bounds
1863
	getNorth: function () {
1864
		return this._northEast.lat;
1865
	},
1866
1867
	// @method contains(otherBounds: LatLngBounds): Boolean
1868
	// Returns `true` if the rectangle contains the given one.
1869
1870
	// @alternative
1871
	// @method contains (latlng: LatLng): Boolean
1872
	// Returns `true` if the rectangle contains the given point.
1873
	contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1874
		if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
1875
			obj = L.latLng(obj);
1876
		} else {
1877
			obj = L.latLngBounds(obj);
1878
		}
1879
1880
		var sw = this._southWest,
1881
		    ne = this._northEast,
1882
		    sw2, ne2;
1883
1884
		if (obj instanceof L.LatLngBounds) {
1885
			sw2 = obj.getSouthWest();
1886
			ne2 = obj.getNorthEast();
1887
		} else {
1888
			sw2 = ne2 = obj;
1889
		}
1890
1891
		return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1892
		       (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1893
	},
1894
1895
	// @method intersects(otherBounds: LatLngBounds): Boolean
1896
	// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1897
	intersects: function (bounds) {
1898
		bounds = L.latLngBounds(bounds);
1899
1900
		var sw = this._southWest,
1901
		    ne = this._northEast,
1902
		    sw2 = bounds.getSouthWest(),
1903
		    ne2 = bounds.getNorthEast(),
1904
1905
		    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1906
		    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1907
1908
		return latIntersects && lngIntersects;
1909
	},
1910
1911
	// @method overlaps(otherBounds: Bounds): Boolean
1912
	// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1913
	overlaps: function (bounds) {
1914
		bounds = L.latLngBounds(bounds);
1915
1916
		var sw = this._southWest,
1917
		    ne = this._northEast,
1918
		    sw2 = bounds.getSouthWest(),
1919
		    ne2 = bounds.getNorthEast(),
1920
1921
		    latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1922
		    lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1923
1924
		return latOverlaps && lngOverlaps;
1925
	},
1926
1927
	// @method toBBoxString(): String
1928
	// 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.
1929
	toBBoxString: function () {
1930
		return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1931
	},
1932
1933
	// @method equals(otherBounds: LatLngBounds): Boolean
1934
	// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1935
	equals: function (bounds) {
1936
		if (!bounds) { return false; }
1937
1938
		bounds = L.latLngBounds(bounds);
1939
1940
		return this._southWest.equals(bounds.getSouthWest()) &&
1941
		       this._northEast.equals(bounds.getNorthEast());
1942
	},
1943
1944
	// @method isValid(): Boolean
1945
	// Returns `true` if the bounds are properly initialized.
1946
	isValid: function () {
1947
		return !!(this._southWest && this._northEast);
1948
	}
1949
};
1950
1951
// TODO International date line?
1952
1953
// @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1954
// Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1955
1956
// @alternative
1957
// @factory L.latLngBounds(latlngs: LatLng[])
1958
// 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).
1959
L.latLngBounds = function (a, b) {
1960
	if (a instanceof L.LatLngBounds) {
1961
		return a;
1962
	}
1963
	return new L.LatLngBounds(a, b);
1964
};
1965
1966
1967
1968
/*
1969
 * @namespace Projection
1970
 * @section
1971
 * Leaflet comes with a set of already defined Projections out of the box:
1972
 *
1973
 * @projection L.Projection.LonLat
1974
 *
1975
 * Equirectangular, or Plate Carree projection — the most simple projection,
1976
 * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1977
 * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1978
 * `EPSG:3395` and `Simple` CRS.
1979
 */
1980
1981
L.Projection = {};
1982
1983
L.Projection.LonLat = {
1984
	project: function (latlng) {
1985
		return new L.Point(latlng.lng, latlng.lat);
1986
	},
1987
1988
	unproject: function (point) {
1989
		return new L.LatLng(point.y, point.x);
1990
	},
1991
1992
	bounds: L.bounds([-180, -90], [180, 90])
1993
};
1994
1995
1996
1997
/*
1998
 * @namespace Projection
1999
 * @projection L.Projection.SphericalMercator
2000
 *
2001
 * Spherical Mercator projection — the most common projection for online maps,
2002
 * used by almost all free and commercial tile providers. Assumes that Earth is
2003
 * a sphere. Used by the `EPSG:3857` CRS.
2004
 */
2005
2006
L.Projection.SphericalMercator = {
2007
2008
	R: 6378137,
2009
	MAX_LATITUDE: 85.0511287798,
2010
2011
	project: function (latlng) {
2012
		var d = Math.PI / 180,
2013
		    max = this.MAX_LATITUDE,
2014
		    lat = Math.max(Math.min(max, latlng.lat), -max),
2015
		    sin = Math.sin(lat * d);
2016
2017
		return new L.Point(
2018
				this.R * latlng.lng * d,
2019
				this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2020
	},
2021
2022
	unproject: function (point) {
2023
		var d = 180 / Math.PI;
2024
2025
		return new L.LatLng(
2026
			(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2027
			point.x * d / this.R);
2028
	},
2029
2030
	bounds: (function () {
2031
		var d = 6378137 * Math.PI;
2032
		return L.bounds([-d, -d], [d, d]);
2033
	})()
2034
};
2035
2036
2037
2038
/*
2039
 * @class CRS
2040
 * @aka L.CRS
2041
 * Abstract class that defines coordinate reference systems for projecting
2042
 * geographical points into pixel (screen) coordinates and back (and to
2043
 * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2044
 * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2045
 *
2046
 * Leaflet defines the most usual CRSs by default. If you want to use a
2047
 * CRS not defined by default, take a look at the
2048
 * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2049
 */
2050
2051
L.CRS = {
2052
	// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2053
	// Projects geographical coordinates into pixel coordinates for a given zoom.
2054
	latLngToPoint: function (latlng, zoom) {
2055
		var projectedPoint = this.projection.project(latlng),
2056
		    scale = this.scale(zoom);
2057
2058
		return this.transformation._transform(projectedPoint, scale);
2059
	},
2060
2061
	// @method pointToLatLng(point: Point, zoom: Number): LatLng
2062
	// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2063
	// zoom into geographical coordinates.
2064
	pointToLatLng: function (point, zoom) {
2065
		var scale = this.scale(zoom),
2066
		    untransformedPoint = this.transformation.untransform(point, scale);
2067
2068
		return this.projection.unproject(untransformedPoint);
2069
	},
2070
2071
	// @method project(latlng: LatLng): Point
2072
	// Projects geographical coordinates into coordinates in units accepted for
2073
	// this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2074
	project: function (latlng) {
2075
		return this.projection.project(latlng);
2076
	},
2077
2078
	// @method unproject(point: Point): LatLng
2079
	// Given a projected coordinate returns the corresponding LatLng.
2080
	// The inverse of `project`.
2081
	unproject: function (point) {
2082
		return this.projection.unproject(point);
2083
	},
2084
2085
	// @method scale(zoom: Number): Number
2086
	// Returns the scale used when transforming projected coordinates into
2087
	// pixel coordinates for a particular zoom. For example, it returns
2088
	// `256 * 2^zoom` for Mercator-based CRS.
2089
	scale: function (zoom) {
2090
		return 256 * Math.pow(2, zoom);
2091
	},
2092
2093
	// @method zoom(scale: Number): Number
2094
	// Inverse of `scale()`, returns the zoom level corresponding to a scale
2095
	// factor of `scale`.
2096
	zoom: function (scale) {
2097
		return Math.log(scale / 256) / Math.LN2;
2098
	},
2099
2100
	// @method getProjectedBounds(zoom: Number): Bounds
2101
	// Returns the projection's bounds scaled and transformed for the provided `zoom`.
2102
	getProjectedBounds: function (zoom) {
2103
		if (this.infinite) { return null; }
2104
2105
		var b = this.projection.bounds,
2106
		    s = this.scale(zoom),
2107
		    min = this.transformation.transform(b.min, s),
2108
		    max = this.transformation.transform(b.max, s);
2109
2110
		return L.bounds(min, max);
2111
	},
2112
2113
	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
2114
	// Returns the distance between two geographical coordinates.
2115
2116
	// @property code: String
2117
	// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2118
	//
2119
	// @property wrapLng: Number[]
2120
	// An array of two numbers defining whether the longitude (horizontal) coordinate
2121
	// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2122
	// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2123
	//
2124
	// @property wrapLat: Number[]
2125
	// Like `wrapLng`, but for the latitude (vertical) axis.
2126
2127
	// wrapLng: [min, max],
2128
	// wrapLat: [min, max],
2129
2130
	// @property infinite: Boolean
2131
	// If true, the coordinate space will be unbounded (infinite in both axes)
2132
	infinite: false,
2133
2134
	// @method wrapLatLng(latlng: LatLng): LatLng
2135
	// Returns a `LatLng` where lat and lng has been wrapped according to the
2136
	// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2137
	// Only accepts actual `L.LatLng` instances, not arrays.
2138
	wrapLatLng: function (latlng) {
2139
		var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2140
		    lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2141
		    alt = latlng.alt;
2142
2143
		return L.latLng(lat, lng, alt);
2144
	},
2145
2146
	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
2147
	// Returns a `LatLngBounds` with the same size as the given one, ensuring
2148
	// that its center is within the CRS's bounds.
2149
	// Only accepts actual `L.LatLngBounds` instances, not arrays.
2150
	wrapLatLngBounds: function (bounds) {
2151
		var center = bounds.getCenter(),
2152
		    newCenter = this.wrapLatLng(center),
2153
		    latShift = center.lat - newCenter.lat,
2154
		    lngShift = center.lng - newCenter.lng;
2155
2156
		if (latShift === 0 && lngShift === 0) {
2157
			return bounds;
2158
		}
2159
2160
		var sw = bounds.getSouthWest(),
2161
		    ne = bounds.getNorthEast(),
2162
		    newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
2163
		    newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});
2164
2165
		return new L.LatLngBounds(newSw, newNe);
2166
	}
2167
};
2168
2169
2170
2171
/*
2172
 * @namespace CRS
2173
 * @crs L.CRS.Simple
2174
 *
2175
 * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2176
 * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2177
 * axis should still be inverted (going from bottom to top). `distance()` returns
2178
 * simple euclidean distance.
2179
 */
2180
2181
L.CRS.Simple = L.extend({}, L.CRS, {
2182
	projection: L.Projection.LonLat,
2183
	transformation: new L.Transformation(1, 0, -1, 0),
2184
2185
	scale: function (zoom) {
2186
		return Math.pow(2, zoom);
2187
	},
2188
2189
	zoom: function (scale) {
2190
		return Math.log(scale) / Math.LN2;
2191
	},
2192
2193
	distance: function (latlng1, latlng2) {
2194
		var dx = latlng2.lng - latlng1.lng,
2195
		    dy = latlng2.lat - latlng1.lat;
2196
2197
		return Math.sqrt(dx * dx + dy * dy);
2198
	},
2199
2200
	infinite: true
2201
});
2202
2203
2204
2205
/*
2206
 * @namespace CRS
2207
 * @crs L.CRS.Earth
2208
 *
2209
 * Serves as the base for CRS that are global such that they cover the earth.
2210
 * Can only be used as the base for other CRS and cannot be used directly,
2211
 * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2212
 * meters.
2213
 */
2214
2215
L.CRS.Earth = L.extend({}, L.CRS, {
2216
	wrapLng: [-180, 180],
2217
2218
	// Mean Earth Radius, as recommended for use by
2219
	// the International Union of Geodesy and Geophysics,
2220
	// see http://rosettacode.org/wiki/Haversine_formula
2221
	R: 6371000,
2222
2223
	// distance between two geographical points using spherical law of cosines approximation
2224
	distance: function (latlng1, latlng2) {
2225
		var rad = Math.PI / 180,
2226
		    lat1 = latlng1.lat * rad,
2227
		    lat2 = latlng2.lat * rad,
2228
		    a = Math.sin(lat1) * Math.sin(lat2) +
2229
		        Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2230
2231
		return this.R * Math.acos(Math.min(a, 1));
2232
	}
2233
});
2234
2235
2236
2237
/*
2238
 * @namespace CRS
2239
 * @crs L.CRS.EPSG3857
2240
 *
2241
 * The most common CRS for online maps, used by almost all free and commercial
2242
 * tile providers. Uses Spherical Mercator projection. Set in by default in
2243
 * Map's `crs` option.
2244
 */
2245
2246
L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2247
	code: 'EPSG:3857',
2248
	projection: L.Projection.SphericalMercator,
2249
2250
	transformation: (function () {
2251
		var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2252
		return new L.Transformation(scale, 0.5, -scale, 0.5);
2253
	}())
2254
});
2255
2256
L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2257
	code: 'EPSG:900913'
2258
});
2259
2260
2261
2262
/*
2263
 * @namespace CRS
2264
 * @crs L.CRS.EPSG4326
2265
 *
2266
 * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2267
 *
2268
 * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
2269
 * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
2270
 * with this CRS, ensure that there are two 256x256 pixel tiles covering the
2271
 * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
2272
 * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
2273
 */
2274
2275
L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2276
	code: 'EPSG:4326',
2277
	projection: L.Projection.LonLat,
2278
	transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2279
});
2280
2281
2282
2283
/*
2284
 * @class Map
2285
 * @aka L.Map
2286
 * @inherits Evented
2287
 *
2288
 * The central class of the API — it is used to create a map on a page and manipulate it.
2289
 *
2290
 * @example
2291
 *
2292
 * ```js
2293
 * // initialize the map on the "map" div with a given center and zoom
2294
 * var map = L.map('map', {
2295
 * 	center: [51.505, -0.09],
2296
 * 	zoom: 13
2297
 * });
2298
 * ```
2299
 *
2300
 */
2301
2302
L.Map = L.Evented.extend({
2303
2304
	options: {
2305
		// @section Map State Options
2306
		// @option crs: CRS = L.CRS.EPSG3857
2307
		// The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2308
		// sure what it means.
2309
		crs: L.CRS.EPSG3857,
2310
2311
		// @option center: LatLng = undefined
2312
		// Initial geographic center of the map
2313
		center: undefined,
2314
2315
		// @option zoom: Number = undefined
2316
		// Initial map zoom level
2317
		zoom: undefined,
2318
2319
		// @option minZoom: Number = undefined
2320
		// Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2321
		minZoom: undefined,
2322
2323
		// @option maxZoom: Number = undefined
2324
		// Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2325
		maxZoom: undefined,
2326
2327
		// @option layers: Layer[] = []
2328
		// Array of layers that will be added to the map initially
2329
		layers: [],
2330
2331
		// @option maxBounds: LatLngBounds = null
2332
		// When this option is set, the map restricts the view to the given
2333
		// geographical bounds, bouncing the user back if the user tries to pan
2334
		// outside the view. To set the restriction dynamically, use
2335
		// [`setMaxBounds`](#map-setmaxbounds) method.
2336
		maxBounds: undefined,
2337
2338
		// @option renderer: Renderer = *
2339
		// The default method for drawing vector layers on the map. `L.SVG`
2340
		// or `L.Canvas` by default depending on browser support.
2341
		renderer: undefined,
2342
2343
2344
		// @section Animation Options
2345
		// @option zoomAnimation: Boolean = true
2346
		// Whether the map zoom animation is enabled. By default it's enabled
2347
		// in all browsers that support CSS3 Transitions except Android.
2348
		zoomAnimation: true,
2349
2350
		// @option zoomAnimationThreshold: Number = 4
2351
		// Won't animate zoom if the zoom difference exceeds this value.
2352
		zoomAnimationThreshold: 4,
2353
2354
		// @option fadeAnimation: Boolean = true
2355
		// Whether the tile fade animation is enabled. By default it's enabled
2356
		// in all browsers that support CSS3 Transitions except Android.
2357
		fadeAnimation: true,
2358
2359
		// @option markerZoomAnimation: Boolean = true
2360
		// Whether markers animate their zoom with the zoom animation, if disabled
2361
		// they will disappear for the length of the animation. By default it's
2362
		// enabled in all browsers that support CSS3 Transitions except Android.
2363
		markerZoomAnimation: true,
2364
2365
		// @option transform3DLimit: Number = 2^23
2366
		// Defines the maximum size of a CSS translation transform. The default
2367
		// value should not be changed unless a web browser positions layers in
2368
		// the wrong place after doing a large `panBy`.
2369
		transform3DLimit: 8388608, // Precision limit of a 32-bit float
2370
2371
		// @section Interaction Options
2372
		// @option zoomSnap: Number = 1
2373
		// Forces the map's zoom level to always be a multiple of this, particularly
2374
		// right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2375
		// By default, the zoom level snaps to the nearest integer; lower values
2376
		// (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2377
		// means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2378
		zoomSnap: 1,
2379
2380
		// @option zoomDelta: Number = 1
2381
		// Controls how much the map's zoom level will change after a
2382
		// [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2383
		// or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2384
		// Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2385
		zoomDelta: 1,
2386
2387
		// @option trackResize: Boolean = true
2388
		// Whether the map automatically handles browser window resize to update itself.
2389
		trackResize: true
2390
	},
2391
2392
	initialize: function (id, options) { // (HTMLElement or String, Object)
2393
		options = L.setOptions(this, options);
2394
2395
		this._initContainer(id);
2396
		this._initLayout();
2397
2398
		// hack for https://github.com/Leaflet/Leaflet/issues/1980
2399
		this._onResize = L.bind(this._onResize, this);
2400
2401
		this._initEvents();
2402
2403
		if (options.maxBounds) {
2404
			this.setMaxBounds(options.maxBounds);
2405
		}
2406
2407
		if (options.zoom !== undefined) {
2408
			this._zoom = this._limitZoom(options.zoom);
2409
		}
2410
2411
		if (options.center && options.zoom !== undefined) {
2412
			this.setView(L.latLng(options.center), options.zoom, {reset: true});
2413
		}
2414
2415
		this._handlers = [];
2416
		this._layers = {};
2417
		this._zoomBoundLayers = {};
2418
		this._sizeChanged = true;
2419
2420
		this.callInitHooks();
2421
2422
		// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
2423
		this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
2424
				this.options.zoomAnimation;
2425
2426
		// zoom transitions run with the same duration for all layers, so if one of transitionend events
2427
		// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
2428
		if (this._zoomAnimated) {
2429
			this._createAnimProxy();
2430
			L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
2431
		}
2432
2433
		this._addLayers(this.options.layers);
2434
	},
2435
2436
2437
	// @section Methods for modifying map state
2438
2439
	// @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2440
	// Sets the view of the map (geographical center and zoom) with the given
2441
	// animation options.
2442
	setView: function (center, zoom, options) {
2443
2444
		zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
2445
		center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
2446
		options = options || {};
2447
2448
		this._stop();
2449
2450
		if (this._loaded && !options.reset && options !== true) {
2451
2452
			if (options.animate !== undefined) {
2453
				options.zoom = L.extend({animate: options.animate}, options.zoom);
2454
				options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
2455
			}
2456
2457
			// try animating pan or zoom
2458
			var moved = (this._zoom !== zoom) ?
2459
				this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
2460
				this._tryAnimatedPan(center, options.pan);
2461
2462
			if (moved) {
2463
				// prevent resize handler call, the view will refresh after animation anyway
2464
				clearTimeout(this._sizeTimer);
2465
				return this;
2466
			}
2467
		}
2468
2469
		// animation didn't start, just reset the map view
2470
		this._resetView(center, zoom);
2471
2472
		return this;
2473
	},
2474
2475
	// @method setZoom(zoom: Number, options: Zoom/pan options): this
2476
	// Sets the zoom of the map.
2477
	setZoom: function (zoom, options) {
2478
		if (!this._loaded) {
2479
			this._zoom = zoom;
2480
			return this;
2481
		}
2482
		return this.setView(this.getCenter(), zoom, {zoom: options});
2483
	},
2484
2485
	// @method zoomIn(delta?: Number, options?: Zoom options): this
2486
	// Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2487
	zoomIn: function (delta, options) {
2488
		delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2489
		return this.setZoom(this._zoom + delta, options);
2490
	},
2491
2492
	// @method zoomOut(delta?: Number, options?: Zoom options): this
2493
	// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2494
	zoomOut: function (delta, options) {
2495
		delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2496
		return this.setZoom(this._zoom - delta, options);
2497
	},
2498
2499
	// @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2500
	// Zooms the map while keeping a specified geographical point on the map
2501
	// stationary (e.g. used internally for scroll zoom and double-click zoom).
2502
	// @alternative
2503
	// @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2504
	// Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2505
	setZoomAround: function (latlng, zoom, options) {
2506
		var scale = this.getZoomScale(zoom),
2507
		    viewHalf = this.getSize().divideBy(2),
2508
		    containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2509
2510
		    centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2511
		    newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2512
2513
		return this.setView(newCenter, zoom, {zoom: options});
2514
	},
2515
2516
	_getBoundsCenterZoom: function (bounds, options) {
2517
2518
		options = options || {};
2519
		bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2520
2521
		var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2522
		    paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2523
2524
		    zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2525
2526
		zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2527
2528
		var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2529
2530
		    swPoint = this.project(bounds.getSouthWest(), zoom),
2531
		    nePoint = this.project(bounds.getNorthEast(), zoom),
2532
		    center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2533
2534
		return {
2535
			center: center,
2536
			zoom: zoom
2537
		};
2538
	},
2539
2540
	// @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
2541
	// Sets a map view that contains the given geographical bounds with the
2542
	// maximum zoom level possible.
2543
	fitBounds: function (bounds, options) {
2544
2545
		bounds = L.latLngBounds(bounds);
2546
2547
		if (!bounds.isValid()) {
2548
			throw new Error('Bounds are not valid.');
2549
		}
2550
2551
		var target = this._getBoundsCenterZoom(bounds, options);
2552
		return this.setView(target.center, target.zoom, options);
2553
	},
2554
2555
	// @method fitWorld(options?: fitBounds options): this
2556
	// Sets a map view that mostly contains the whole world with the maximum
2557
	// zoom level possible.
2558
	fitWorld: function (options) {
2559
		return this.fitBounds([[-90, -180], [90, 180]], options);
2560
	},
2561
2562
	// @method panTo(latlng: LatLng, options?: Pan options): this
2563
	// Pans the map to a given center.
2564
	panTo: function (center, options) { // (LatLng)
2565
		return this.setView(center, this._zoom, {pan: options});
2566
	},
2567
2568
	// @method panBy(offset: Point): this
2569
	// Pans the map by a given number of pixels (animated).
2570
	panBy: function (offset, options) {
2571
		offset = L.point(offset).round();
2572
		options = options || {};
2573
2574
		if (!offset.x && !offset.y) {
2575
			return this.fire('moveend');
2576
		}
2577
		// If we pan too far, Chrome gets issues with tiles
2578
		// and makes them disappear or appear in the wrong place (slightly offset) #2602
2579
		if (options.animate !== true && !this.getSize().contains(offset)) {
2580
			this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
2581
			return this;
2582
		}
2583
2584
		if (!this._panAnim) {
2585
			this._panAnim = new L.PosAnimation();
2586
2587
			this._panAnim.on({
2588
				'step': this._onPanTransitionStep,
2589
				'end': this._onPanTransitionEnd
2590
			}, this);
2591
		}
2592
2593
		// don't fire movestart if animating inertia
2594
		if (!options.noMoveStart) {
2595
			this.fire('movestart');
2596
		}
2597
2598
		// animate pan unless animate: false specified
2599
		if (options.animate !== false) {
2600
			L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
2601
2602
			var newPos = this._getMapPanePos().subtract(offset).round();
2603
			this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
2604
		} else {
2605
			this._rawPanBy(offset);
2606
			this.fire('move').fire('moveend');
2607
		}
2608
2609
		return this;
2610
	},
2611
2612
	// @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
2613
	// Sets the view of the map (geographical center and zoom) performing a smooth
2614
	// pan-zoom animation.
2615
	flyTo: function (targetCenter, targetZoom, options) {
2616
2617
		options = options || {};
2618
		if (options.animate === false || !L.Browser.any3d) {
2619
			return this.setView(targetCenter, targetZoom, options);
2620
		}
2621
2622
		this._stop();
2623
2624
		var from = this.project(this.getCenter()),
2625
		    to = this.project(targetCenter),
2626
		    size = this.getSize(),
2627
		    startZoom = this._zoom;
2628
2629
		targetCenter = L.latLng(targetCenter);
2630
		targetZoom = targetZoom === undefined ? startZoom : targetZoom;
2631
2632
		var w0 = Math.max(size.x, size.y),
2633
		    w1 = w0 * this.getZoomScale(startZoom, targetZoom),
2634
		    u1 = (to.distanceTo(from)) || 1,
2635
		    rho = 1.42,
2636
		    rho2 = rho * rho;
2637
2638
		function r(i) {
2639
			var s1 = i ? -1 : 1,
2640
			    s2 = i ? w1 : w0,
2641
			    t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
2642
			    b1 = 2 * s2 * rho2 * u1,
2643
			    b = t1 / b1,
2644
			    sq = Math.sqrt(b * b + 1) - b;
2645
2646
			    // workaround for floating point precision bug when sq = 0, log = -Infinite,
2647
			    // thus triggering an infinite loop in flyTo
2648
			    var log = sq < 0.000000001 ? -18 : Math.log(sq);
2649
2650
			return log;
2651
		}
2652
2653
		function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
2654
		function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
2655
		function tanh(n) { return sinh(n) / cosh(n); }
2656
2657
		var r0 = r(0);
2658
2659
		function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
2660
		function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
2661
2662
		function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
2663
2664
		var start = Date.now(),
2665
		    S = (r(1) - r0) / rho,
2666
		    duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
2667
2668
		function frame() {
2669
			var t = (Date.now() - start) / duration,
2670
			    s = easeOut(t) * S;
2671
2672
			if (t <= 1) {
2673
				this._flyToFrame = L.Util.requestAnimFrame(frame, this);
2674
2675
				this._move(
2676
					this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
2677
					this.getScaleZoom(w0 / w(s), startZoom),
2678
					{flyTo: true});
2679
2680
			} else {
2681
				this
2682
					._move(targetCenter, targetZoom)
2683
					._moveEnd(true);
2684
			}
2685
		}
2686
2687
		this._moveStart(true);
2688
2689
		frame.call(this);
2690
		return this;
2691
	},
2692
2693
	// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
2694
	// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
2695
	// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
2696
	flyToBounds: function (bounds, options) {
2697
		var target = this._getBoundsCenterZoom(bounds, options);
2698
		return this.flyTo(target.center, target.zoom, options);
2699
	},
2700
2701
	// @method setMaxBounds(bounds: Bounds): this
2702
	// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2703
	setMaxBounds: function (bounds) {
2704
		bounds = L.latLngBounds(bounds);
2705
2706
		if (!bounds.isValid()) {
2707
			this.options.maxBounds = null;
2708
			return this.off('moveend', this._panInsideMaxBounds);
2709
		} else if (this.options.maxBounds) {
2710
			this.off('moveend', this._panInsideMaxBounds);
2711
		}
2712
2713
		this.options.maxBounds = bounds;
2714
2715
		if (this._loaded) {
2716
			this._panInsideMaxBounds();
2717
		}
2718
2719
		return this.on('moveend', this._panInsideMaxBounds);
2720
	},
2721
2722
	// @method setMinZoom(zoom: Number): this
2723
	// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2724
	setMinZoom: function (zoom) {
2725
		this.options.minZoom = zoom;
2726
2727
		if (this._loaded && this.getZoom() < this.options.minZoom) {
2728
			return this.setZoom(zoom);
2729
		}
2730
2731
		return this;
2732
	},
2733
2734
	// @method setMaxZoom(zoom: Number): this
2735
	// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2736
	setMaxZoom: function (zoom) {
2737
		this.options.maxZoom = zoom;
2738
2739
		if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2740
			return this.setZoom(zoom);
2741
		}
2742
2743
		return this;
2744
	},
2745
2746
	// @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2747
	// 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.
2748
	panInsideBounds: function (bounds, options) {
2749
		this._enforcingBounds = true;
2750
		var center = this.getCenter(),
2751
		    newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2752
2753
		if (!center.equals(newCenter)) {
2754
			this.panTo(newCenter, options);
2755
		}
2756
2757
		this._enforcingBounds = false;
2758
		return this;
2759
	},
2760
2761
	// @method invalidateSize(options: Zoom/Pan options): this
2762
	// Checks if the map container size changed and updates the map if so —
2763
	// call it after you've changed the map size dynamically, also animating
2764
	// pan by default. If `options.pan` is `false`, panning will not occur.
2765
	// If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2766
	// that it doesn't happen often even if the method is called many
2767
	// times in a row.
2768
2769
	// @alternative
2770
	// @method invalidateSize(animate: Boolean): this
2771
	// Checks if the map container size changed and updates the map if so —
2772
	// call it after you've changed the map size dynamically, also animating
2773
	// pan by default.
2774
	invalidateSize: function (options) {
2775
		if (!this._loaded) { return this; }
2776
2777
		options = L.extend({
2778
			animate: false,
2779
			pan: true
2780
		}, options === true ? {animate: true} : options);
2781
2782
		var oldSize = this.getSize();
2783
		this._sizeChanged = true;
2784
		this._lastCenter = null;
2785
2786
		var newSize = this.getSize(),
2787
		    oldCenter = oldSize.divideBy(2).round(),
2788
		    newCenter = newSize.divideBy(2).round(),
2789
		    offset = oldCenter.subtract(newCenter);
2790
2791
		if (!offset.x && !offset.y) { return this; }
2792
2793
		if (options.animate && options.pan) {
2794
			this.panBy(offset);
2795
2796
		} else {
2797
			if (options.pan) {
2798
				this._rawPanBy(offset);
2799
			}
2800
2801
			this.fire('move');
2802
2803
			if (options.debounceMoveend) {
2804
				clearTimeout(this._sizeTimer);
2805
				this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2806
			} else {
2807
				this.fire('moveend');
2808
			}
2809
		}
2810
2811
		// @section Map state change events
2812
		// @event resize: ResizeEvent
2813
		// Fired when the map is resized.
2814
		return this.fire('resize', {
2815
			oldSize: oldSize,
2816
			newSize: newSize
2817
		});
2818
	},
2819
2820
	// @section Methods for modifying map state
2821
	// @method stop(): this
2822
	// Stops the currently running `panTo` or `flyTo` animation, if any.
2823
	stop: function () {
2824
		this.setZoom(this._limitZoom(this._zoom));
2825
		if (!this.options.zoomSnap) {
2826
			this.fire('viewreset');
2827
		}
2828
		return this._stop();
2829
	},
2830
2831
	// @section Geolocation methods
2832
	// @method locate(options?: Locate options): this
2833
	// Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
2834
	// event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
2835
	// and optionally sets the map view to the user's location with respect to
2836
	// detection accuracy (or to the world view if geolocation failed).
2837
	// Note that, if your page doesn't use HTTPS, this method will fail in
2838
	// modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
2839
	// See `Locate options` for more details.
2840
	locate: function (options) {
2841
2842
		options = this._locateOptions = L.extend({
2843
			timeout: 10000,
2844
			watch: false
2845
			// setView: false
2846
			// maxZoom: <Number>
2847
			// maximumAge: 0
2848
			// enableHighAccuracy: false
2849
		}, options);
2850
2851
		if (!('geolocation' in navigator)) {
2852
			this._handleGeolocationError({
2853
				code: 0,
2854
				message: 'Geolocation not supported.'
2855
			});
2856
			return this;
2857
		}
2858
2859
		var onResponse = L.bind(this._handleGeolocationResponse, this),
2860
		    onError = L.bind(this._handleGeolocationError, this);
2861
2862
		if (options.watch) {
2863
			this._locationWatchId =
2864
			        navigator.geolocation.watchPosition(onResponse, onError, options);
2865
		} else {
2866
			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
2867
		}
2868
		return this;
2869
	},
2870
2871
	// @method stopLocate(): this
2872
	// Stops watching location previously initiated by `map.locate({watch: true})`
2873
	// and aborts resetting the map view if map.locate was called with
2874
	// `{setView: true}`.
2875
	stopLocate: function () {
2876
		if (navigator.geolocation && navigator.geolocation.clearWatch) {
2877
			navigator.geolocation.clearWatch(this._locationWatchId);
2878
		}
2879
		if (this._locateOptions) {
2880
			this._locateOptions.setView = false;
2881
		}
2882
		return this;
2883
	},
2884
2885
	_handleGeolocationError: function (error) {
2886
		var c = error.code,
2887
		    message = error.message ||
2888
		            (c === 1 ? 'permission denied' :
2889
		            (c === 2 ? 'position unavailable' : 'timeout'));
2890
2891
		if (this._locateOptions.setView && !this._loaded) {
2892
			this.fitWorld();
2893
		}
2894
2895
		// @section Location events
2896
		// @event locationerror: ErrorEvent
2897
		// Fired when geolocation (using the [`locate`](#map-locate) method) failed.
2898
		this.fire('locationerror', {
2899
			code: c,
2900
			message: 'Geolocation error: ' + message + '.'
2901
		});
2902
	},
2903
2904
	_handleGeolocationResponse: function (pos) {
2905
		var lat = pos.coords.latitude,
2906
		    lng = pos.coords.longitude,
2907
		    latlng = new L.LatLng(lat, lng),
2908
		    bounds = latlng.toBounds(pos.coords.accuracy),
2909
		    options = this._locateOptions;
2910
2911
		if (options.setView) {
2912
			var zoom = this.getBoundsZoom(bounds);
2913
			this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
2914
		}
2915
2916
		var data = {
2917
			latlng: latlng,
2918
			bounds: bounds,
2919
			timestamp: pos.timestamp
2920
		};
2921
2922
		for (var i in pos.coords) {
2923
			if (typeof pos.coords[i] === 'number') {
2924
				data[i] = pos.coords[i];
2925
			}
2926
		}
2927
2928
		// @event locationfound: LocationEvent
2929
		// Fired when geolocation (using the [`locate`](#map-locate) method)
2930
		// went successfully.
2931
		this.fire('locationfound', data);
2932
	},
2933
2934
	// TODO handler.addTo
2935
	// TODO Appropiate docs section?
2936
	// @section Other Methods
2937
	// @method addHandler(name: String, HandlerClass: Function): this
2938
	// Adds a new `Handler` to the map, given its name and constructor function.
2939
	addHandler: function (name, HandlerClass) {
2940
		if (!HandlerClass) { return this; }
2941
2942
		var handler = this[name] = new HandlerClass(this);
2943
2944
		this._handlers.push(handler);
2945
2946
		if (this.options[name]) {
2947
			handler.enable();
2948
		}
2949
2950
		return this;
2951
	},
2952
2953
	// @method remove(): this
2954
	// Destroys the map and clears all related event listeners.
2955
	remove: function () {
2956
2957
		this._initEvents(true);
2958
2959
		if (this._containerId !== this._container._leaflet_id) {
2960
			throw new Error('Map container is being reused by another instance');
2961
		}
2962
2963
		try {
2964
			// throws error in IE6-8
2965
			delete this._container._leaflet_id;
2966
			delete this._containerId;
2967
		} catch (e) {
2968
			/*eslint-disable */
2969
			this._container._leaflet_id = undefined;
2970
			/*eslint-enable */
2971
			this._containerId = undefined;
2972
		}
2973
2974
		L.DomUtil.remove(this._mapPane);
2975
2976
		if (this._clearControlPos) {
2977
			this._clearControlPos();
2978
		}
2979
2980
		this._clearHandlers();
2981
2982
		if (this._loaded) {
2983
			// @section Map state change events
2984
			// @event unload: Event
2985
			// Fired when the map is destroyed with [remove](#map-remove) method.
2986
			this.fire('unload');
2987
		}
2988
2989
		for (var i in this._layers) {
2990
			this._layers[i].remove();
2991
		}
2992
2993
		return this;
2994
	},
2995
2996
	// @section Other Methods
2997
	// @method createPane(name: String, container?: HTMLElement): HTMLElement
2998
	// Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2999
	// then returns it. The pane is created as a children of `container`, or
3000
	// as a children of the main map pane if not set.
3001
	createPane: function (name, container) {
3002
		var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3003
		    pane = L.DomUtil.create('div', className, container || this._mapPane);
3004
3005
		if (name) {
3006
			this._panes[name] = pane;
3007
		}
3008
		return pane;
3009
	},
3010
3011
	// @section Methods for Getting Map State
3012
3013
	// @method getCenter(): LatLng
3014
	// Returns the geographical center of the map view
3015
	getCenter: function () {
3016
		this._checkIfLoaded();
3017
3018
		if (this._lastCenter && !this._moved()) {
3019
			return this._lastCenter;
3020
		}
3021
		return this.layerPointToLatLng(this._getCenterLayerPoint());
3022
	},
3023
3024
	// @method getZoom(): Number
3025
	// Returns the current zoom level of the map view
3026
	getZoom: function () {
3027
		return this._zoom;
3028
	},
3029
3030
	// @method getBounds(): LatLngBounds
3031
	// Returns the geographical bounds visible in the current map view
3032
	getBounds: function () {
3033
		var bounds = this.getPixelBounds(),
3034
		    sw = this.unproject(bounds.getBottomLeft()),
3035
		    ne = this.unproject(bounds.getTopRight());
3036
3037
		return new L.LatLngBounds(sw, ne);
3038
	},
3039
3040
	// @method getMinZoom(): Number
3041
	// 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.
3042
	getMinZoom: function () {
3043
		return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3044
	},
3045
3046
	// @method getMaxZoom(): Number
3047
	// Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3048
	getMaxZoom: function () {
3049
		return this.options.maxZoom === undefined ?
3050
			(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3051
			this.options.maxZoom;
3052
	},
3053
3054
	// @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3055
	// Returns the maximum zoom level on which the given bounds fit to the map
3056
	// view in its entirety. If `inside` (optional) is set to `true`, the method
3057
	// instead returns the minimum zoom level on which the map view fits into
3058
	// the given bounds in its entirety.
3059
	getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3060
		bounds = L.latLngBounds(bounds);
3061
		padding = L.point(padding || [0, 0]);
3062
3063
		var zoom = this.getZoom() || 0,
3064
		    min = this.getMinZoom(),
3065
		    max = this.getMaxZoom(),
3066
		    nw = bounds.getNorthWest(),
3067
		    se = bounds.getSouthEast(),
3068
		    size = this.getSize().subtract(padding),
3069
		    boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3070
		    snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3071
3072
		var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
3073
		zoom = this.getScaleZoom(scale, zoom);
3074
3075
		if (snap) {
3076
			zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3077
			zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3078
		}
3079
3080
		return Math.max(min, Math.min(max, zoom));
3081
	},
3082
3083
	// @method getSize(): Point
3084
	// Returns the current size of the map container (in pixels).
3085
	getSize: function () {
3086
		if (!this._size || this._sizeChanged) {
3087
			this._size = new L.Point(
3088
				this._container.clientWidth || 0,
3089
				this._container.clientHeight || 0);
3090
3091
			this._sizeChanged = false;
3092
		}
3093
		return this._size.clone();
3094
	},
3095
3096
	// @method getPixelBounds(): Bounds
3097
	// Returns the bounds of the current map view in projected pixel
3098
	// coordinates (sometimes useful in layer and overlay implementations).
3099
	getPixelBounds: function (center, zoom) {
3100
		var topLeftPoint = this._getTopLeftPoint(center, zoom);
3101
		return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3102
	},
3103
3104
	// TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3105
	// the map pane? "left point of the map layer" can be confusing, specially
3106
	// since there can be negative offsets.
3107
	// @method getPixelOrigin(): Point
3108
	// Returns the projected pixel coordinates of the top left point of
3109
	// the map layer (useful in custom layer and overlay implementations).
3110
	getPixelOrigin: function () {
3111
		this._checkIfLoaded();
3112
		return this._pixelOrigin;
3113
	},
3114
3115
	// @method getPixelWorldBounds(zoom?: Number): Bounds
3116
	// Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3117
	// If `zoom` is omitted, the map's current zoom level is used.
3118
	getPixelWorldBounds: function (zoom) {
3119
		return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3120
	},
3121
3122
	// @section Other Methods
3123
3124
	// @method getPane(pane: String|HTMLElement): HTMLElement
3125
	// Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3126
	getPane: function (pane) {
3127
		return typeof pane === 'string' ? this._panes[pane] : pane;
3128
	},
3129
3130
	// @method getPanes(): Object
3131
	// Returns a plain object containing the names of all [panes](#map-pane) as keys and
3132
	// the panes as values.
3133
	getPanes: function () {
3134
		return this._panes;
3135
	},
3136
3137
	// @method getContainer: HTMLElement
3138
	// Returns the HTML element that contains the map.
3139
	getContainer: function () {
3140
		return this._container;
3141
	},
3142
3143
3144
	// @section Conversion Methods
3145
3146
	// @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3147
	// Returns the scale factor to be applied to a map transition from zoom level
3148
	// `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3149
	getZoomScale: function (toZoom, fromZoom) {
3150
		// TODO replace with universal implementation after refactoring projections
3151
		var crs = this.options.crs;
3152
		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3153
		return crs.scale(toZoom) / crs.scale(fromZoom);
3154
	},
3155
3156
	// @method getScaleZoom(scale: Number, fromZoom: Number): Number
3157
	// Returns the zoom level that the map would end up at, if it is at `fromZoom`
3158
	// level and everything is scaled by a factor of `scale`. Inverse of
3159
	// [`getZoomScale`](#map-getZoomScale).
3160
	getScaleZoom: function (scale, fromZoom) {
3161
		var crs = this.options.crs;
3162
		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3163
		var zoom = crs.zoom(scale * crs.scale(fromZoom));
3164
		return isNaN(zoom) ? Infinity : zoom;
3165
	},
3166
3167
	// @method project(latlng: LatLng, zoom: Number): Point
3168
	// Projects a geographical coordinate `LatLng` according to the projection
3169
	// of the map's CRS, then scales it according to `zoom` and the CRS's
3170
	// `Transformation`. The result is pixel coordinate relative to
3171
	// the CRS origin.
3172
	project: function (latlng, zoom) {
3173
		zoom = zoom === undefined ? this._zoom : zoom;
3174
		return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
3175
	},
3176
3177
	// @method unproject(point: Point, zoom: Number): LatLng
3178
	// Inverse of [`project`](#map-project).
3179
	unproject: function (point, zoom) {
3180
		zoom = zoom === undefined ? this._zoom : zoom;
3181
		return this.options.crs.pointToLatLng(L.point(point), zoom);
3182
	},
3183
3184
	// @method layerPointToLatLng(point: Point): LatLng
3185
	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3186
	// returns the corresponding geographical coordinate (for the current zoom level).
3187
	layerPointToLatLng: function (point) {
3188
		var projectedPoint = L.point(point).add(this.getPixelOrigin());
3189
		return this.unproject(projectedPoint);
3190
	},
3191
3192
	// @method latLngToLayerPoint(latlng: LatLng): Point
3193
	// Given a geographical coordinate, returns the corresponding pixel coordinate
3194
	// relative to the [origin pixel](#map-getpixelorigin).
3195
	latLngToLayerPoint: function (latlng) {
3196
		var projectedPoint = this.project(L.latLng(latlng))._round();
3197
		return projectedPoint._subtract(this.getPixelOrigin());
3198
	},
3199
3200
	// @method wrapLatLng(latlng: LatLng): LatLng
3201
	// Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3202
	// map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3203
	// CRS's bounds.
3204
	// By default this means longitude is wrapped around the dateline so its
3205
	// value is between -180 and +180 degrees.
3206
	wrapLatLng: function (latlng) {
3207
		return this.options.crs.wrapLatLng(L.latLng(latlng));
3208
	},
3209
3210
	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3211
	// Returns a `LatLngBounds` with the same size as the given one, ensuring that
3212
	// its center is within the CRS's bounds.
3213
	// By default this means the center longitude is wrapped around the dateline so its
3214
	// value is between -180 and +180 degrees, and the majority of the bounds
3215
	// overlaps the CRS's bounds.
3216
	wrapLatLngBounds: function (latlng) {
3217
		return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng));
3218
	},
3219
3220
	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
3221
	// Returns the distance between two geographical coordinates according to
3222
	// the map's CRS. By default this measures distance in meters.
3223
	distance: function (latlng1, latlng2) {
3224
		return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
3225
	},
3226
3227
	// @method containerPointToLayerPoint(point: Point): Point
3228
	// Given a pixel coordinate relative to the map container, returns the corresponding
3229
	// pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3230
	containerPointToLayerPoint: function (point) { // (Point)
3231
		return L.point(point).subtract(this._getMapPanePos());
3232
	},
3233
3234
	// @method layerPointToContainerPoint(point: Point): Point
3235
	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3236
	// returns the corresponding pixel coordinate relative to the map container.
3237
	layerPointToContainerPoint: function (point) { // (Point)
3238
		return L.point(point).add(this._getMapPanePos());
3239
	},
3240
3241
	// @method containerPointToLatLng(point: Point): LatLng
3242
	// Given a pixel coordinate relative to the map container, returns
3243
	// the corresponding geographical coordinate (for the current zoom level).
3244
	containerPointToLatLng: function (point) {
3245
		var layerPoint = this.containerPointToLayerPoint(L.point(point));
3246
		return this.layerPointToLatLng(layerPoint);
3247
	},
3248
3249
	// @method latLngToContainerPoint(latlng: LatLng): Point
3250
	// Given a geographical coordinate, returns the corresponding pixel coordinate
3251
	// relative to the map container.
3252
	latLngToContainerPoint: function (latlng) {
3253
		return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
3254
	},
3255
3256
	// @method mouseEventToContainerPoint(ev: MouseEvent): Point
3257
	// Given a MouseEvent object, returns the pixel coordinate relative to the
3258
	// map container where the event took place.
3259
	mouseEventToContainerPoint: function (e) {
3260
		return L.DomEvent.getMousePosition(e, this._container);
3261
	},
3262
3263
	// @method mouseEventToLayerPoint(ev: MouseEvent): Point
3264
	// Given a MouseEvent object, returns the pixel coordinate relative to
3265
	// the [origin pixel](#map-getpixelorigin) where the event took place.
3266
	mouseEventToLayerPoint: function (e) {
3267
		return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3268
	},
3269
3270
	// @method mouseEventToLatLng(ev: MouseEvent): LatLng
3271
	// Given a MouseEvent object, returns geographical coordinate where the
3272
	// event took place.
3273
	mouseEventToLatLng: function (e) { // (MouseEvent)
3274
		return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3275
	},
3276
3277
3278
	// map initialization methods
3279
3280
	_initContainer: function (id) {
3281
		var container = this._container = L.DomUtil.get(id);
3282
3283
		if (!container) {
3284
			throw new Error('Map container not found.');
3285
		} else if (container._leaflet_id) {
3286
			throw new Error('Map container is already initialized.');
3287
		}
3288
3289
		L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
3290
		this._containerId = L.Util.stamp(container);
3291
	},
3292
3293
	_initLayout: function () {
3294
		var container = this._container;
3295
3296
		this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
3297
3298
		L.DomUtil.addClass(container, 'leaflet-container' +
3299
			(L.Browser.touch ? ' leaflet-touch' : '') +
3300
			(L.Browser.retina ? ' leaflet-retina' : '') +
3301
			(L.Browser.ielt9 ? ' leaflet-oldie' : '') +
3302
			(L.Browser.safari ? ' leaflet-safari' : '') +
3303
			(this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3304
3305
		var position = L.DomUtil.getStyle(container, 'position');
3306
3307
		if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3308
			container.style.position = 'relative';
3309
		}
3310
3311
		this._initPanes();
3312
3313
		if (this._initControlPos) {
3314
			this._initControlPos();
3315
		}
3316
	},
3317
3318
	_initPanes: function () {
3319
		var panes = this._panes = {};
3320
		this._paneRenderers = {};
3321
3322
		// @section
3323
		//
3324
		// Panes are DOM elements used to control the ordering of layers on the map. You
3325
		// can access panes with [`map.getPane`](#map-getpane) or
3326
		// [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3327
		// [`map.createPane`](#map-createpane) method.
3328
		//
3329
		// Every map has the following default panes that differ only in zIndex.
3330
		//
3331
		// @pane mapPane: HTMLElement = 'auto'
3332
		// Pane that contains all other map panes
3333
3334
		this._mapPane = this.createPane('mapPane', this._container);
3335
		L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3336
3337
		// @pane tilePane: HTMLElement = 200
3338
		// Pane for `GridLayer`s and `TileLayer`s
3339
		this.createPane('tilePane');
3340
		// @pane overlayPane: HTMLElement = 400
3341
		// Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3342
		this.createPane('shadowPane');
3343
		// @pane shadowPane: HTMLElement = 500
3344
		// Pane for overlay shadows (e.g. `Marker` shadows)
3345
		this.createPane('overlayPane');
3346
		// @pane markerPane: HTMLElement = 600
3347
		// Pane for `Icon`s of `Marker`s
3348
		this.createPane('markerPane');
3349
		// @pane tooltipPane: HTMLElement = 650
3350
		// Pane for tooltip.
3351
		this.createPane('tooltipPane');
3352
		// @pane popupPane: HTMLElement = 700
3353
		// Pane for `Popup`s.
3354
		this.createPane('popupPane');
3355
3356
		if (!this.options.markerZoomAnimation) {
3357
			L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3358
			L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3359
		}
3360
	},
3361
3362
3363
	// private methods that modify map state
3364
3365
	// @section Map state change events
3366
	_resetView: function (center, zoom) {
3367
		L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3368
3369
		var loading = !this._loaded;
3370
		this._loaded = true;
3371
		zoom = this._limitZoom(zoom);
3372
3373
		this.fire('viewprereset');
3374
3375
		var zoomChanged = this._zoom !== zoom;
3376
		this
3377
			._moveStart(zoomChanged)
3378
			._move(center, zoom)
3379
			._moveEnd(zoomChanged);
3380
3381
		// @event viewreset: Event
3382
		// Fired when the map needs to redraw its content (this usually happens
3383
		// on map zoom or load). Very useful for creating custom overlays.
3384
		this.fire('viewreset');
3385
3386
		// @event load: Event
3387
		// Fired when the map is initialized (when its center and zoom are set
3388
		// for the first time).
3389
		if (loading) {
3390
			this.fire('load');
3391
		}
3392
	},
3393
3394
	_moveStart: function (zoomChanged) {
3395
		// @event zoomstart: Event
3396
		// Fired when the map zoom is about to change (e.g. before zoom animation).
3397
		// @event movestart: Event
3398
		// Fired when the view of the map starts changing (e.g. user starts dragging the map).
3399
		if (zoomChanged) {
3400
			this.fire('zoomstart');
3401
		}
3402
		return this.fire('movestart');
3403
	},
3404
3405
	_move: function (center, zoom, data) {
3406
		if (zoom === undefined) {
3407
			zoom = this._zoom;
3408
		}
3409
		var zoomChanged = this._zoom !== zoom;
3410
3411
		this._zoom = zoom;
3412
		this._lastCenter = center;
3413
		this._pixelOrigin = this._getNewPixelOrigin(center);
3414
3415
		// @event zoom: Event
3416
		// Fired repeatedly during any change in zoom level, including zoom
3417
		// and fly animations.
3418
		if (zoomChanged || (data && data.pinch)) {	// Always fire 'zoom' if pinching because #3530
3419
			this.fire('zoom', data);
3420
		}
3421
3422
		// @event move: Event
3423
		// Fired repeatedly during any movement of the map, including pan and
3424
		// fly animations.
3425
		return this.fire('move', data);
3426
	},
3427
3428
	_moveEnd: function (zoomChanged) {
3429
		// @event zoomend: Event
3430
		// Fired when the map has changed, after any animations.
3431
		if (zoomChanged) {
3432
			this.fire('zoomend');
3433
		}
3434
3435
		// @event moveend: Event
3436
		// Fired when the center of the map stops changing (e.g. user stopped
3437
		// dragging the map).
3438
		return this.fire('moveend');
3439
	},
3440
3441
	_stop: function () {
3442
		L.Util.cancelAnimFrame(this._flyToFrame);
3443
		if (this._panAnim) {
3444
			this._panAnim.stop();
3445
		}
3446
		return this;
3447
	},
3448
3449
	_rawPanBy: function (offset) {
3450
		L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3451
	},
3452
3453
	_getZoomSpan: function () {
3454
		return this.getMaxZoom() - this.getMinZoom();
3455
	},
3456
3457
	_panInsideMaxBounds: function () {
3458
		if (!this._enforcingBounds) {
3459
			this.panInsideBounds(this.options.maxBounds);
3460
		}
3461
	},
3462
3463
	_checkIfLoaded: function () {
3464
		if (!this._loaded) {
3465
			throw new Error('Set map center and zoom first.');
3466
		}
3467
	},
3468
3469
	// DOM event handling
3470
3471
	// @section Interaction events
3472
	_initEvents: function (remove) {
3473
		if (!L.DomEvent) { return; }
3474
3475
		this._targets = {};
3476
		this._targets[L.stamp(this._container)] = this;
3477
3478
		var onOff = remove ? 'off' : 'on';
3479
3480
		// @event click: MouseEvent
3481
		// Fired when the user clicks (or taps) the map.
3482
		// @event dblclick: MouseEvent
3483
		// Fired when the user double-clicks (or double-taps) the map.
3484
		// @event mousedown: MouseEvent
3485
		// Fired when the user pushes the mouse button on the map.
3486
		// @event mouseup: MouseEvent
3487
		// Fired when the user releases the mouse button on the map.
3488
		// @event mouseover: MouseEvent
3489
		// Fired when the mouse enters the map.
3490
		// @event mouseout: MouseEvent
3491
		// Fired when the mouse leaves the map.
3492
		// @event mousemove: MouseEvent
3493
		// Fired while the mouse moves over the map.
3494
		// @event contextmenu: MouseEvent
3495
		// Fired when the user pushes the right mouse button on the map, prevents
3496
		// default browser context menu from showing if there are listeners on
3497
		// this event. Also fired on mobile when the user holds a single touch
3498
		// for a second (also called long press).
3499
		// @event keypress: KeyboardEvent
3500
		// Fired when the user presses a key from the keyboard while the map is focused.
3501
		L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3502
			'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3503
3504
		if (this.options.trackResize) {
3505
			L.DomEvent[onOff](window, 'resize', this._onResize, this);
3506
		}
3507
3508
		if (L.Browser.any3d && this.options.transform3DLimit) {
3509
			this[onOff]('moveend', this._onMoveEnd);
3510
		}
3511
	},
3512
3513
	_onResize: function () {
3514
		L.Util.cancelAnimFrame(this._resizeRequest);
3515
		this._resizeRequest = L.Util.requestAnimFrame(
3516
		        function () { this.invalidateSize({debounceMoveend: true}); }, this);
3517
	},
3518
3519
	_onScroll: function () {
3520
		this._container.scrollTop  = 0;
3521
		this._container.scrollLeft = 0;
3522
	},
3523
3524
	_onMoveEnd: function () {
3525
		var pos = this._getMapPanePos();
3526
		if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3527
			// https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3528
			// a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3529
			this._resetView(this.getCenter(), this.getZoom());
3530
		}
3531
	},
3532
3533
	_findEventTargets: function (e, type) {
3534
		var targets = [],
3535
		    target,
3536
		    isHover = type === 'mouseout' || type === 'mouseover',
3537
		    src = e.target || e.srcElement,
3538
		    dragging = false;
3539
3540
		while (src) {
3541
			target = this._targets[L.stamp(src)];
3542
			if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3543
				// Prevent firing click after you just dragged an object.
3544
				dragging = true;
3545
				break;
3546
			}
3547
			if (target && target.listens(type, true)) {
3548
				if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3549
				targets.push(target);
3550
				if (isHover) { break; }
3551
			}
3552
			if (src === this._container) { break; }
3553
			src = src.parentNode;
3554
		}
3555
		if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3556
			targets = [this];
3557
		}
3558
		return targets;
3559
	},
3560
3561
	_handleDOMEvent: function (e) {
3562
		if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3563
3564
		var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3565
3566
		if (type === 'mousedown') {
3567
			// prevents outline when clicking on keyboard-focusable element
3568
			L.DomUtil.preventOutline(e.target || e.srcElement);
3569
		}
3570
3571
		this._fireDOMEvent(e, type);
3572
	},
3573
3574
	_fireDOMEvent: function (e, type, targets) {
3575
3576
		if (e.type === 'click') {
3577
			// Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3578
			// @event preclick: MouseEvent
3579
			// Fired before mouse click on the map (sometimes useful when you
3580
			// want something to happen on click before any existing click
3581
			// handlers start running).
3582
			var synth = L.Util.extend({}, e);
3583
			synth.type = 'preclick';
3584
			this._fireDOMEvent(synth, synth.type, targets);
3585
		}
3586
3587
		if (e._stopped) { return; }
3588
3589
		// Find the layer the event is propagating from and its parents.
3590
		targets = (targets || []).concat(this._findEventTargets(e, type));
3591
3592
		if (!targets.length) { return; }
3593
3594
		var target = targets[0];
3595
		if (type === 'contextmenu' && target.listens(type, true)) {
3596
			L.DomEvent.preventDefault(e);
3597
		}
3598
3599
		var data = {
3600
			originalEvent: e
3601
		};
3602
3603
		if (e.type !== 'keypress') {
3604
			var isMarker = target instanceof L.Marker;
3605
			data.containerPoint = isMarker ?
3606
					this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3607
			data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3608
			data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3609
		}
3610
3611
		for (var i = 0; i < targets.length; i++) {
3612
			targets[i].fire(type, data, true);
3613
			if (data.originalEvent._stopped ||
3614
				(targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3615
		}
3616
	},
3617
3618
	_draggableMoved: function (obj) {
3619
		obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3620
		return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3621
	},
3622
3623
	_clearHandlers: function () {
3624
		for (var i = 0, len = this._handlers.length; i < len; i++) {
3625
			this._handlers[i].disable();
3626
		}
3627
	},
3628
3629
	// @section Other Methods
3630
3631
	// @method whenReady(fn: Function, context?: Object): this
3632
	// Runs the given function `fn` when the map gets initialized with
3633
	// a view (center and zoom) and at least one layer, or immediately
3634
	// if it's already initialized, optionally passing a function context.
3635
	whenReady: function (callback, context) {
3636
		if (this._loaded) {
3637
			callback.call(context || this, {target: this});
3638
		} else {
3639
			this.on('load', callback, context);
3640
		}
3641
		return this;
3642
	},
3643
3644
3645
	// private methods for getting map state
3646
3647
	_getMapPanePos: function () {
3648
		return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3649
	},
3650
3651
	_moved: function () {
3652
		var pos = this._getMapPanePos();
3653
		return pos && !pos.equals([0, 0]);
3654
	},
3655
3656
	_getTopLeftPoint: function (center, zoom) {
3657
		var pixelOrigin = center && zoom !== undefined ?
3658
			this._getNewPixelOrigin(center, zoom) :
3659
			this.getPixelOrigin();
3660
		return pixelOrigin.subtract(this._getMapPanePos());
3661
	},
3662
3663
	_getNewPixelOrigin: function (center, zoom) {
3664
		var viewHalf = this.getSize()._divideBy(2);
3665
		return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3666
	},
3667
3668
	_latLngToNewLayerPoint: function (latlng, zoom, center) {
3669
		var topLeft = this._getNewPixelOrigin(center, zoom);
3670
		return this.project(latlng, zoom)._subtract(topLeft);
3671
	},
3672
3673
	_latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
3674
		var topLeft = this._getNewPixelOrigin(center, zoom);
3675
		return L.bounds([
3676
			this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
3677
			this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
3678
			this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
3679
			this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
3680
		]);
3681
	},
3682
3683
	// layer point of the current center
3684
	_getCenterLayerPoint: function () {
3685
		return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3686
	},
3687
3688
	// offset of the specified place to the current center in pixels
3689
	_getCenterOffset: function (latlng) {
3690
		return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3691
	},
3692
3693
	// adjust center for view to get inside bounds
3694
	_limitCenter: function (center, zoom, bounds) {
3695
3696
		if (!bounds) { return center; }
3697
3698
		var centerPoint = this.project(center, zoom),
3699
		    viewHalf = this.getSize().divideBy(2),
3700
		    viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3701
		    offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3702
3703
		// If offset is less than a pixel, ignore.
3704
		// This prevents unstable projections from getting into
3705
		// an infinite loop of tiny offsets.
3706
		if (offset.round().equals([0, 0])) {
3707
			return center;
3708
		}
3709
3710
		return this.unproject(centerPoint.add(offset), zoom);
3711
	},
3712
3713
	// adjust offset for view to get inside bounds
3714
	_limitOffset: function (offset, bounds) {
3715
		if (!bounds) { return offset; }
3716
3717
		var viewBounds = this.getPixelBounds(),
3718
		    newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3719
3720
		return offset.add(this._getBoundsOffset(newBounds, bounds));
3721
	},
3722
3723
	// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3724
	_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3725
		var projectedMaxBounds = L.bounds(
3726
		        this.project(maxBounds.getNorthEast(), zoom),
3727
		        this.project(maxBounds.getSouthWest(), zoom)
3728
		    ),
3729
		    minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3730
		    maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3731
3732
		    dx = this._rebound(minOffset.x, -maxOffset.x),
3733
		    dy = this._rebound(minOffset.y, -maxOffset.y);
3734
3735
		return new L.Point(dx, dy);
3736
	},
3737
3738
	_rebound: function (left, right) {
3739
		return left + right > 0 ?
3740
			Math.round(left - right) / 2 :
3741
			Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3742
	},
3743
3744
	_limitZoom: function (zoom) {
3745
		var min = this.getMinZoom(),
3746
		    max = this.getMaxZoom(),
3747
		    snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3748
		if (snap) {
3749
			zoom = Math.round(zoom / snap) * snap;
3750
		}
3751
		return Math.max(min, Math.min(max, zoom));
3752
	},
3753
3754
	_onPanTransitionStep: function () {
3755
		this.fire('move');
3756
	},
3757
3758
	_onPanTransitionEnd: function () {
3759
		L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
3760
		this.fire('moveend');
3761
	},
3762
3763
	_tryAnimatedPan: function (center, options) {
3764
		// difference between the new and current centers in pixels
3765
		var offset = this._getCenterOffset(center)._floor();
3766
3767
		// don't animate too far unless animate: true specified in options
3768
		if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
3769
3770
		this.panBy(offset, options);
3771
3772
		return true;
3773
	},
3774
3775
	_createAnimProxy: function () {
3776
3777
		var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
3778
		this._panes.mapPane.appendChild(proxy);
3779
3780
		this.on('zoomanim', function (e) {
3781
			var prop = L.DomUtil.TRANSFORM,
3782
			    transform = proxy.style[prop];
3783
3784
			L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
3785
3786
			// workaround for case when transform is the same and so transitionend event is not fired
3787
			if (transform === proxy.style[prop] && this._animatingZoom) {
3788
				this._onZoomTransitionEnd();
3789
			}
3790
		}, this);
3791
3792
		this.on('load moveend', function () {
3793
			var c = this.getCenter(),
3794
			    z = this.getZoom();
3795
			L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
3796
		}, this);
3797
	},
3798
3799
	_catchTransitionEnd: function (e) {
3800
		if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
3801
			this._onZoomTransitionEnd();
3802
		}
3803
	},
3804
3805
	_nothingToAnimate: function () {
3806
		return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
3807
	},
3808
3809
	_tryAnimatedZoom: function (center, zoom, options) {
3810
3811
		if (this._animatingZoom) { return true; }
3812
3813
		options = options || {};
3814
3815
		// don't animate if disabled, not supported or zoom difference is too large
3816
		if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
3817
		        Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
3818
3819
		// offset is the pixel coords of the zoom origin relative to the current center
3820
		var scale = this.getZoomScale(zoom),
3821
		    offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
3822
3823
		// don't animate if the zoom origin isn't within one screen from the current center, unless forced
3824
		if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
3825
3826
		L.Util.requestAnimFrame(function () {
3827
			this
3828
			    ._moveStart(true)
3829
			    ._animateZoom(center, zoom, true);
3830
		}, this);
3831
3832
		return true;
3833
	},
3834
3835
	_animateZoom: function (center, zoom, startAnim, noUpdate) {
3836
		if (startAnim) {
3837
			this._animatingZoom = true;
3838
3839
			// remember what center/zoom to set after animation
3840
			this._animateToCenter = center;
3841
			this._animateToZoom = zoom;
3842
3843
			L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
3844
		}
3845
3846
		// @event zoomanim: ZoomAnimEvent
3847
		// Fired on every frame of a zoom animation
3848
		this.fire('zoomanim', {
3849
			center: center,
3850
			zoom: zoom,
3851
			noUpdate: noUpdate
3852
		});
3853
3854
		// Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
3855
		setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
3856
	},
3857
3858
	_onZoomTransitionEnd: function () {
3859
		if (!this._animatingZoom) { return; }
3860
3861
		L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
3862
3863
		this._animatingZoom = false;
3864
3865
		this._move(this._animateToCenter, this._animateToZoom);
3866
3867
		// This anim frame should prevent an obscure iOS webkit tile loading race condition.
3868
		L.Util.requestAnimFrame(function () {
3869
			this._moveEnd(true);
3870
		}, this);
3871
	}
3872
});
3873
3874
// @section
3875
3876
// @factory L.map(id: String, options?: Map options)
3877
// Instantiates a map object given the DOM ID of a `<div>` element
3878
// and optionally an object literal with `Map options`.
3879
//
3880
// @alternative
3881
// @factory L.map(el: HTMLElement, options?: Map options)
3882
// Instantiates a map object given an instance of a `<div>` HTML element
3883
// and optionally an object literal with `Map options`.
3884
L.map = function (id, options) {
3885
	return new L.Map(id, options);
3886
};
3887
3888
3889
3890
3891
/*
3892
 * @class Layer
3893
 * @inherits Evented
3894
 * @aka L.Layer
3895
 * @aka ILayer
3896
 *
3897
 * A set of methods from the Layer base class that all Leaflet layers use.
3898
 * Inherits all methods, options and events from `L.Evented`.
3899
 *
3900
 * @example
3901
 *
3902
 * ```js
3903
 * var layer = L.Marker(latlng).addTo(map);
3904
 * layer.addTo(map);
3905
 * layer.remove();
3906
 * ```
3907
 *
3908
 * @event add: Event
3909
 * Fired after the layer is added to a map
3910
 *
3911
 * @event remove: Event
3912
 * Fired after the layer is removed from a map
3913
 */
3914
3915
3916
L.Layer = L.Evented.extend({
3917
3918
	// Classes extending `L.Layer` will inherit the following options:
3919
	options: {
3920
		// @option pane: String = 'overlayPane'
3921
		// 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.
3922
		pane: 'overlayPane',
3923
		nonBubblingEvents: [],  // Array of events that should not be bubbled to DOM parents (like the map),
3924
3925
		// @option attribution: String = null
3926
		// String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3927
		attribution: null
3928
	},
3929
3930
	/* @section
3931
	 * Classes extending `L.Layer` will inherit the following methods:
3932
	 *
3933
	 * @method addTo(map: Map): this
3934
	 * Adds the layer to the given map
3935
	 */
3936
	addTo: function (map) {
3937
		map.addLayer(this);
3938
		return this;
3939
	},
3940
3941
	// @method remove: this
3942
	// Removes the layer from the map it is currently active on.
3943
	remove: function () {
3944
		return this.removeFrom(this._map || this._mapToAdd);
3945
	},
3946
3947
	// @method removeFrom(map: Map): this
3948
	// Removes the layer from the given map
3949
	removeFrom: function (obj) {
3950
		if (obj) {
3951
			obj.removeLayer(this);
3952
		}
3953
		return this;
3954
	},
3955
3956
	// @method getPane(name? : String): HTMLElement
3957
	// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3958
	getPane: function (name) {
3959
		return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3960
	},
3961
3962
	addInteractiveTarget: function (targetEl) {
3963
		this._map._targets[L.stamp(targetEl)] = this;
3964
		return this;
3965
	},
3966
3967
	removeInteractiveTarget: function (targetEl) {
3968
		delete this._map._targets[L.stamp(targetEl)];
3969
		return this;
3970
	},
3971
3972
	// @method getAttribution: String
3973
	// Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3974
	getAttribution: function () {
3975
		return this.options.attribution;
3976
	},
3977
3978
	_layerAdd: function (e) {
3979
		var map = e.target;
3980
3981
		// check in case layer gets added and then removed before the map is ready
3982
		if (!map.hasLayer(this)) { return; }
3983
3984
		this._map = map;
3985
		this._zoomAnimated = map._zoomAnimated;
3986
3987
		if (this.getEvents) {
3988
			var events = this.getEvents();
3989
			map.on(events, this);
3990
			this.once('remove', function () {
3991
				map.off(events, this);
3992
			}, this);
3993
		}
3994
3995
		this.onAdd(map);
3996
3997
		if (this.getAttribution && map.attributionControl) {
3998
			map.attributionControl.addAttribution(this.getAttribution());
3999
		}
4000
4001
		this.fire('add');
4002
		map.fire('layeradd', {layer: this});
4003
	}
4004
});
4005
4006
/* @section Extension methods
4007
 * @uninheritable
4008
 *
4009
 * Every layer should extend from `L.Layer` and (re-)implement the following methods.
4010
 *
4011
 * @method onAdd(map: Map): this
4012
 * 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).
4013
 *
4014
 * @method onRemove(map: Map): this
4015
 * 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).
4016
 *
4017
 * @method getEvents(): Object
4018
 * 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.
4019
 *
4020
 * @method getAttribution(): String
4021
 * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
4022
 *
4023
 * @method beforeAdd(map: Map): this
4024
 * 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.
4025
 */
4026
4027
4028
/* @namespace Map
4029
 * @section Layer events
4030
 *
4031
 * @event layeradd: LayerEvent
4032
 * Fired when a new layer is added to the map.
4033
 *
4034
 * @event layerremove: LayerEvent
4035
 * Fired when some layer is removed from the map
4036
 *
4037
 * @section Methods for Layers and Controls
4038
 */
4039
L.Map.include({
4040
	// @method addLayer(layer: Layer): this
4041
	// Adds the given layer to the map
4042
	addLayer: function (layer) {
4043
		var id = L.stamp(layer);
4044
		if (this._layers[id]) { return this; }
4045
		this._layers[id] = layer;
4046
4047
		layer._mapToAdd = this;
4048
4049
		if (layer.beforeAdd) {
4050
			layer.beforeAdd(this);
4051
		}
4052
4053
		this.whenReady(layer._layerAdd, layer);
4054
4055
		return this;
4056
	},
4057
4058
	// @method removeLayer(layer: Layer): this
4059
	// Removes the given layer from the map.
4060
	removeLayer: function (layer) {
4061
		var id = L.stamp(layer);
4062
4063
		if (!this._layers[id]) { return this; }
4064
4065
		if (this._loaded) {
4066
			layer.onRemove(this);
4067
		}
4068
4069
		if (layer.getAttribution && this.attributionControl) {
4070
			this.attributionControl.removeAttribution(layer.getAttribution());
4071
		}
4072
4073
		delete this._layers[id];
4074
4075
		if (this._loaded) {
4076
			this.fire('layerremove', {layer: layer});
4077
			layer.fire('remove');
4078
		}
4079
4080
		layer._map = layer._mapToAdd = null;
4081
4082
		return this;
4083
	},
4084
4085
	// @method hasLayer(layer: Layer): Boolean
4086
	// Returns `true` if the given layer is currently added to the map
4087
	hasLayer: function (layer) {
4088
		return !!layer && (L.stamp(layer) in this._layers);
4089
	},
4090
4091
	/* @method eachLayer(fn: Function, context?: Object): this
4092
	 * Iterates over the layers of the map, optionally specifying context of the iterator function.
4093
	 * ```
4094
	 * map.eachLayer(function(layer){
4095
	 *     layer.bindPopup('Hello');
4096
	 * });
4097
	 * ```
4098
	 */
4099
	eachLayer: function (method, context) {
4100
		for (var i in this._layers) {
4101
			method.call(context, this._layers[i]);
4102
		}
4103
		return this;
4104
	},
4105
4106
	_addLayers: function (layers) {
4107
		layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
4108
4109
		for (var i = 0, len = layers.length; i < len; i++) {
4110
			this.addLayer(layers[i]);
4111
		}
4112
	},
4113
4114
	_addZoomLimit: function (layer) {
4115
		if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
4116
			this._zoomBoundLayers[L.stamp(layer)] = layer;
4117
			this._updateZoomLevels();
4118
		}
4119
	},
4120
4121
	_removeZoomLimit: function (layer) {
4122
		var id = L.stamp(layer);
4123
4124
		if (this._zoomBoundLayers[id]) {
4125
			delete this._zoomBoundLayers[id];
4126
			this._updateZoomLevels();
4127
		}
4128
	},
4129
4130
	_updateZoomLevels: function () {
4131
		var minZoom = Infinity,
4132
		    maxZoom = -Infinity,
4133
		    oldZoomSpan = this._getZoomSpan();
4134
4135
		for (var i in this._zoomBoundLayers) {
4136
			var options = this._zoomBoundLayers[i].options;
4137
4138
			minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
4139
			maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
4140
		}
4141
4142
		this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
4143
		this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
4144
4145
		// @section Map state change events
4146
		// @event zoomlevelschange: Event
4147
		// Fired when the number of zoomlevels on the map is changed due
4148
		// to adding or removing a layer.
4149
		if (oldZoomSpan !== this._getZoomSpan()) {
4150
			this.fire('zoomlevelschange');
4151
		}
4152
4153
		if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
4154
			this.setZoom(this._layersMaxZoom);
4155
		}
4156
		if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
4157
			this.setZoom(this._layersMinZoom);
4158
		}
4159
	}
4160
});
4161
4162
4163
4164
/*
4165
 * @namespace DomEvent
4166
 * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
4167
 */
4168
4169
// Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
4170
4171
4172
4173
var eventsKey = '_leaflet_events';
4174
4175
L.DomEvent = {
4176
4177
	// @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
4178
	// Adds a listener function (`fn`) to a particular DOM event type of the
4179
	// element `el`. You can optionally specify the context of the listener
4180
	// (object the `this` keyword will point to). You can also pass several
4181
	// space-separated types (e.g. `'click dblclick'`).
4182
4183
	// @alternative
4184
	// @function on(el: HTMLElement, eventMap: Object, context?: Object): this
4185
	// Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4186
	on: function (obj, types, fn, context) {
4187
4188
		if (typeof types === 'object') {
4189
			for (var type in types) {
4190
				this._on(obj, type, types[type], fn);
4191
			}
4192
		} else {
4193
			types = L.Util.splitWords(types);
4194
4195
			for (var i = 0, len = types.length; i < len; i++) {
4196
				this._on(obj, types[i], fn, context);
4197
			}
4198
		}
4199
4200
		return this;
4201
	},
4202
4203
	// @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
4204
	// Removes a previously added listener function. If no function is specified,
4205
	// it will remove all the listeners of that particular DOM event from the element.
4206
	// Note that if you passed a custom context to on, you must pass the same
4207
	// context to `off` in order to remove the listener.
4208
4209
	// @alternative
4210
	// @function off(el: HTMLElement, eventMap: Object, context?: Object): this
4211
	// Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4212
	off: function (obj, types, fn, context) {
4213
4214
		if (typeof types === 'object') {
4215
			for (var type in types) {
4216
				this._off(obj, type, types[type], fn);
4217
			}
4218
		} else {
4219
			types = L.Util.splitWords(types);
4220
4221
			for (var i = 0, len = types.length; i < len; i++) {
4222
				this._off(obj, types[i], fn, context);
4223
			}
4224
		}
4225
4226
		return this;
4227
	},
4228
4229
	_on: function (obj, type, fn, context) {
4230
		var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
4231
4232
		if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
4233
4234
		var handler = function (e) {
4235
			return fn.call(context || obj, e || window.event);
4236
		};
4237
4238
		var originalHandler = handler;
4239
4240
		if (L.Browser.pointer && type.indexOf('touch') === 0) {
4241
			this.addPointerListener(obj, type, handler, id);
4242
4243
		} else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener &&
4244
		           !(L.Browser.pointer && L.Browser.chrome)) {
4245
			// Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
4246
			// See #5180
4247
			this.addDoubleTapListener(obj, handler, id);
4248
4249
		} else if ('addEventListener' in obj) {
4250
4251
			if (type === 'mousewheel') {
4252
				obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4253
4254
			} else if ((type === 'mouseenter') || (type === 'mouseleave')) {
4255
				handler = function (e) {
4256
					e = e || window.event;
4257
					if (L.DomEvent._isExternalTarget(obj, e)) {
4258
						originalHandler(e);
4259
					}
4260
				};
4261
				obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
4262
4263
			} else {
4264
				if (type === 'click' && L.Browser.android) {
4265
					handler = function (e) {
4266
						return L.DomEvent._filterClick(e, originalHandler);
4267
					};
4268
				}
4269
				obj.addEventListener(type, handler, false);
4270
			}
4271
4272
		} else if ('attachEvent' in obj) {
4273
			obj.attachEvent('on' + type, handler);
4274
		}
4275
4276
		obj[eventsKey] = obj[eventsKey] || {};
4277
		obj[eventsKey][id] = handler;
4278
4279
		return this;
4280
	},
4281
4282
	_off: function (obj, type, fn, context) {
4283
4284
		var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
4285
		    handler = obj[eventsKey] && obj[eventsKey][id];
4286
4287
		if (!handler) { return this; }
4288
4289
		if (L.Browser.pointer && type.indexOf('touch') === 0) {
4290
			this.removePointerListener(obj, type, id);
4291
4292
		} else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
4293
			this.removeDoubleTapListener(obj, id);
4294
4295
		} else if ('removeEventListener' in obj) {
4296
4297
			if (type === 'mousewheel') {
4298
				obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4299
4300
			} else {
4301
				obj.removeEventListener(
4302
					type === 'mouseenter' ? 'mouseover' :
4303
					type === 'mouseleave' ? 'mouseout' : type, handler, false);
4304
			}
4305
4306
		} else if ('detachEvent' in obj) {
4307
			obj.detachEvent('on' + type, handler);
4308
		}
4309
4310
		obj[eventsKey][id] = null;
4311
4312
		return this;
4313
	},
4314
4315
	// @function stopPropagation(ev: DOMEvent): this
4316
	// Stop the given event from propagation to parent elements. Used inside the listener functions:
4317
	// ```js
4318
	// L.DomEvent.on(div, 'click', function (ev) {
4319
	// 	L.DomEvent.stopPropagation(ev);
4320
	// });
4321
	// ```
4322
	stopPropagation: function (e) {
4323
4324
		if (e.stopPropagation) {
4325
			e.stopPropagation();
4326
		} else if (e.originalEvent) {  // In case of Leaflet event.
4327
			e.originalEvent._stopped = true;
4328
		} else {
4329
			e.cancelBubble = true;
4330
		}
4331
		L.DomEvent._skipped(e);
4332
4333
		return this;
4334
	},
4335
4336
	// @function disableScrollPropagation(el: HTMLElement): this
4337
	// Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
4338
	disableScrollPropagation: function (el) {
4339
		return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
4340
	},
4341
4342
	// @function disableClickPropagation(el: HTMLElement): this
4343
	// Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
4344
	// `'mousedown'` and `'touchstart'` events (plus browser variants).
4345
	disableClickPropagation: function (el) {
4346
		var stop = L.DomEvent.stopPropagation;
4347
4348
		L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
4349
4350
		return L.DomEvent.on(el, {
4351
			click: L.DomEvent._fakeStop,
4352
			dblclick: stop
4353
		});
4354
	},
4355
4356
	// @function preventDefault(ev: DOMEvent): this
4357
	// Prevents the default action of the DOM Event `ev` from happening (such as
4358
	// following a link in the href of the a element, or doing a POST request
4359
	// with page reload when a `<form>` is submitted).
4360
	// Use it inside listener functions.
4361
	preventDefault: function (e) {
4362
4363
		if (e.preventDefault) {
4364
			e.preventDefault();
4365
		} else {
4366
			e.returnValue = false;
4367
		}
4368
		return this;
4369
	},
4370
4371
	// @function stop(ev): this
4372
	// Does `stopPropagation` and `preventDefault` at the same time.
4373
	stop: function (e) {
4374
		return L.DomEvent
4375
			.preventDefault(e)
4376
			.stopPropagation(e);
4377
	},
4378
4379
	// @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
4380
	// Gets normalized mouse position from a DOM event relative to the
4381
	// `container` or to the whole page if not specified.
4382
	getMousePosition: function (e, container) {
4383
		if (!container) {
4384
			return new L.Point(e.clientX, e.clientY);
4385
		}
4386
4387
		var rect = container.getBoundingClientRect();
4388
4389
		return new L.Point(
4390
			e.clientX - rect.left - container.clientLeft,
4391
			e.clientY - rect.top - container.clientTop);
4392
	},
4393
4394
	// Chrome on Win scrolls double the pixels as in other platforms (see #4538),
4395
	// and Firefox scrolls device pixels, not CSS pixels
4396
	_wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
4397
	                L.Browser.gecko ? window.devicePixelRatio :
4398
	                1,
4399
4400
	// @function getWheelDelta(ev: DOMEvent): Number
4401
	// Gets normalized wheel delta from a mousewheel DOM event, in vertical
4402
	// pixels scrolled (negative if scrolling down).
4403
	// Events from pointing devices without precise scrolling are mapped to
4404
	// a best guess of 60 pixels.
4405
	getWheelDelta: function (e) {
4406
		return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
4407
		       (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
4408
		       (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
4409
		       (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
4410
		       (e.deltaX || e.deltaZ) ? 0 :	// Skip horizontal/depth wheel events
4411
		       e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
4412
		       (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
4413
		       e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
4414
		       0;
4415
	},
4416
4417
	_skipEvents: {},
4418
4419
	_fakeStop: function (e) {
4420
		// fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
4421
		L.DomEvent._skipEvents[e.type] = true;
4422
	},
4423
4424
	_skipped: function (e) {
4425
		var skipped = this._skipEvents[e.type];
4426
		// reset when checking, as it's only used in map container and propagates outside of the map
4427
		this._skipEvents[e.type] = false;
4428
		return skipped;
4429
	},
4430
4431
	// check if element really left/entered the event target (for mouseenter/mouseleave)
4432
	_isExternalTarget: function (el, e) {
4433
4434
		var related = e.relatedTarget;
4435
4436
		if (!related) { return true; }
4437
4438
		try {
4439
			while (related && (related !== el)) {
4440
				related = related.parentNode;
4441
			}
4442
		} catch (err) {
4443
			return false;
4444
		}
4445
		return (related !== el);
4446
	},
4447
4448
	// this is a horrible workaround for a bug in Android where a single touch triggers two click events
4449
	_filterClick: function (e, handler) {
4450
		var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
4451
		    elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
4452
4453
		// are they closer together than 500ms yet more than 100ms?
4454
		// Android typically triggers them ~300ms apart while multiple listeners
4455
		// on the same event should be triggered far faster;
4456
		// or check if click is simulated on the element, and if it is, reject any non-simulated events
4457
4458
		if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
4459
			L.DomEvent.stop(e);
4460
			return;
4461
		}
4462
		L.DomEvent._lastClick = timeStamp;
4463
4464
		handler(e);
4465
	}
4466
};
4467
4468
// @function addListener(…): this
4469
// Alias to [`L.DomEvent.on`](#domevent-on)
4470
L.DomEvent.addListener = L.DomEvent.on;
4471
4472
// @function removeListener(…): this
4473
// Alias to [`L.DomEvent.off`](#domevent-off)
4474
L.DomEvent.removeListener = L.DomEvent.off;
4475
4476
4477
4478
/*
4479
 * @class PosAnimation
4480
 * @aka L.PosAnimation
4481
 * @inherits Evented
4482
 * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
4483
 *
4484
 * @example
4485
 * ```js
4486
 * var fx = new L.PosAnimation();
4487
 * fx.run(el, [300, 500], 0.5);
4488
 * ```
4489
 *
4490
 * @constructor L.PosAnimation()
4491
 * Creates a `PosAnimation` object.
4492
 *
4493
 */
4494
4495
L.PosAnimation = L.Evented.extend({
4496
4497
	// @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
4498
	// Run an animation of a given element to a new position, optionally setting
4499
	// duration in seconds (`0.25` by default) and easing linearity factor (3rd
4500
	// argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
4501
	// `0.5` by default).
4502
	run: function (el, newPos, duration, easeLinearity) {
4503
		this.stop();
4504
4505
		this._el = el;
4506
		this._inProgress = true;
4507
		this._duration = duration || 0.25;
4508
		this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
4509
4510
		this._startPos = L.DomUtil.getPosition(el);
4511
		this._offset = newPos.subtract(this._startPos);
4512
		this._startTime = +new Date();
4513
4514
		// @event start: Event
4515
		// Fired when the animation starts
4516
		this.fire('start');
4517
4518
		this._animate();
4519
	},
4520
4521
	// @method stop()
4522
	// Stops the animation (if currently running).
4523
	stop: function () {
4524
		if (!this._inProgress) { return; }
4525
4526
		this._step(true);
4527
		this._complete();
4528
	},
4529
4530
	_animate: function () {
4531
		// animation loop
4532
		this._animId = L.Util.requestAnimFrame(this._animate, this);
4533
		this._step();
4534
	},
4535
4536
	_step: function (round) {
4537
		var elapsed = (+new Date()) - this._startTime,
4538
		    duration = this._duration * 1000;
4539
4540
		if (elapsed < duration) {
4541
			this._runFrame(this._easeOut(elapsed / duration), round);
4542
		} else {
4543
			this._runFrame(1);
4544
			this._complete();
4545
		}
4546
	},
4547
4548
	_runFrame: function (progress, round) {
4549
		var pos = this._startPos.add(this._offset.multiplyBy(progress));
4550
		if (round) {
4551
			pos._round();
4552
		}
4553
		L.DomUtil.setPosition(this._el, pos);
4554
4555
		// @event step: Event
4556
		// Fired continuously during the animation.
4557
		this.fire('step');
4558
	},
4559
4560
	_complete: function () {
4561
		L.Util.cancelAnimFrame(this._animId);
4562
4563
		this._inProgress = false;
4564
		// @event end: Event
4565
		// Fired when the animation ends.
4566
		this.fire('end');
4567
	},
4568
4569
	_easeOut: function (t) {
4570
		return 1 - Math.pow(1 - t, this._easeOutPower);
4571
	}
4572
});
4573
4574
4575
4576
/*
4577
 * @namespace Projection
4578
 * @projection L.Projection.Mercator
4579
 *
4580
 * 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.
4581
 */
4582
4583
L.Projection.Mercator = {
4584
	R: 6378137,
4585
	R_MINOR: 6356752.314245179,
4586
4587
	bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
4588
4589
	project: function (latlng) {
4590
		var d = Math.PI / 180,
4591
		    r = this.R,
4592
		    y = latlng.lat * d,
4593
		    tmp = this.R_MINOR / r,
4594
		    e = Math.sqrt(1 - tmp * tmp),
4595
		    con = e * Math.sin(y);
4596
4597
		var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
4598
		y = -r * Math.log(Math.max(ts, 1E-10));
4599
4600
		return new L.Point(latlng.lng * d * r, y);
4601
	},
4602
4603
	unproject: function (point) {
4604
		var d = 180 / Math.PI,
4605
		    r = this.R,
4606
		    tmp = this.R_MINOR / r,
4607
		    e = Math.sqrt(1 - tmp * tmp),
4608
		    ts = Math.exp(-point.y / r),
4609
		    phi = Math.PI / 2 - 2 * Math.atan(ts);
4610
4611
		for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
4612
			con = e * Math.sin(phi);
4613
			con = Math.pow((1 - con) / (1 + con), e / 2);
4614
			dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
4615
			phi += dphi;
4616
		}
4617
4618
		return new L.LatLng(phi * d, point.x * d / r);
4619
	}
4620
};
4621
4622
4623
4624
/*
4625
 * @namespace CRS
4626
 * @crs L.CRS.EPSG3395
4627
 *
4628
 * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
4629
 */
4630
4631
L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
4632
	code: 'EPSG:3395',
4633
	projection: L.Projection.Mercator,
4634
4635
	transformation: (function () {
4636
		var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
4637
		return new L.Transformation(scale, 0.5, -scale, 0.5);
4638
	}())
4639
});
4640
4641
4642
4643
/*
4644
 * @class GridLayer
4645
 * @inherits Layer
4646
 * @aka L.GridLayer
4647
 *
4648
 * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
4649
 * 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.
4650
 *
4651
 *
4652
 * @section Synchronous usage
4653
 * @example
4654
 *
4655
 * 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.
4656
 *
4657
 * ```js
4658
 * var CanvasLayer = L.GridLayer.extend({
4659
 *     createTile: function(coords){
4660
 *         // create a <canvas> element for drawing
4661
 *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4662
 *
4663
 *         // setup tile width and height according to the options
4664
 *         var size = this.getTileSize();
4665
 *         tile.width = size.x;
4666
 *         tile.height = size.y;
4667
 *
4668
 *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
4669
 *         var ctx = tile.getContext('2d');
4670
 *
4671
 *         // return the tile so it can be rendered on screen
4672
 *         return tile;
4673
 *     }
4674
 * });
4675
 * ```
4676
 *
4677
 * @section Asynchronous usage
4678
 * @example
4679
 *
4680
 * 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.
4681
 *
4682
 * ```js
4683
 * var CanvasLayer = L.GridLayer.extend({
4684
 *     createTile: function(coords, done){
4685
 *         var error;
4686
 *
4687
 *         // create a <canvas> element for drawing
4688
 *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4689
 *
4690
 *         // setup tile width and height according to the options
4691
 *         var size = this.getTileSize();
4692
 *         tile.width = size.x;
4693
 *         tile.height = size.y;
4694
 *
4695
 *         // draw something asynchronously and pass the tile to the done() callback
4696
 *         setTimeout(function() {
4697
 *             done(error, tile);
4698
 *         }, 1000);
4699
 *
4700
 *         return tile;
4701
 *     }
4702
 * });
4703
 * ```
4704
 *
4705
 * @section
4706
 */
4707
4708
4709
L.GridLayer = L.Layer.extend({
4710
4711
	// @section
4712
	// @aka GridLayer options
4713
	options: {
4714
		// @option tileSize: Number|Point = 256
4715
		// Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
4716
		tileSize: 256,
4717
4718
		// @option opacity: Number = 1.0
4719
		// Opacity of the tiles. Can be used in the `createTile()` function.
4720
		opacity: 1,
4721
4722
		// @option updateWhenIdle: Boolean = depends
4723
		// If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
4724
		updateWhenIdle: L.Browser.mobile,
4725
4726
		// @option updateWhenZooming: Boolean = true
4727
		// 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.
4728
		updateWhenZooming: true,
4729
4730
		// @option updateInterval: Number = 200
4731
		// Tiles will not update more than once every `updateInterval` milliseconds when panning.
4732
		updateInterval: 200,
4733
4734
		// @option zIndex: Number = 1
4735
		// The explicit zIndex of the tile layer.
4736
		zIndex: 1,
4737
4738
		// @option bounds: LatLngBounds = undefined
4739
		// If set, tiles will only be loaded inside the set `LatLngBounds`.
4740
		bounds: null,
4741
4742
		// @option minZoom: Number = 0
4743
		// The minimum zoom level that tiles will be loaded at. By default the entire map.
4744
		minZoom: 0,
4745
4746
		// @option maxZoom: Number = undefined
4747
		// The maximum zoom level that tiles will be loaded at.
4748
		maxZoom: undefined,
4749
4750
		// @option noWrap: Boolean = false
4751
		// Whether the layer is wrapped around the antimeridian. If `true`, the
4752
		// GridLayer will only be displayed once at low zoom levels. Has no
4753
		// effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
4754
		// in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
4755
		// tiles outside the CRS limits.
4756
		noWrap: false,
4757
4758
		// @option pane: String = 'tilePane'
4759
		// `Map pane` where the grid layer will be added.
4760
		pane: 'tilePane',
4761
4762
		// @option className: String = ''
4763
		// A custom class name to assign to the tile layer. Empty by default.
4764
		className: '',
4765
4766
		// @option keepBuffer: Number = 2
4767
		// When panning the map, keep this many rows and columns of tiles before unloading them.
4768
		keepBuffer: 2
4769
	},
4770
4771
	initialize: function (options) {
4772
		L.setOptions(this, options);
4773
	},
4774
4775
	onAdd: function () {
4776
		this._initContainer();
4777
4778
		this._levels = {};
4779
		this._tiles = {};
4780
4781
		this._resetView();
4782
		this._update();
4783
	},
4784
4785
	beforeAdd: function (map) {
4786
		map._addZoomLimit(this);
4787
	},
4788
4789
	onRemove: function (map) {
4790
		this._removeAllTiles();
4791
		L.DomUtil.remove(this._container);
4792
		map._removeZoomLimit(this);
4793
		this._container = null;
4794
		this._tileZoom = null;
4795
	},
4796
4797
	// @method bringToFront: this
4798
	// Brings the tile layer to the top of all tile layers.
4799
	bringToFront: function () {
4800
		if (this._map) {
4801
			L.DomUtil.toFront(this._container);
4802
			this._setAutoZIndex(Math.max);
4803
		}
4804
		return this;
4805
	},
4806
4807
	// @method bringToBack: this
4808
	// Brings the tile layer to the bottom of all tile layers.
4809
	bringToBack: function () {
4810
		if (this._map) {
4811
			L.DomUtil.toBack(this._container);
4812
			this._setAutoZIndex(Math.min);
4813
		}
4814
		return this;
4815
	},
4816
4817
	// @method getContainer: HTMLElement
4818
	// Returns the HTML element that contains the tiles for this layer.
4819
	getContainer: function () {
4820
		return this._container;
4821
	},
4822
4823
	// @method setOpacity(opacity: Number): this
4824
	// Changes the [opacity](#gridlayer-opacity) of the grid layer.
4825
	setOpacity: function (opacity) {
4826
		this.options.opacity = opacity;
4827
		this._updateOpacity();
4828
		return this;
4829
	},
4830
4831
	// @method setZIndex(zIndex: Number): this
4832
	// Changes the [zIndex](#gridlayer-zindex) of the grid layer.
4833
	setZIndex: function (zIndex) {
4834
		this.options.zIndex = zIndex;
4835
		this._updateZIndex();
4836
4837
		return this;
4838
	},
4839
4840
	// @method isLoading: Boolean
4841
	// Returns `true` if any tile in the grid layer has not finished loading.
4842
	isLoading: function () {
4843
		return this._loading;
4844
	},
4845
4846
	// @method redraw: this
4847
	// Causes the layer to clear all the tiles and request them again.
4848
	redraw: function () {
4849
		if (this._map) {
4850
			this._removeAllTiles();
4851
			this._update();
4852
		}
4853
		return this;
4854
	},
4855
4856
	getEvents: function () {
4857
		var events = {
4858
			viewprereset: this._invalidateAll,
4859
			viewreset: this._resetView,
4860
			zoom: this._resetView,
4861
			moveend: this._onMoveEnd
4862
		};
4863
4864
		if (!this.options.updateWhenIdle) {
4865
			// update tiles on move, but not more often than once per given interval
4866
			if (!this._onMove) {
4867
				this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4868
			}
4869
4870
			events.move = this._onMove;
4871
		}
4872
4873
		if (this._zoomAnimated) {
4874
			events.zoomanim = this._animateZoom;
4875
		}
4876
4877
		return events;
4878
	},
4879
4880
	// @section Extension methods
4881
	// Layers extending `GridLayer` shall reimplement the following method.
4882
	// @method createTile(coords: Object, done?: Function): HTMLElement
4883
	// Called only internally, must be overriden by classes extending `GridLayer`.
4884
	// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4885
	// is specified, it must be called when the tile has finished loading and drawing.
4886
	createTile: function () {
4887
		return document.createElement('div');
4888
	},
4889
4890
	// @section
4891
	// @method getTileSize: Point
4892
	// Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4893
	getTileSize: function () {
4894
		var s = this.options.tileSize;
4895
		return s instanceof L.Point ? s : new L.Point(s, s);
4896
	},
4897
4898
	_updateZIndex: function () {
4899
		if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4900
			this._container.style.zIndex = this.options.zIndex;
4901
		}
4902
	},
4903
4904
	_setAutoZIndex: function (compare) {
4905
		// go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4906
4907
		var layers = this.getPane().children,
4908
		    edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4909
4910
		for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4911
4912
			zIndex = layers[i].style.zIndex;
4913
4914
			if (layers[i] !== this._container && zIndex) {
4915
				edgeZIndex = compare(edgeZIndex, +zIndex);
4916
			}
4917
		}
4918
4919
		if (isFinite(edgeZIndex)) {
4920
			this.options.zIndex = edgeZIndex + compare(-1, 1);
4921
			this._updateZIndex();
4922
		}
4923
	},
4924
4925
	_updateOpacity: function () {
4926
		if (!this._map) { return; }
4927
4928
		// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4929
		if (L.Browser.ielt9) { return; }
4930
4931
		L.DomUtil.setOpacity(this._container, this.options.opacity);
4932
4933
		var now = +new Date(),
4934
		    nextFrame = false,
4935
		    willPrune = false;
4936
4937
		for (var key in this._tiles) {
4938
			var tile = this._tiles[key];
4939
			if (!tile.current || !tile.loaded) { continue; }
4940
4941
			var fade = Math.min(1, (now - tile.loaded) / 200);
4942
4943
			L.DomUtil.setOpacity(tile.el, fade);
4944
			if (fade < 1) {
4945
				nextFrame = true;
4946
			} else {
4947
				if (tile.active) { willPrune = true; }
4948
				tile.active = true;
4949
			}
4950
		}
4951
4952
		if (willPrune && !this._noPrune) { this._pruneTiles(); }
4953
4954
		if (nextFrame) {
4955
			L.Util.cancelAnimFrame(this._fadeFrame);
4956
			this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4957
		}
4958
	},
4959
4960
	_initContainer: function () {
4961
		if (this._container) { return; }
4962
4963
		this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4964
		this._updateZIndex();
4965
4966
		if (this.options.opacity < 1) {
4967
			this._updateOpacity();
4968
		}
4969
4970
		this.getPane().appendChild(this._container);
4971
	},
4972
4973
	_updateLevels: function () {
4974
4975
		var zoom = this._tileZoom,
4976
		    maxZoom = this.options.maxZoom;
4977
4978
		if (zoom === undefined) { return undefined; }
4979
4980
		for (var z in this._levels) {
4981
			if (this._levels[z].el.children.length || z === zoom) {
4982
				this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4983
			} else {
4984
				L.DomUtil.remove(this._levels[z].el);
4985
				this._removeTilesAtZoom(z);
4986
				delete this._levels[z];
4987
			}
4988
		}
4989
4990
		var level = this._levels[zoom],
4991
		    map = this._map;
4992
4993
		if (!level) {
4994
			level = this._levels[zoom] = {};
4995
4996
			level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4997
			level.el.style.zIndex = maxZoom;
4998
4999
			level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
5000
			level.zoom = zoom;
5001
5002
			this._setZoomTransform(level, map.getCenter(), map.getZoom());
5003
5004
			// force the browser to consider the newly added element for transition
5005
			L.Util.falseFn(level.el.offsetWidth);
5006
		}
5007
5008
		this._level = level;
5009
5010
		return level;
5011
	},
5012
5013
	_pruneTiles: function () {
5014
		if (!this._map) {
5015
			return;
5016
		}
5017
5018
		var key, tile;
5019
5020
		var zoom = this._map.getZoom();
5021
		if (zoom > this.options.maxZoom ||
5022
			zoom < this.options.minZoom) {
5023
			this._removeAllTiles();
5024
			return;
5025
		}
5026
5027
		for (key in this._tiles) {
5028
			tile = this._tiles[key];
5029
			tile.retain = tile.current;
5030
		}
5031
5032
		for (key in this._tiles) {
5033
			tile = this._tiles[key];
5034
			if (tile.current && !tile.active) {
5035
				var coords = tile.coords;
5036
				if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
5037
					this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
5038
				}
5039
			}
5040
		}
5041
5042
		for (key in this._tiles) {
5043
			if (!this._tiles[key].retain) {
5044
				this._removeTile(key);
5045
			}
5046
		}
5047
	},
5048
5049
	_removeTilesAtZoom: function (zoom) {
5050
		for (var key in this._tiles) {
5051
			if (this._tiles[key].coords.z !== zoom) {
5052
				continue;
5053
			}
5054
			this._removeTile(key);
5055
		}
5056
	},
5057
5058
	_removeAllTiles: function () {
5059
		for (var key in this._tiles) {
5060
			this._removeTile(key);
5061
		}
5062
	},
5063
5064
	_invalidateAll: function () {
5065
		for (var z in this._levels) {
5066
			L.DomUtil.remove(this._levels[z].el);
5067
			delete this._levels[z];
5068
		}
5069
		this._removeAllTiles();
5070
5071
		this._tileZoom = null;
5072
	},
5073
5074
	_retainParent: function (x, y, z, minZoom) {
5075
		var x2 = Math.floor(x / 2),
5076
		    y2 = Math.floor(y / 2),
5077
		    z2 = z - 1,
5078
		    coords2 = new L.Point(+x2, +y2);
5079
		coords2.z = +z2;
5080
5081
		var key = this._tileCoordsToKey(coords2),
5082
		    tile = this._tiles[key];
5083
5084
		if (tile && tile.active) {
5085
			tile.retain = true;
5086
			return true;
5087
5088
		} else if (tile && tile.loaded) {
5089
			tile.retain = true;
5090
		}
5091
5092
		if (z2 > minZoom) {
5093
			return this._retainParent(x2, y2, z2, minZoom);
5094
		}
5095
5096
		return false;
5097
	},
5098
5099
	_retainChildren: function (x, y, z, maxZoom) {
5100
5101
		for (var i = 2 * x; i < 2 * x + 2; i++) {
5102
			for (var j = 2 * y; j < 2 * y + 2; j++) {
5103
5104
				var coords = new L.Point(i, j);
5105
				coords.z = z + 1;
5106
5107
				var key = this._tileCoordsToKey(coords),
5108
				    tile = this._tiles[key];
5109
5110
				if (tile && tile.active) {
5111
					tile.retain = true;
5112
					continue;
5113
5114
				} else if (tile && tile.loaded) {
5115
					tile.retain = true;
5116
				}
5117
5118
				if (z + 1 < maxZoom) {
5119
					this._retainChildren(i, j, z + 1, maxZoom);
5120
				}
5121
			}
5122
		}
5123
	},
5124
5125
	_resetView: function (e) {
5126
		var animating = e && (e.pinch || e.flyTo);
5127
		this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
5128
	},
5129
5130
	_animateZoom: function (e) {
5131
		this._setView(e.center, e.zoom, true, e.noUpdate);
5132
	},
5133
5134
	_setView: function (center, zoom, noPrune, noUpdate) {
5135
		var tileZoom = Math.round(zoom);
5136
		if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
5137
		    (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
5138
			tileZoom = undefined;
5139
		}
5140
5141
		var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
5142
5143
		if (!noUpdate || tileZoomChanged) {
5144
5145
			this._tileZoom = tileZoom;
5146
5147
			if (this._abortLoading) {
5148
				this._abortLoading();
5149
			}
5150
5151
			this._updateLevels();
5152
			this._resetGrid();
5153
5154
			if (tileZoom !== undefined) {
5155
				this._update(center);
5156
			}
5157
5158
			if (!noPrune) {
5159
				this._pruneTiles();
5160
			}
5161
5162
			// Flag to prevent _updateOpacity from pruning tiles during
5163
			// a zoom anim or a pinch gesture
5164
			this._noPrune = !!noPrune;
5165
		}
5166
5167
		this._setZoomTransforms(center, zoom);
5168
	},
5169
5170
	_setZoomTransforms: function (center, zoom) {
5171
		for (var i in this._levels) {
5172
			this._setZoomTransform(this._levels[i], center, zoom);
5173
		}
5174
	},
5175
5176
	_setZoomTransform: function (level, center, zoom) {
5177
		var scale = this._map.getZoomScale(zoom, level.zoom),
5178
		    translate = level.origin.multiplyBy(scale)
5179
		        .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
5180
5181
		if (L.Browser.any3d) {
5182
			L.DomUtil.setTransform(level.el, translate, scale);
5183
		} else {
5184
			L.DomUtil.setPosition(level.el, translate);
5185
		}
5186
	},
5187
5188
	_resetGrid: function () {
5189
		var map = this._map,
5190
		    crs = map.options.crs,
5191
		    tileSize = this._tileSize = this.getTileSize(),
5192
		    tileZoom = this._tileZoom;
5193
5194
		var bounds = this._map.getPixelWorldBounds(this._tileZoom);
5195
		if (bounds) {
5196
			this._globalTileRange = this._pxBoundsToTileRange(bounds);
5197
		}
5198
5199
		this._wrapX = crs.wrapLng && !this.options.noWrap && [
5200
			Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
5201
			Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
5202
		];
5203
		this._wrapY = crs.wrapLat && !this.options.noWrap && [
5204
			Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
5205
			Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
5206
		];
5207
	},
5208
5209
	_onMoveEnd: function () {
5210
		if (!this._map || this._map._animatingZoom) { return; }
5211
5212
		this._update();
5213
	},
5214
5215
	_getTiledPixelBounds: function (center) {
5216
		var map = this._map,
5217
		    mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
5218
		    scale = map.getZoomScale(mapZoom, this._tileZoom),
5219
		    pixelCenter = map.project(center, this._tileZoom).floor(),
5220
		    halfSize = map.getSize().divideBy(scale * 2);
5221
5222
		return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
5223
	},
5224
5225
	// Private method to load tiles in the grid's active zoom level according to map bounds
5226
	_update: function (center) {
5227
		var map = this._map;
5228
		if (!map) { return; }
5229
		var zoom = map.getZoom();
5230
5231
		if (center === undefined) { center = map.getCenter(); }
5232
		if (this._tileZoom === undefined) { return; }	// if out of minzoom/maxzoom
5233
5234
		var pixelBounds = this._getTiledPixelBounds(center),
5235
		    tileRange = this._pxBoundsToTileRange(pixelBounds),
5236
		    tileCenter = tileRange.getCenter(),
5237
		    queue = [],
5238
		    margin = this.options.keepBuffer,
5239
		    noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
5240
		                              tileRange.getTopRight().add([margin, -margin]));
5241
5242
		for (var key in this._tiles) {
5243
			var c = this._tiles[key].coords;
5244
			if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
5245
				this._tiles[key].current = false;
5246
			}
5247
		}
5248
5249
		// _update just loads more tiles. If the tile zoom level differs too much
5250
		// from the map's, let _setView reset levels and prune old tiles.
5251
		if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
5252
5253
		// create a queue of coordinates to load tiles from
5254
		for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
5255
			for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
5256
				var coords = new L.Point(i, j);
5257
				coords.z = this._tileZoom;
5258
5259
				if (!this._isValidTile(coords)) { continue; }
5260
5261
				var tile = this._tiles[this._tileCoordsToKey(coords)];
5262
				if (tile) {
5263
					tile.current = true;
5264
				} else {
5265
					queue.push(coords);
5266
				}
5267
			}
5268
		}
5269
5270
		// sort tile queue to load tiles in order of their distance to center
5271
		queue.sort(function (a, b) {
5272
			return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
5273
		});
5274
5275
		if (queue.length !== 0) {
5276
			// if it's the first batch of tiles to load
5277
			if (!this._loading) {
5278
				this._loading = true;
5279
				// @event loading: Event
5280
				// Fired when the grid layer starts loading tiles.
5281
				this.fire('loading');
5282
			}
5283
5284
			// create DOM fragment to append tiles in one batch
5285
			var fragment = document.createDocumentFragment();
5286
5287
			for (i = 0; i < queue.length; i++) {
5288
				this._addTile(queue[i], fragment);
5289
			}
5290
5291
			this._level.el.appendChild(fragment);
5292
		}
5293
	},
5294
5295
	_isValidTile: function (coords) {
5296
		var crs = this._map.options.crs;
5297
5298
		if (!crs.infinite) {
5299
			// don't load tile if it's out of bounds and not wrapped
5300
			var bounds = this._globalTileRange;
5301
			if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
5302
			    (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
5303
		}
5304
5305
		if (!this.options.bounds) { return true; }
5306
5307
		// don't load tile if it doesn't intersect the bounds in options
5308
		var tileBounds = this._tileCoordsToBounds(coords);
5309
		return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
5310
	},
5311
5312
	_keyToBounds: function (key) {
5313
		return this._tileCoordsToBounds(this._keyToTileCoords(key));
5314
	},
5315
5316
	// converts tile coordinates to its geographical bounds
5317
	_tileCoordsToBounds: function (coords) {
5318
5319
		var map = this._map,
5320
		    tileSize = this.getTileSize(),
5321
5322
		    nwPoint = coords.scaleBy(tileSize),
5323
		    sePoint = nwPoint.add(tileSize),
5324
5325
		    nw = map.unproject(nwPoint, coords.z),
5326
		    se = map.unproject(sePoint, coords.z),
5327
		    bounds = new L.LatLngBounds(nw, se);
5328
5329
		if (!this.options.noWrap) {
5330
			map.wrapLatLngBounds(bounds);
5331
		}
5332
5333
		return bounds;
5334
	},
5335
5336
	// converts tile coordinates to key for the tile cache
5337
	_tileCoordsToKey: function (coords) {
5338
		return coords.x + ':' + coords.y + ':' + coords.z;
5339
	},
5340
5341
	// converts tile cache key to coordinates
5342
	_keyToTileCoords: function (key) {
5343
		var k = key.split(':'),
5344
		    coords = new L.Point(+k[0], +k[1]);
5345
		coords.z = +k[2];
5346
		return coords;
5347
	},
5348
5349
	_removeTile: function (key) {
5350
		var tile = this._tiles[key];
5351
		if (!tile) { return; }
5352
5353
		L.DomUtil.remove(tile.el);
5354
5355
		delete this._tiles[key];
5356
5357
		// @event tileunload: TileEvent
5358
		// Fired when a tile is removed (e.g. when a tile goes off the screen).
5359
		this.fire('tileunload', {
5360
			tile: tile.el,
5361
			coords: this._keyToTileCoords(key)
5362
		});
5363
	},
5364
5365
	_initTile: function (tile) {
5366
		L.DomUtil.addClass(tile, 'leaflet-tile');
5367
5368
		var tileSize = this.getTileSize();
5369
		tile.style.width = tileSize.x + 'px';
5370
		tile.style.height = tileSize.y + 'px';
5371
5372
		tile.onselectstart = L.Util.falseFn;
5373
		tile.onmousemove = L.Util.falseFn;
5374
5375
		// update opacity on tiles in IE7-8 because of filter inheritance problems
5376
		if (L.Browser.ielt9 && this.options.opacity < 1) {
5377
			L.DomUtil.setOpacity(tile, this.options.opacity);
5378
		}
5379
5380
		// without this hack, tiles disappear after zoom on Chrome for Android
5381
		// https://github.com/Leaflet/Leaflet/issues/2078
5382
		if (L.Browser.android && !L.Browser.android23) {
5383
			tile.style.WebkitBackfaceVisibility = 'hidden';
5384
		}
5385
	},
5386
5387
	_addTile: function (coords, container) {
5388
		var tilePos = this._getTilePos(coords),
5389
		    key = this._tileCoordsToKey(coords);
5390
5391
		var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
5392
5393
		this._initTile(tile);
5394
5395
		// if createTile is defined with a second argument ("done" callback),
5396
		// we know that tile is async and will be ready later; otherwise
5397
		if (this.createTile.length < 2) {
5398
			// mark tile as ready, but delay one frame for opacity animation to happen
5399
			L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
5400
		}
5401
5402
		L.DomUtil.setPosition(tile, tilePos);
5403
5404
		// save tile in cache
5405
		this._tiles[key] = {
5406
			el: tile,
5407
			coords: coords,
5408
			current: true
5409
		};
5410
5411
		container.appendChild(tile);
5412
		// @event tileloadstart: TileEvent
5413
		// Fired when a tile is requested and starts loading.
5414
		this.fire('tileloadstart', {
5415
			tile: tile,
5416
			coords: coords
5417
		});
5418
	},
5419
5420
	_tileReady: function (coords, err, tile) {
5421
		if (!this._map) { return; }
5422
5423
		if (err) {
5424
			// @event tileerror: TileErrorEvent
5425
			// Fired when there is an error loading a tile.
5426
			this.fire('tileerror', {
5427
				error: err,
5428
				tile: tile,
5429
				coords: coords
5430
			});
5431
		}
5432
5433
		var key = this._tileCoordsToKey(coords);
5434
5435
		tile = this._tiles[key];
5436
		if (!tile) { return; }
5437
5438
		tile.loaded = +new Date();
5439
		if (this._map._fadeAnimated) {
5440
			L.DomUtil.setOpacity(tile.el, 0);
5441
			L.Util.cancelAnimFrame(this._fadeFrame);
5442
			this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
5443
		} else {
5444
			tile.active = true;
5445
			this._pruneTiles();
5446
		}
5447
5448
		if (!err) {
5449
			L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
5450
5451
			// @event tileload: TileEvent
5452
			// Fired when a tile loads.
5453
			this.fire('tileload', {
5454
				tile: tile.el,
5455
				coords: coords
5456
			});
5457
		}
5458
5459
		if (this._noTilesToLoad()) {
5460
			this._loading = false;
5461
			// @event load: Event
5462
			// Fired when the grid layer loaded all visible tiles.
5463
			this.fire('load');
5464
5465
			if (L.Browser.ielt9 || !this._map._fadeAnimated) {
5466
				L.Util.requestAnimFrame(this._pruneTiles, this);
5467
			} else {
5468
				// Wait a bit more than 0.2 secs (the duration of the tile fade-in)
5469
				// to trigger a pruning.
5470
				setTimeout(L.bind(this._pruneTiles, this), 250);
5471
			}
5472
		}
5473
	},
5474
5475
	_getTilePos: function (coords) {
5476
		return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
5477
	},
5478
5479
	_wrapCoords: function (coords) {
5480
		var newCoords = new L.Point(
5481
			this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
5482
			this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
5483
		newCoords.z = coords.z;
5484
		return newCoords;
5485
	},
5486
5487
	_pxBoundsToTileRange: function (bounds) {
5488
		var tileSize = this.getTileSize();
5489
		return new L.Bounds(
5490
			bounds.min.unscaleBy(tileSize).floor(),
5491
			bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
5492
	},
5493
5494
	_noTilesToLoad: function () {
5495
		for (var key in this._tiles) {
5496
			if (!this._tiles[key].loaded) { return false; }
5497
		}
5498
		return true;
5499
	}
5500
});
5501
5502
// @factory L.gridLayer(options?: GridLayer options)
5503
// Creates a new instance of GridLayer with the supplied options.
5504
L.gridLayer = function (options) {
5505
	return new L.GridLayer(options);
5506
};
5507
5508
5509
5510
/*
5511
 * @class TileLayer
5512
 * @inherits GridLayer
5513
 * @aka L.TileLayer
5514
 * Used to load and display tile layers on the map. Extends `GridLayer`.
5515
 *
5516
 * @example
5517
 *
5518
 * ```js
5519
 * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
5520
 * ```
5521
 *
5522
 * @section URL template
5523
 * @example
5524
 *
5525
 * A string of the following form:
5526
 *
5527
 * ```
5528
 * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
5529
 * ```
5530
 *
5531
 * `{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.
5532
 *
5533
 * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
5534
 *
5535
 * ```
5536
 * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
5537
 * ```
5538
 */
5539
5540
5541
L.TileLayer = L.GridLayer.extend({
5542
5543
	// @section
5544
	// @aka TileLayer options
5545
	options: {
5546
		// @option minZoom: Number = 0
5547
		// Minimum zoom number.
5548
		minZoom: 0,
5549
5550
		// @option maxZoom: Number = 18
5551
		// Maximum zoom number.
5552
		maxZoom: 18,
5553
5554
		// @option maxNativeZoom: Number = null
5555
		// Maximum zoom number the tile source has available. If it is specified,
5556
		// the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
5557
		// from `maxNativeZoom` level and auto-scaled.
5558
		maxNativeZoom: null,
5559
5560
		// @option minNativeZoom: Number = null
5561
		// Minimum zoom number the tile source has available. If it is specified,
5562
		// the tiles on all zoom levels lower than `minNativeZoom` will be loaded
5563
		// from `minNativeZoom` level and auto-scaled.
5564
		minNativeZoom: null,
5565
5566
		// @option subdomains: String|String[] = 'abc'
5567
		// 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 strings.
5568
		subdomains: 'abc',
5569
5570
		// @option errorTileUrl: String = ''
5571
		// URL to the tile image to show in place of the tile that failed to load.
5572
		errorTileUrl: '',
5573
5574
		// @option zoomOffset: Number = 0
5575
		// The zoom number used in tile URLs will be offset with this value.
5576
		zoomOffset: 0,
5577
5578
		// @option tms: Boolean = false
5579
		// If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
5580
		tms: false,
5581
5582
		// @option zoomReverse: Boolean = false
5583
		// If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
5584
		zoomReverse: false,
5585
5586
		// @option detectRetina: Boolean = false
5587
		// 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.
5588
		detectRetina: false,
5589
5590
		// @option crossOrigin: Boolean = false
5591
		// If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
5592
		crossOrigin: false
5593
	},
5594
5595
	initialize: function (url, options) {
5596
5597
		this._url = url;
5598
5599
		options = L.setOptions(this, options);
5600
5601
		// detecting retina displays, adjusting tileSize and zoom levels
5602
		if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
5603
5604
			options.tileSize = Math.floor(options.tileSize / 2);
5605
5606
			if (!options.zoomReverse) {
5607
				options.zoomOffset++;
5608
				options.maxZoom--;
5609
			} else {
5610
				options.zoomOffset--;
5611
				options.minZoom++;
5612
			}
5613
5614
			options.minZoom = Math.max(0, options.minZoom);
5615
		}
5616
5617
		if (typeof options.subdomains === 'string') {
5618
			options.subdomains = options.subdomains.split('');
5619
		}
5620
5621
		// for https://github.com/Leaflet/Leaflet/issues/137
5622
		if (!L.Browser.android) {
5623
			this.on('tileunload', this._onTileRemove);
5624
		}
5625
	},
5626
5627
	// @method setUrl(url: String, noRedraw?: Boolean): this
5628
	// Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
5629
	setUrl: function (url, noRedraw) {
5630
		this._url = url;
5631
5632
		if (!noRedraw) {
5633
			this.redraw();
5634
		}
5635
		return this;
5636
	},
5637
5638
	// @method createTile(coords: Object, done?: Function): HTMLElement
5639
	// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
5640
	// to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
5641
	// callback is called when the tile has been loaded.
5642
	createTile: function (coords, done) {
5643
		var tile = document.createElement('img');
5644
5645
		L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
5646
		L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
5647
5648
		if (this.options.crossOrigin) {
5649
			tile.crossOrigin = '';
5650
		}
5651
5652
		/*
5653
		 Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
5654
		 http://www.w3.org/TR/WCAG20-TECHS/H67
5655
		*/
5656
		tile.alt = '';
5657
5658
		/*
5659
		 Set role="presentation" to force screen readers to ignore this
5660
		 https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
5661
		*/
5662
		tile.setAttribute('role', 'presentation');
5663
5664
		tile.src = this.getTileUrl(coords);
5665
5666
		return tile;
5667
	},
5668
5669
	// @section Extension methods
5670
	// @uninheritable
5671
	// Layers extending `TileLayer` might reimplement the following method.
5672
	// @method getTileUrl(coords: Object): String
5673
	// Called only internally, returns the URL for a tile given its coordinates.
5674
	// Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
5675
	getTileUrl: function (coords) {
5676
		var data = {
5677
			r: L.Browser.retina ? '@2x' : '',
5678
			s: this._getSubdomain(coords),
5679
			x: coords.x,
5680
			y: coords.y,
5681
			z: this._getZoomForUrl()
5682
		};
5683
		if (this._map && !this._map.options.crs.infinite) {
5684
			var invertedY = this._globalTileRange.max.y - coords.y;
5685
			if (this.options.tms) {
5686
				data['y'] = invertedY;
5687
			}
5688
			data['-y'] = invertedY;
5689
		}
5690
5691
		return L.Util.template(this._url, L.extend(data, this.options));
5692
	},
5693
5694
	_tileOnLoad: function (done, tile) {
5695
		// For https://github.com/Leaflet/Leaflet/issues/3332
5696
		if (L.Browser.ielt9) {
5697
			setTimeout(L.bind(done, this, null, tile), 0);
5698
		} else {
5699
			done(null, tile);
5700
		}
5701
	},
5702
5703
	_tileOnError: function (done, tile, e) {
5704
		var errorUrl = this.options.errorTileUrl;
5705
		if (errorUrl && tile.src !== errorUrl) {
5706
			tile.src = errorUrl;
5707
		}
5708
		done(e, tile);
5709
	},
5710
5711
	getTileSize: function () {
5712
		var map = this._map,
5713
		tileSize = L.GridLayer.prototype.getTileSize.call(this),
5714
		zoom = this._tileZoom + this.options.zoomOffset,
5715
		minNativeZoom = this.options.minNativeZoom,
5716
		maxNativeZoom = this.options.maxNativeZoom;
5717
5718
		// decrease tile size when scaling below minNativeZoom
5719
		if (minNativeZoom !== null && zoom < minNativeZoom) {
5720
			return tileSize.divideBy(map.getZoomScale(minNativeZoom, zoom)).round();
5721
		}
5722
5723
		// increase tile size when scaling above maxNativeZoom
5724
		if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5725
			return tileSize.divideBy(map.getZoomScale(maxNativeZoom, zoom)).round();
5726
		}
5727
5728
		return tileSize;
5729
	},
5730
5731
	_onTileRemove: function (e) {
5732
		e.tile.onload = null;
5733
	},
5734
5735
	_getZoomForUrl: function () {
5736
		var zoom = this._tileZoom,
5737
		maxZoom = this.options.maxZoom,
5738
		zoomReverse = this.options.zoomReverse,
5739
		zoomOffset = this.options.zoomOffset,
5740
		minNativeZoom = this.options.minNativeZoom,
5741
		maxNativeZoom = this.options.maxNativeZoom;
5742
5743
		if (zoomReverse) {
5744
			zoom = maxZoom - zoom;
5745
		}
5746
5747
		zoom += zoomOffset;
5748
5749
		if (minNativeZoom !== null && zoom < minNativeZoom) {
5750
			return minNativeZoom;
5751
		}
5752
5753
		if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5754
			return maxNativeZoom;
5755
		}
5756
5757
		return zoom;
5758
	},
5759
5760
	_getSubdomain: function (tilePoint) {
5761
		var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
5762
		return this.options.subdomains[index];
5763
	},
5764
5765
	// stops loading all tiles in the background layer
5766
	_abortLoading: function () {
5767
		var i, tile;
5768
		for (i in this._tiles) {
5769
			if (this._tiles[i].coords.z !== this._tileZoom) {
5770
				tile = this._tiles[i].el;
5771
5772
				tile.onload = L.Util.falseFn;
5773
				tile.onerror = L.Util.falseFn;
5774
5775
				if (!tile.complete) {
5776
					tile.src = L.Util.emptyImageUrl;
5777
					L.DomUtil.remove(tile);
5778
				}
5779
			}
5780
		}
5781
	}
5782
});
5783
5784
5785
// @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
5786
// Instantiates a tile layer object given a `URL template` and optionally an options object.
5787
5788
L.tileLayer = function (url, options) {
5789
	return new L.TileLayer(url, options);
5790
};
5791
5792
5793
5794
/*
5795
 * @class TileLayer.WMS
5796
 * @inherits TileLayer
5797
 * @aka L.TileLayer.WMS
5798
 * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
5799
 *
5800
 * @example
5801
 *
5802
 * ```js
5803
 * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
5804
 * 	layers: 'nexrad-n0r-900913',
5805
 * 	format: 'image/png',
5806
 * 	transparent: true,
5807
 * 	attribution: "Weather data © 2012 IEM Nexrad"
5808
 * });
5809
 * ```
5810
 */
5811
5812
L.TileLayer.WMS = L.TileLayer.extend({
5813
5814
	// @section
5815
	// @aka TileLayer.WMS options
5816
	// If any custom options not documented here are used, they will be sent to the
5817
	// WMS server as extra parameters in each request URL. This can be useful for
5818
	// [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
5819
	defaultWmsParams: {
5820
		service: 'WMS',
5821
		request: 'GetMap',
5822
5823
		// @option layers: String = ''
5824
		// **(required)** Comma-separated list of WMS layers to show.
5825
		layers: '',
5826
5827
		// @option styles: String = ''
5828
		// Comma-separated list of WMS styles.
5829
		styles: '',
5830
5831
		// @option format: String = 'image/jpeg'
5832
		// WMS image format (use `'image/png'` for layers with transparency).
5833
		format: 'image/jpeg',
5834
5835
		// @option transparent: Boolean = false
5836
		// If `true`, the WMS service will return images with transparency.
5837
		transparent: false,
5838
5839
		// @option version: String = '1.1.1'
5840
		// Version of the WMS service to use
5841
		version: '1.1.1'
5842
	},
5843
5844
	options: {
5845
		// @option crs: CRS = null
5846
		// Coordinate Reference System to use for the WMS requests, defaults to
5847
		// map CRS. Don't change this if you're not sure what it means.
5848
		crs: null,
5849
5850
		// @option uppercase: Boolean = false
5851
		// If `true`, WMS request parameter keys will be uppercase.
5852
		uppercase: false
5853
	},
5854
5855
	initialize: function (url, options) {
5856
5857
		this._url = url;
5858
5859
		var wmsParams = L.extend({}, this.defaultWmsParams);
5860
5861
		// all keys that are not TileLayer options go to WMS params
5862
		for (var i in options) {
5863
			if (!(i in this.options)) {
5864
				wmsParams[i] = options[i];
5865
			}
5866
		}
5867
5868
		options = L.setOptions(this, options);
5869
5870
		wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
5871
5872
		this.wmsParams = wmsParams;
5873
	},
5874
5875
	onAdd: function (map) {
5876
5877
		this._crs = this.options.crs || map.options.crs;
5878
		this._wmsVersion = parseFloat(this.wmsParams.version);
5879
5880
		var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
5881
		this.wmsParams[projectionKey] = this._crs.code;
5882
5883
		L.TileLayer.prototype.onAdd.call(this, map);
5884
	},
5885
5886
	getTileUrl: function (coords) {
5887
5888
		var tileBounds = this._tileCoordsToBounds(coords),
5889
		    nw = this._crs.project(tileBounds.getNorthWest()),
5890
		    se = this._crs.project(tileBounds.getSouthEast()),
5891
5892
		    bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
5893
			    [se.y, nw.x, nw.y, se.x] :
5894
			    [nw.x, se.y, se.x, nw.y]).join(','),
5895
5896
		    url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5897
5898
		return url +
5899
			L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5900
			(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5901
	},
5902
5903
	// @method setParams(params: Object, noRedraw?: Boolean): this
5904
	// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5905
	setParams: function (params, noRedraw) {
5906
5907
		L.extend(this.wmsParams, params);
5908
5909
		if (!noRedraw) {
5910
			this.redraw();
5911
		}
5912
5913
		return this;
5914
	}
5915
});
5916
5917
5918
// @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5919
// Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5920
L.tileLayer.wms = function (url, options) {
5921
	return new L.TileLayer.WMS(url, options);
5922
};
5923
5924
5925
5926
/*
5927
 * @class ImageOverlay
5928
 * @aka L.ImageOverlay
5929
 * @inherits Interactive layer
5930
 *
5931
 * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5932
 *
5933
 * @example
5934
 *
5935
 * ```js
5936
 * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5937
 * 	imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5938
 * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5939
 * ```
5940
 */
5941
5942
L.ImageOverlay = L.Layer.extend({
5943
5944
	// @section
5945
	// @aka ImageOverlay options
5946
	options: {
5947
		// @option opacity: Number = 1.0
5948
		// The opacity of the image overlay.
5949
		opacity: 1,
5950
5951
		// @option alt: String = ''
5952
		// Text for the `alt` attribute of the image (useful for accessibility).
5953
		alt: '',
5954
5955
		// @option interactive: Boolean = false
5956
		// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5957
		interactive: false,
5958
5959
		// @option crossOrigin: Boolean = false
5960
		// If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5961
		crossOrigin: false
5962
	},
5963
5964
	initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5965
		this._url = url;
5966
		this._bounds = L.latLngBounds(bounds);
5967
5968
		L.setOptions(this, options);
5969
	},
5970
5971
	onAdd: function () {
5972
		if (!this._image) {
5973
			this._initImage();
5974
5975
			if (this.options.opacity < 1) {
5976
				this._updateOpacity();
5977
			}
5978
		}
5979
5980
		if (this.options.interactive) {
5981
			L.DomUtil.addClass(this._image, 'leaflet-interactive');
5982
			this.addInteractiveTarget(this._image);
5983
		}
5984
5985
		this.getPane().appendChild(this._image);
5986
		this._reset();
5987
	},
5988
5989
	onRemove: function () {
5990
		L.DomUtil.remove(this._image);
5991
		if (this.options.interactive) {
5992
			this.removeInteractiveTarget(this._image);
5993
		}
5994
	},
5995
5996
	// @method setOpacity(opacity: Number): this
5997
	// Sets the opacity of the overlay.
5998
	setOpacity: function (opacity) {
5999
		this.options.opacity = opacity;
6000
6001
		if (this._image) {
6002
			this._updateOpacity();
6003
		}
6004
		return this;
6005
	},
6006
6007
	setStyle: function (styleOpts) {
6008
		if (styleOpts.opacity) {
6009
			this.setOpacity(styleOpts.opacity);
6010
		}
6011
		return this;
6012
	},
6013
6014
	// @method bringToFront(): this
6015
	// Brings the layer to the top of all overlays.
6016
	bringToFront: function () {
6017
		if (this._map) {
6018
			L.DomUtil.toFront(this._image);
6019
		}
6020
		return this;
6021
	},
6022
6023
	// @method bringToBack(): this
6024
	// Brings the layer to the bottom of all overlays.
6025
	bringToBack: function () {
6026
		if (this._map) {
6027
			L.DomUtil.toBack(this._image);
6028
		}
6029
		return this;
6030
	},
6031
6032
	// @method setUrl(url: String): this
6033
	// Changes the URL of the image.
6034
	setUrl: function (url) {
6035
		this._url = url;
6036
6037
		if (this._image) {
6038
			this._image.src = url;
6039
		}
6040
		return this;
6041
	},
6042
6043
	// @method setBounds(bounds: LatLngBounds): this
6044
	// Update the bounds that this ImageOverlay covers
6045
	setBounds: function (bounds) {
6046
		this._bounds = bounds;
6047
6048
		if (this._map) {
6049
			this._reset();
6050
		}
6051
		return this;
6052
	},
6053
6054
	getEvents: function () {
6055
		var events = {
6056
			zoom: this._reset,
6057
			viewreset: this._reset
6058
		};
6059
6060
		if (this._zoomAnimated) {
6061
			events.zoomanim = this._animateZoom;
6062
		}
6063
6064
		return events;
6065
	},
6066
6067
	// @method getBounds(): LatLngBounds
6068
	// Get the bounds that this ImageOverlay covers
6069
	getBounds: function () {
6070
		return this._bounds;
6071
	},
6072
6073
	// @method getElement(): HTMLElement
6074
	// Get the img element that represents the ImageOverlay on the map
6075
	getElement: function () {
6076
		return this._image;
6077
	},
6078
6079
	_initImage: function () {
6080
		var img = this._image = L.DomUtil.create('img',
6081
				'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
6082
6083
		img.onselectstart = L.Util.falseFn;
6084
		img.onmousemove = L.Util.falseFn;
6085
6086
		img.onload = L.bind(this.fire, this, 'load');
6087
6088
		if (this.options.crossOrigin) {
6089
			img.crossOrigin = '';
6090
		}
6091
6092
		img.src = this._url;
6093
		img.alt = this.options.alt;
6094
	},
6095
6096
	_animateZoom: function (e) {
6097
		var scale = this._map.getZoomScale(e.zoom),
6098
		    offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
6099
6100
		L.DomUtil.setTransform(this._image, offset, scale);
6101
	},
6102
6103
	_reset: function () {
6104
		var image = this._image,
6105
		    bounds = new L.Bounds(
6106
		        this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
6107
		        this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
6108
		    size = bounds.getSize();
6109
6110
		L.DomUtil.setPosition(image, bounds.min);
6111
6112
		image.style.width  = size.x + 'px';
6113
		image.style.height = size.y + 'px';
6114
	},
6115
6116
	_updateOpacity: function () {
6117
		L.DomUtil.setOpacity(this._image, this.options.opacity);
6118
	}
6119
});
6120
6121
// @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
6122
// Instantiates an image overlay object given the URL of the image and the
6123
// geographical bounds it is tied to.
6124
L.imageOverlay = function (url, bounds, options) {
6125
	return new L.ImageOverlay(url, bounds, options);
6126
};
6127
6128
6129
6130
/*
6131
 * @class Icon
6132
 * @aka L.Icon
6133
 * @inherits Layer
6134
 *
6135
 * Represents an icon to provide when creating a marker.
6136
 *
6137
 * @example
6138
 *
6139
 * ```js
6140
 * var myIcon = L.icon({
6141
 *     iconUrl: 'my-icon.png',
6142
 *     iconRetinaUrl: 'my-icon@2x.png',
6143
 *     iconSize: [38, 95],
6144
 *     iconAnchor: [22, 94],
6145
 *     popupAnchor: [-3, -76],
6146
 *     shadowUrl: 'my-icon-shadow.png',
6147
 *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
6148
 *     shadowSize: [68, 95],
6149
 *     shadowAnchor: [22, 94]
6150
 * });
6151
 *
6152
 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6153
 * ```
6154
 *
6155
 * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6156
 *
6157
 */
6158
6159
L.Icon = L.Class.extend({
6160
6161
	/* @section
6162
	 * @aka Icon options
6163
	 *
6164
	 * @option iconUrl: String = null
6165
	 * **(required)** The URL to the icon image (absolute or relative to your script path).
6166
	 *
6167
	 * @option iconRetinaUrl: String = null
6168
	 * The URL to a retina sized version of the icon image (absolute or relative to your
6169
	 * script path). Used for Retina screen devices.
6170
	 *
6171
	 * @option iconSize: Point = null
6172
	 * Size of the icon image in pixels.
6173
	 *
6174
	 * @option iconAnchor: Point = null
6175
	 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6176
	 * will be aligned so that this point is at the marker's geographical location. Centered
6177
	 * by default if size is specified, also can be set in CSS with negative margins.
6178
	 *
6179
	 * @option popupAnchor: Point = null
6180
	 * The coordinates of the point from which popups will "open", relative to the icon anchor.
6181
	 *
6182
	 * @option shadowUrl: String = null
6183
	 * The URL to the icon shadow image. If not specified, no shadow image will be created.
6184
	 *
6185
	 * @option shadowRetinaUrl: String = null
6186
	 *
6187
	 * @option shadowSize: Point = null
6188
	 * Size of the shadow image in pixels.
6189
	 *
6190
	 * @option shadowAnchor: Point = null
6191
	 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6192
	 * as iconAnchor if not specified).
6193
	 *
6194
	 * @option className: String = ''
6195
	 * A custom class name to assign to both icon and shadow images. Empty by default.
6196
	 */
6197
6198
	initialize: function (options) {
6199
		L.setOptions(this, options);
6200
	},
6201
6202
	// @method createIcon(oldIcon?: HTMLElement): HTMLElement
6203
	// Called internally when the icon has to be shown, returns a `<img>` HTML element
6204
	// styled according to the options.
6205
	createIcon: function (oldIcon) {
6206
		return this._createIcon('icon', oldIcon);
6207
	},
6208
6209
	// @method createShadow(oldIcon?: HTMLElement): HTMLElement
6210
	// As `createIcon`, but for the shadow beneath it.
6211
	createShadow: function (oldIcon) {
6212
		return this._createIcon('shadow', oldIcon);
6213
	},
6214
6215
	_createIcon: function (name, oldIcon) {
6216
		var src = this._getIconUrl(name);
6217
6218
		if (!src) {
6219
			if (name === 'icon') {
6220
				throw new Error('iconUrl not set in Icon options (see the docs).');
6221
			}
6222
			return null;
6223
		}
6224
6225
		var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6226
		this._setIconStyles(img, name);
6227
6228
		return img;
6229
	},
6230
6231
	_setIconStyles: function (img, name) {
6232
		var options = this.options;
6233
		var sizeOption = options[name + 'Size'];
6234
6235
		if (typeof sizeOption === 'number') {
6236
			sizeOption = [sizeOption, sizeOption];
6237
		}
6238
6239
		var size = L.point(sizeOption),
6240
		    anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6241
		            size && size.divideBy(2, true));
6242
6243
		img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6244
6245
		if (anchor) {
6246
			img.style.marginLeft = (-anchor.x) + 'px';
6247
			img.style.marginTop  = (-anchor.y) + 'px';
6248
		}
6249
6250
		if (size) {
6251
			img.style.width  = size.x + 'px';
6252
			img.style.height = size.y + 'px';
6253
		}
6254
	},
6255
6256
	_createImg: function (src, el) {
6257
		el = el || document.createElement('img');
6258
		el.src = src;
6259
		return el;
6260
	},
6261
6262
	_getIconUrl: function (name) {
6263
		return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6264
	}
6265
});
6266
6267
6268
// @factory L.icon(options: Icon options)
6269
// Creates an icon instance with the given options.
6270
L.icon = function (options) {
6271
	return new L.Icon(options);
6272
};
6273
6274
6275
6276
/*
6277
 * @miniclass Icon.Default (Icon)
6278
 * @aka L.Icon.Default
6279
 * @section
6280
 *
6281
 * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6282
 * no icon is specified. Points to the blue marker image distributed with Leaflet
6283
 * releases.
6284
 *
6285
 * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6286
 * (which is a set of `Icon options`).
6287
 *
6288
 * If you want to _completely_ replace the default icon, override the
6289
 * `L.Marker.prototype.options.icon` with your own icon instead.
6290
 */
6291
6292
L.Icon.Default = L.Icon.extend({
6293
6294
	options: {
6295
		iconUrl:       'marker-icon.png',
6296
		iconRetinaUrl: 'marker-icon-2x.png',
6297
		shadowUrl:     'marker-shadow.png',
6298
		iconSize:    [25, 41],
6299
		iconAnchor:  [12, 41],
6300
		popupAnchor: [1, -34],
6301
		tooltipAnchor: [16, -28],
6302
		shadowSize:  [41, 41]
6303
	},
6304
6305
	_getIconUrl: function (name) {
6306
		if (!L.Icon.Default.imagePath) {	// Deprecated, backwards-compatibility only
6307
			L.Icon.Default.imagePath = this._detectIconPath();
6308
		}
6309
6310
		// @option imagePath: String
6311
		// `L.Icon.Default` will try to auto-detect the absolute location of the
6312
		// blue icon images. If you are placing these images in a non-standard
6313
		// way, set this option to point to the right absolute path.
6314
		return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
6315
	},
6316
6317
	_detectIconPath: function () {
6318
		var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
6319
		var path = L.DomUtil.getStyle(el, 'background-image') ||
6320
		           L.DomUtil.getStyle(el, 'backgroundImage');	// IE8
6321
6322
		document.body.removeChild(el);
6323
6324
		return path.indexOf('url') === 0 ?
6325
			path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
6326
	}
6327
});
6328
6329
6330
6331
/*
6332
 * @class Marker
6333
 * @inherits Interactive layer
6334
 * @aka L.Marker
6335
 * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
6336
 *
6337
 * @example
6338
 *
6339
 * ```js
6340
 * L.marker([50.5, 30.5]).addTo(map);
6341
 * ```
6342
 */
6343
6344
L.Marker = L.Layer.extend({
6345
6346
	// @section
6347
	// @aka Marker options
6348
	options: {
6349
		// @option icon: Icon = *
6350
		// 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.
6351
		icon: new L.Icon.Default(),
6352
6353
		// Option inherited from "Interactive layer" abstract class
6354
		interactive: true,
6355
6356
		// @option draggable: Boolean = false
6357
		// Whether the marker is draggable with mouse/touch or not.
6358
		draggable: false,
6359
6360
		// @option keyboard: Boolean = true
6361
		// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
6362
		keyboard: true,
6363
6364
		// @option title: String = ''
6365
		// Text for the browser tooltip that appear on marker hover (no tooltip by default).
6366
		title: '',
6367
6368
		// @option alt: String = ''
6369
		// Text for the `alt` attribute of the icon image (useful for accessibility).
6370
		alt: '',
6371
6372
		// @option zIndexOffset: Number = 0
6373
		// 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).
6374
		zIndexOffset: 0,
6375
6376
		// @option opacity: Number = 1.0
6377
		// The opacity of the marker.
6378
		opacity: 1,
6379
6380
		// @option riseOnHover: Boolean = false
6381
		// If `true`, the marker will get on top of others when you hover the mouse over it.
6382
		riseOnHover: false,
6383
6384
		// @option riseOffset: Number = 250
6385
		// The z-index offset used for the `riseOnHover` feature.
6386
		riseOffset: 250,
6387
6388
		// @option pane: String = 'markerPane'
6389
		// `Map pane` where the markers icon will be added.
6390
		pane: 'markerPane',
6391
6392
		// FIXME: shadowPane is no longer a valid option
6393
		nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
6394
	},
6395
6396
	/* @section
6397
	 *
6398
	 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
6399
	 */
6400
6401
	initialize: function (latlng, options) {
6402
		L.setOptions(this, options);
6403
		this._latlng = L.latLng(latlng);
6404
	},
6405
6406
	onAdd: function (map) {
6407
		this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
6408
6409
		if (this._zoomAnimated) {
6410
			map.on('zoomanim', this._animateZoom, this);
6411
		}
6412
6413
		this._initIcon();
6414
		this.update();
6415
	},
6416
6417
	onRemove: function (map) {
6418
		if (this.dragging && this.dragging.enabled()) {
6419
			this.options.draggable = true;
6420
			this.dragging.removeHooks();
6421
		}
6422
6423
		if (this._zoomAnimated) {
6424
			map.off('zoomanim', this._animateZoom, this);
6425
		}
6426
6427
		this._removeIcon();
6428
		this._removeShadow();
6429
	},
6430
6431
	getEvents: function () {
6432
		return {
6433
			zoom: this.update,
6434
			viewreset: this.update
6435
		};
6436
	},
6437
6438
	// @method getLatLng: LatLng
6439
	// Returns the current geographical position of the marker.
6440
	getLatLng: function () {
6441
		return this._latlng;
6442
	},
6443
6444
	// @method setLatLng(latlng: LatLng): this
6445
	// Changes the marker position to the given point.
6446
	setLatLng: function (latlng) {
6447
		var oldLatLng = this._latlng;
6448
		this._latlng = L.latLng(latlng);
6449
		this.update();
6450
6451
		// @event move: Event
6452
		// 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`.
6453
		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
6454
	},
6455
6456
	// @method setZIndexOffset(offset: Number): this
6457
	// Changes the [zIndex offset](#marker-zindexoffset) of the marker.
6458
	setZIndexOffset: function (offset) {
6459
		this.options.zIndexOffset = offset;
6460
		return this.update();
6461
	},
6462
6463
	// @method setIcon(icon: Icon): this
6464
	// Changes the marker icon.
6465
	setIcon: function (icon) {
6466
6467
		this.options.icon = icon;
6468
6469
		if (this._map) {
6470
			this._initIcon();
6471
			this.update();
6472
		}
6473
6474
		if (this._popup) {
6475
			this.bindPopup(this._popup, this._popup.options);
6476
		}
6477
6478
		return this;
6479
	},
6480
6481
	getElement: function () {
6482
		return this._icon;
6483
	},
6484
6485
	update: function () {
6486
6487
		if (this._icon) {
6488
			var pos = this._map.latLngToLayerPoint(this._latlng).round();
6489
			this._setPos(pos);
6490
		}
6491
6492
		return this;
6493
	},
6494
6495
	_initIcon: function () {
6496
		var options = this.options,
6497
		    classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6498
6499
		var icon = options.icon.createIcon(this._icon),
6500
		    addIcon = false;
6501
6502
		// if we're not reusing the icon, remove the old one and init new one
6503
		if (icon !== this._icon) {
6504
			if (this._icon) {
6505
				this._removeIcon();
6506
			}
6507
			addIcon = true;
6508
6509
			if (options.title) {
6510
				icon.title = options.title;
6511
			}
6512
			if (options.alt) {
6513
				icon.alt = options.alt;
6514
			}
6515
		}
6516
6517
		L.DomUtil.addClass(icon, classToAdd);
6518
6519
		if (options.keyboard) {
6520
			icon.tabIndex = '0';
6521
		}
6522
6523
		this._icon = icon;
6524
6525
		if (options.riseOnHover) {
6526
			this.on({
6527
				mouseover: this._bringToFront,
6528
				mouseout: this._resetZIndex
6529
			});
6530
		}
6531
6532
		var newShadow = options.icon.createShadow(this._shadow),
6533
		    addShadow = false;
6534
6535
		if (newShadow !== this._shadow) {
6536
			this._removeShadow();
6537
			addShadow = true;
6538
		}
6539
6540
		if (newShadow) {
6541
			L.DomUtil.addClass(newShadow, classToAdd);
6542
			newShadow.alt = '';
6543
		}
6544
		this._shadow = newShadow;
6545
6546
6547
		if (options.opacity < 1) {
6548
			this._updateOpacity();
6549
		}
6550
6551
6552
		if (addIcon) {
6553
			this.getPane().appendChild(this._icon);
6554
		}
6555
		this._initInteraction();
6556
		if (newShadow && addShadow) {
6557
			this.getPane('shadowPane').appendChild(this._shadow);
6558
		}
6559
	},
6560
6561
	_removeIcon: function () {
6562
		if (this.options.riseOnHover) {
6563
			this.off({
6564
				mouseover: this._bringToFront,
6565
				mouseout: this._resetZIndex
6566
			});
6567
		}
6568
6569
		L.DomUtil.remove(this._icon);
6570
		this.removeInteractiveTarget(this._icon);
6571
6572
		this._icon = null;
6573
	},
6574
6575
	_removeShadow: function () {
6576
		if (this._shadow) {
6577
			L.DomUtil.remove(this._shadow);
6578
		}
6579
		this._shadow = null;
6580
	},
6581
6582
	_setPos: function (pos) {
6583
		L.DomUtil.setPosition(this._icon, pos);
6584
6585
		if (this._shadow) {
6586
			L.DomUtil.setPosition(this._shadow, pos);
6587
		}
6588
6589
		this._zIndex = pos.y + this.options.zIndexOffset;
6590
6591
		this._resetZIndex();
6592
	},
6593
6594
	_updateZIndex: function (offset) {
6595
		this._icon.style.zIndex = this._zIndex + offset;
6596
	},
6597
6598
	_animateZoom: function (opt) {
6599
		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
6600
6601
		this._setPos(pos);
6602
	},
6603
6604
	_initInteraction: function () {
6605
6606
		if (!this.options.interactive) { return; }
6607
6608
		L.DomUtil.addClass(this._icon, 'leaflet-interactive');
6609
6610
		this.addInteractiveTarget(this._icon);
6611
6612
		if (L.Handler.MarkerDrag) {
6613
			var draggable = this.options.draggable;
6614
			if (this.dragging) {
6615
				draggable = this.dragging.enabled();
6616
				this.dragging.disable();
6617
			}
6618
6619
			this.dragging = new L.Handler.MarkerDrag(this);
6620
6621
			if (draggable) {
6622
				this.dragging.enable();
6623
			}
6624
		}
6625
	},
6626
6627
	// @method setOpacity(opacity: Number): this
6628
	// Changes the opacity of the marker.
6629
	setOpacity: function (opacity) {
6630
		this.options.opacity = opacity;
6631
		if (this._map) {
6632
			this._updateOpacity();
6633
		}
6634
6635
		return this;
6636
	},
6637
6638
	_updateOpacity: function () {
6639
		var opacity = this.options.opacity;
6640
6641
		L.DomUtil.setOpacity(this._icon, opacity);
6642
6643
		if (this._shadow) {
6644
			L.DomUtil.setOpacity(this._shadow, opacity);
6645
		}
6646
	},
6647
6648
	_bringToFront: function () {
6649
		this._updateZIndex(this.options.riseOffset);
6650
	},
6651
6652
	_resetZIndex: function () {
6653
		this._updateZIndex(0);
6654
	},
6655
6656
	_getPopupAnchor: function () {
6657
		return this.options.icon.options.popupAnchor || [0, 0];
6658
	},
6659
6660
	_getTooltipAnchor: function () {
6661
		return this.options.icon.options.tooltipAnchor || [0, 0];
6662
	}
6663
});
6664
6665
6666
// factory L.marker(latlng: LatLng, options? : Marker options)
6667
6668
// @factory L.marker(latlng: LatLng, options? : Marker options)
6669
// Instantiates a Marker object given a geographical point and optionally an options object.
6670
L.marker = function (latlng, options) {
6671
	return new L.Marker(latlng, options);
6672
};
6673
6674
6675
6676
/*
6677
 * @class DivIcon
6678
 * @aka L.DivIcon
6679
 * @inherits Icon
6680
 *
6681
 * Represents a lightweight icon for markers that uses a simple `<div>`
6682
 * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
6683
 *
6684
 * @example
6685
 * ```js
6686
 * var myIcon = L.divIcon({className: 'my-div-icon'});
6687
 * // you can set .my-div-icon styles in CSS
6688
 *
6689
 * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6690
 * ```
6691
 *
6692
 * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
6693
 */
6694
6695
L.DivIcon = L.Icon.extend({
6696
	options: {
6697
		// @section
6698
		// @aka DivIcon options
6699
		iconSize: [12, 12], // also can be set through CSS
6700
6701
		// iconAnchor: (Point),
6702
		// popupAnchor: (Point),
6703
6704
		// @option html: String = ''
6705
		// Custom HTML code to put inside the div element, empty by default.
6706
		html: false,
6707
6708
		// @option bgPos: Point = [0, 0]
6709
		// Optional relative position of the background, in pixels
6710
		bgPos: null,
6711
6712
		className: 'leaflet-div-icon'
6713
	},
6714
6715
	createIcon: function (oldIcon) {
6716
		var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
6717
		    options = this.options;
6718
6719
		div.innerHTML = options.html !== false ? options.html : '';
6720
6721
		if (options.bgPos) {
6722
			var bgPos = L.point(options.bgPos);
6723
			div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
6724
		}
6725
		this._setIconStyles(div, 'icon');
6726
6727
		return div;
6728
	},
6729
6730
	createShadow: function () {
6731
		return null;
6732
	}
6733
});
6734
6735
// @factory L.divIcon(options: DivIcon options)
6736
// Creates a `DivIcon` instance with the given options.
6737
L.divIcon = function (options) {
6738
	return new L.DivIcon(options);
6739
};
6740
6741
6742
6743
/*
6744
 * @class DivOverlay
6745
 * @inherits Layer
6746
 * @aka L.DivOverlay
6747
 * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
6748
 */
6749
6750
// @namespace DivOverlay
6751
L.DivOverlay = L.Layer.extend({
6752
6753
	// @section
6754
	// @aka DivOverlay options
6755
	options: {
6756
		// @option offset: Point = Point(0, 7)
6757
		// The offset of the popup position. Useful to control the anchor
6758
		// of the popup when opening it on some overlays.
6759
		offset: [0, 7],
6760
6761
		// @option className: String = ''
6762
		// A custom CSS class name to assign to the popup.
6763
		className: '',
6764
6765
		// @option pane: String = 'popupPane'
6766
		// `Map pane` where the popup will be added.
6767
		pane: 'popupPane'
6768
	},
6769
6770
	initialize: function (options, source) {
6771
		L.setOptions(this, options);
6772
6773
		this._source = source;
6774
	},
6775
6776
	onAdd: function (map) {
6777
		this._zoomAnimated = map._zoomAnimated;
6778
6779
		if (!this._container) {
6780
			this._initLayout();
6781
		}
6782
6783
		if (map._fadeAnimated) {
6784
			L.DomUtil.setOpacity(this._container, 0);
6785
		}
6786
6787
		clearTimeout(this._removeTimeout);
6788
		this.getPane().appendChild(this._container);
6789
		this.update();
6790
6791
		if (map._fadeAnimated) {
6792
			L.DomUtil.setOpacity(this._container, 1);
6793
		}
6794
6795
		this.bringToFront();
6796
	},
6797
6798
	onRemove: function (map) {
6799
		if (map._fadeAnimated) {
6800
			L.DomUtil.setOpacity(this._container, 0);
6801
			this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
6802
		} else {
6803
			L.DomUtil.remove(this._container);
6804
		}
6805
	},
6806
6807
	// @namespace Popup
6808
	// @method getLatLng: LatLng
6809
	// Returns the geographical point of popup.
6810
	getLatLng: function () {
6811
		return this._latlng;
6812
	},
6813
6814
	// @method setLatLng(latlng: LatLng): this