]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Update to iD v2.18.1
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /* @preserve
2  * Leaflet 1.6.0, a JS library for interactive maps. http://leafletjs.com
3  * (c) 2010-2019 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4  */
5
6 (function (global, factory) {
7         typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
8         typeof define === 'function' && define.amd ? define(['exports'], factory) :
9         (factory((global.L = {})));
10 }(this, (function (exports) { 'use strict';
11
12 var version = "1.6.0";
13
14 /*
15  * @namespace Util
16  *
17  * Various utility functions, used by Leaflet internally.
18  */
19
20 var freeze = Object.freeze;
21 Object.freeze = function (obj) { return obj; };
22
23 // @function extend(dest: Object, src?: Object): Object
24 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25 function extend(dest) {
26         var i, j, len, src;
27
28         for (j = 1, len = arguments.length; j < len; j++) {
29                 src = arguments[j];
30                 for (i in src) {
31                         dest[i] = src[i];
32                 }
33         }
34         return dest;
35 }
36
37 // @function create(proto: Object, properties?: Object): Object
38 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39 var create = Object.create || (function () {
40         function F() {}
41         return function (proto) {
42                 F.prototype = proto;
43                 return new F();
44         };
45 })();
46
47 // @function bind(fn: Function, …): Function
48 // 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).
49 // Has a `L.bind()` shortcut.
50 function bind(fn, obj) {
51         var slice = Array.prototype.slice;
52
53         if (fn.bind) {
54                 return fn.bind.apply(fn, slice.call(arguments, 1));
55         }
56
57         var args = slice.call(arguments, 2);
58
59         return function () {
60                 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
61         };
62 }
63
64 // @property lastId: Number
65 // Last unique ID used by [`stamp()`](#util-stamp)
66 var lastId = 0;
67
68 // @function stamp(obj: Object): Number
69 // Returns the unique ID of an object, assigning it one if it doesn't have it.
70 function stamp(obj) {
71         /*eslint-disable */
72         obj._leaflet_id = obj._leaflet_id || ++lastId;
73         return obj._leaflet_id;
74         /* eslint-enable */
75 }
76
77 // @function throttle(fn: Function, time: Number, context: Object): Function
78 // Returns a function which executes function `fn` with the given scope `context`
79 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80 // `fn` will be called no more than one time per given amount of `time`. The arguments
81 // received by the bound function will be any arguments passed when binding the
82 // function, followed by any arguments passed when invoking the bound function.
83 // Has an `L.throttle` shortcut.
84 function throttle(fn, time, context) {
85         var lock, args, wrapperFn, later;
86
87         later = function () {
88                 // reset lock and call if queued
89                 lock = false;
90                 if (args) {
91                         wrapperFn.apply(context, args);
92                         args = false;
93                 }
94         };
95
96         wrapperFn = function () {
97                 if (lock) {
98                         // called too soon, queue to call later
99                         args = arguments;
100
101                 } else {
102                         // call and lock until later
103                         fn.apply(context, arguments);
104                         setTimeout(later, time);
105                         lock = true;
106                 }
107         };
108
109         return wrapperFn;
110 }
111
112 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113 // Returns the number `num` modulo `range` in such a way so it lies within
114 // `range[0]` and `range[1]`. The returned value will be always smaller than
115 // `range[1]` unless `includeMax` is set to `true`.
116 function wrapNum(x, range, includeMax) {
117         var max = range[1],
118             min = range[0],
119             d = max - min;
120         return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
121 }
122
123 // @function falseFn(): Function
124 // Returns a function which always returns `false`.
125 function falseFn() { return false; }
126
127 // @function formatNum(num: Number, digits?: Number): Number
128 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129 function formatNum(num, digits) {
130         var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131         return Math.round(num * pow) / pow;
132 }
133
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136 function trim(str) {
137         return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
138 }
139
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143         return trim(str).split(/\s+/);
144 }
145
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149         if (!obj.hasOwnProperty('options')) {
150                 obj.options = obj.options ? create(obj.options) : {};
151         }
152         for (var i in options) {
153                 obj.options[i] = options[i];
154         }
155         return obj.options;
156 }
157
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
164         var params = [];
165         for (var i in obj) {
166                 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167         }
168         return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
169 }
170
171 var templateRe = /\{ *([\w_-]+) *\}/g;
172
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179         return str.replace(templateRe, function (str, key) {
180                 var value = data[key];
181
182                 if (value === undefined) {
183                         throw new Error('No value provided for variable ' + str);
184
185                 } else if (typeof value === 'function') {
186                         value = value(data);
187                 }
188                 return value;
189         });
190 }
191
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195         return (Object.prototype.toString.call(obj) === '[object Array]');
196 };
197
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201         for (var i = 0; i < array.length; i++) {
202                 if (array[i] === el) { return i; }
203         }
204         return -1;
205 }
206
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212
213 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
214
215 function getPrefixed(name) {
216         return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217 }
218
219 var lastTime = 0;
220
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223         var time = +new Date(),
224             timeToCall = Math.max(0, 16 - (time - lastTime));
225
226         lastTime = time + timeToCall;
227         return window.setTimeout(fn, timeToCall);
228 }
229
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232                 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241         if (immediate && requestFn === timeoutDefer) {
242                 fn.call(context);
243         } else {
244                 return requestFn.call(window, bind(fn, context));
245         }
246 }
247
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
251         if (id) {
252                 cancelFn.call(window, id);
253         }
254 }
255
256
257 var Util = (Object.freeze || Object)({
258         freeze: freeze,
259         extend: extend,
260         create: create,
261         bind: bind,
262         lastId: lastId,
263         stamp: stamp,
264         throttle: throttle,
265         wrapNum: wrapNum,
266         falseFn: falseFn,
267         formatNum: formatNum,
268         trim: trim,
269         splitWords: splitWords,
270         setOptions: setOptions,
271         getParamString: getParamString,
272         template: template,
273         isArray: isArray,
274         indexOf: indexOf,
275         emptyImageUrl: emptyImageUrl,
276         requestFn: requestFn,
277         cancelFn: cancelFn,
278         requestAnimFrame: requestAnimFrame,
279         cancelAnimFrame: cancelAnimFrame
280 });
281
282 // @class Class
283 // @aka L.Class
284
285 // @section
286 // @uninheritable
287
288 // Thanks to John Resig and Dean Edwards for inspiration!
289
290 function Class() {}
291
292 Class.extend = function (props) {
293
294         // @function extend(props: Object): Function
295         // [Extends the current class](#class-inheritance) given the properties to be included.
296         // Returns a Javascript function that is a class constructor (to be called with `new`).
297         var NewClass = function () {
298
299                 // call the constructor
300                 if (this.initialize) {
301                         this.initialize.apply(this, arguments);
302                 }
303
304                 // call all constructor hooks
305                 this.callInitHooks();
306         };
307
308         var parentProto = NewClass.__super__ = this.prototype;
309
310         var proto = create(parentProto);
311         proto.constructor = NewClass;
312
313         NewClass.prototype = proto;
314
315         // inherit parent's statics
316         for (var i in this) {
317                 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318                         NewClass[i] = this[i];
319                 }
320         }
321
322         // mix static properties into the class
323         if (props.statics) {
324                 extend(NewClass, props.statics);
325                 delete props.statics;
326         }
327
328         // mix includes into the prototype
329         if (props.includes) {
330                 checkDeprecatedMixinEvents(props.includes);
331                 extend.apply(null, [proto].concat(props.includes));
332                 delete props.includes;
333         }
334
335         // merge options
336         if (proto.options) {
337                 props.options = extend(create(proto.options), props.options);
338         }
339
340         // mix given properties into the prototype
341         extend(proto, props);
342
343         proto._initHooks = [];
344
345         // add method for calling all hooks
346         proto.callInitHooks = function () {
347
348                 if (this._initHooksCalled) { return; }
349
350                 if (parentProto.callInitHooks) {
351                         parentProto.callInitHooks.call(this);
352                 }
353
354                 this._initHooksCalled = true;
355
356                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357                         proto._initHooks[i].call(this);
358                 }
359         };
360
361         return NewClass;
362 };
363
364
365 // @function include(properties: Object): this
366 // [Includes a mixin](#class-includes) into the current class.
367 Class.include = function (props) {
368         extend(this.prototype, props);
369         return this;
370 };
371
372 // @function mergeOptions(options: Object): this
373 // [Merges `options`](#class-options) into the defaults of the class.
374 Class.mergeOptions = function (options) {
375         extend(this.prototype.options, options);
376         return this;
377 };
378
379 // @function addInitHook(fn: Function): this
380 // Adds a [constructor hook](#class-constructor-hooks) to the class.
381 Class.addInitHook = function (fn) { // (Function) || (String, args...)
382         var args = Array.prototype.slice.call(arguments, 1);
383
384         var init = typeof fn === 'function' ? fn : function () {
385                 this[fn].apply(this, args);
386         };
387
388         this.prototype._initHooks = this.prototype._initHooks || [];
389         this.prototype._initHooks.push(init);
390         return this;
391 };
392
393 function checkDeprecatedMixinEvents(includes) {
394         if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
395
396         includes = isArray(includes) ? includes : [includes];
397
398         for (var i = 0; i < includes.length; i++) {
399                 if (includes[i] === L.Mixin.Events) {
400                         console.warn('Deprecated include of L.Mixin.Events: ' +
401                                 'this property will be removed in future releases, ' +
402                                 'please inherit from L.Evented instead.', new Error().stack);
403                 }
404         }
405 }
406
407 /*
408  * @class Evented
409  * @aka L.Evented
410  * @inherits Class
411  *
412  * 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).
413  *
414  * @example
415  *
416  * ```js
417  * map.on('click', function(e) {
418  *      alert(e.latlng);
419  * } );
420  * ```
421  *
422  * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
423  *
424  * ```js
425  * function onClick(e) { ... }
426  *
427  * map.on('click', onClick);
428  * map.off('click', onClick);
429  * ```
430  */
431
432 var Events = {
433         /* @method on(type: String, fn: Function, context?: Object): this
434          * 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'`).
435          *
436          * @alternative
437          * @method on(eventMap: Object): this
438          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
439          */
440         on: function (types, fn, context) {
441
442                 // types can be a map of types/handlers
443                 if (typeof types === 'object') {
444                         for (var type in types) {
445                                 // we don't process space-separated events here for performance;
446                                 // it's a hot path since Layer uses the on(obj) syntax
447                                 this._on(type, types[type], fn);
448                         }
449
450                 } else {
451                         // types can be a string of space-separated words
452                         types = splitWords(types);
453
454                         for (var i = 0, len = types.length; i < len; i++) {
455                                 this._on(types[i], fn, context);
456                         }
457                 }
458
459                 return this;
460         },
461
462         /* @method off(type: String, fn?: Function, context?: Object): this
463          * 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.
464          *
465          * @alternative
466          * @method off(eventMap: Object): this
467          * Removes a set of type/listener pairs.
468          *
469          * @alternative
470          * @method off: this
471          * Removes all listeners to all events on the object. This includes implicitly attached events.
472          */
473         off: function (types, fn, context) {
474
475                 if (!types) {
476                         // clear all listeners if called without arguments
477                         delete this._events;
478
479                 } else if (typeof types === 'object') {
480                         for (var type in types) {
481                                 this._off(type, types[type], fn);
482                         }
483
484                 } else {
485                         types = splitWords(types);
486
487                         for (var i = 0, len = types.length; i < len; i++) {
488                                 this._off(types[i], fn, context);
489                         }
490                 }
491
492                 return this;
493         },
494
495         // attach listener (without syntactic sugar now)
496         _on: function (type, fn, context) {
497                 this._events = this._events || {};
498
499                 /* get/init listeners for type */
500                 var typeListeners = this._events[type];
501                 if (!typeListeners) {
502                         typeListeners = [];
503                         this._events[type] = typeListeners;
504                 }
505
506                 if (context === this) {
507                         // Less memory footprint.
508                         context = undefined;
509                 }
510                 var newListener = {fn: fn, ctx: context},
511                     listeners = typeListeners;
512
513                 // check if fn already there
514                 for (var i = 0, len = listeners.length; i < len; i++) {
515                         if (listeners[i].fn === fn && listeners[i].ctx === context) {
516                                 return;
517                         }
518                 }
519
520                 listeners.push(newListener);
521         },
522
523         _off: function (type, fn, context) {
524                 var listeners,
525                     i,
526                     len;
527
528                 if (!this._events) { return; }
529
530                 listeners = this._events[type];
531
532                 if (!listeners) {
533                         return;
534                 }
535
536                 if (!fn) {
537                         // Set all removed listeners to noop so they are not called if remove happens in fire
538                         for (i = 0, len = listeners.length; i < len; i++) {
539                                 listeners[i].fn = falseFn;
540                         }
541                         // clear all listeners for a type if function isn't specified
542                         delete this._events[type];
543                         return;
544                 }
545
546                 if (context === this) {
547                         context = undefined;
548                 }
549
550                 if (listeners) {
551
552                         // find fn and remove it
553                         for (i = 0, len = listeners.length; i < len; i++) {
554                                 var l = listeners[i];
555                                 if (l.ctx !== context) { continue; }
556                                 if (l.fn === fn) {
557
558                                         // set the removed listener to noop so that's not called if remove happens in fire
559                                         l.fn = falseFn;
560
561                                         if (this._firingCount) {
562                                                 /* copy array in case events are being fired */
563                                                 this._events[type] = listeners = listeners.slice();
564                                         }
565                                         listeners.splice(i, 1);
566
567                                         return;
568                                 }
569                         }
570                 }
571         },
572
573         // @method fire(type: String, data?: Object, propagate?: Boolean): this
574         // Fires an event of the specified type. You can optionally provide an data
575         // object — the first argument of the listener function will contain its
576         // properties. The event can optionally be propagated to event parents.
577         fire: function (type, data, propagate) {
578                 if (!this.listens(type, propagate)) { return this; }
579
580                 var event = extend({}, data, {
581                         type: type,
582                         target: this,
583                         sourceTarget: data && data.sourceTarget || this
584                 });
585
586                 if (this._events) {
587                         var listeners = this._events[type];
588
589                         if (listeners) {
590                                 this._firingCount = (this._firingCount + 1) || 1;
591                                 for (var i = 0, len = listeners.length; i < len; i++) {
592                                         var l = listeners[i];
593                                         l.fn.call(l.ctx || this, event);
594                                 }
595
596                                 this._firingCount--;
597                         }
598                 }
599
600                 if (propagate) {
601                         // propagate the event to parents (set with addEventParent)
602                         this._propagateEvent(event);
603                 }
604
605                 return this;
606         },
607
608         // @method listens(type: String): Boolean
609         // Returns `true` if a particular event type has any listeners attached to it.
610         listens: function (type, propagate) {
611                 var listeners = this._events && this._events[type];
612                 if (listeners && listeners.length) { return true; }
613
614                 if (propagate) {
615                         // also check parents for listeners if event propagates
616                         for (var id in this._eventParents) {
617                                 if (this._eventParents[id].listens(type, propagate)) { return true; }
618                         }
619                 }
620                 return false;
621         },
622
623         // @method once(…): this
624         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
625         once: function (types, fn, context) {
626
627                 if (typeof types === 'object') {
628                         for (var type in types) {
629                                 this.once(type, types[type], fn);
630                         }
631                         return this;
632                 }
633
634                 var handler = bind(function () {
635                         this
636                             .off(types, fn, context)
637                             .off(types, handler, context);
638                 }, this);
639
640                 // add a listener that's executed once and removed after that
641                 return this
642                     .on(types, fn, context)
643                     .on(types, handler, context);
644         },
645
646         // @method addEventParent(obj: Evented): this
647         // Adds an event parent - an `Evented` that will receive propagated events
648         addEventParent: function (obj) {
649                 this._eventParents = this._eventParents || {};
650                 this._eventParents[stamp(obj)] = obj;
651                 return this;
652         },
653
654         // @method removeEventParent(obj: Evented): this
655         // Removes an event parent, so it will stop receiving propagated events
656         removeEventParent: function (obj) {
657                 if (this._eventParents) {
658                         delete this._eventParents[stamp(obj)];
659                 }
660                 return this;
661         },
662
663         _propagateEvent: function (e) {
664                 for (var id in this._eventParents) {
665                         this._eventParents[id].fire(e.type, extend({
666                                 layer: e.target,
667                                 propagatedFrom: e.target
668                         }, e), true);
669                 }
670         }
671 };
672
673 // aliases; we should ditch those eventually
674
675 // @method addEventListener(…): this
676 // Alias to [`on(…)`](#evented-on)
677 Events.addEventListener = Events.on;
678
679 // @method removeEventListener(…): this
680 // Alias to [`off(…)`](#evented-off)
681
682 // @method clearAllEventListeners(…): this
683 // Alias to [`off()`](#evented-off)
684 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
685
686 // @method addOneTimeEventListener(…): this
687 // Alias to [`once(…)`](#evented-once)
688 Events.addOneTimeEventListener = Events.once;
689
690 // @method fireEvent(…): this
691 // Alias to [`fire(…)`](#evented-fire)
692 Events.fireEvent = Events.fire;
693
694 // @method hasEventListeners(…): Boolean
695 // Alias to [`listens(…)`](#evented-listens)
696 Events.hasEventListeners = Events.listens;
697
698 var Evented = Class.extend(Events);
699
700 /*
701  * @class Point
702  * @aka L.Point
703  *
704  * Represents a point with `x` and `y` coordinates in pixels.
705  *
706  * @example
707  *
708  * ```js
709  * var point = L.point(200, 300);
710  * ```
711  *
712  * 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:
713  *
714  * ```js
715  * map.panBy([200, 300]);
716  * map.panBy(L.point(200, 300));
717  * ```
718  *
719  * Note that `Point` does not inherit from Leafet's `Class` object,
720  * which means new classes can't inherit from it, and new methods
721  * can't be added to it with the `include` function.
722  */
723
724 function Point(x, y, round) {
725         // @property x: Number; The `x` coordinate of the point
726         this.x = (round ? Math.round(x) : x);
727         // @property y: Number; The `y` coordinate of the point
728         this.y = (round ? Math.round(y) : y);
729 }
730
731 var trunc = Math.trunc || function (v) {
732         return v > 0 ? Math.floor(v) : Math.ceil(v);
733 };
734
735 Point.prototype = {
736
737         // @method clone(): Point
738         // Returns a copy of the current point.
739         clone: function () {
740                 return new Point(this.x, this.y);
741         },
742
743         // @method add(otherPoint: Point): Point
744         // Returns the result of addition of the current and the given points.
745         add: function (point) {
746                 // non-destructive, returns a new point
747                 return this.clone()._add(toPoint(point));
748         },
749
750         _add: function (point) {
751                 // destructive, used directly for performance in situations where it's safe to modify existing point
752                 this.x += point.x;
753                 this.y += point.y;
754                 return this;
755         },
756
757         // @method subtract(otherPoint: Point): Point
758         // Returns the result of subtraction of the given point from the current.
759         subtract: function (point) {
760                 return this.clone()._subtract(toPoint(point));
761         },
762
763         _subtract: function (point) {
764                 this.x -= point.x;
765                 this.y -= point.y;
766                 return this;
767         },
768
769         // @method divideBy(num: Number): Point
770         // Returns the result of division of the current point by the given number.
771         divideBy: function (num) {
772                 return this.clone()._divideBy(num);
773         },
774
775         _divideBy: function (num) {
776                 this.x /= num;
777                 this.y /= num;
778                 return this;
779         },
780
781         // @method multiplyBy(num: Number): Point
782         // Returns the result of multiplication of the current point by the given number.
783         multiplyBy: function (num) {
784                 return this.clone()._multiplyBy(num);
785         },
786
787         _multiplyBy: function (num) {
788                 this.x *= num;
789                 this.y *= num;
790                 return this;
791         },
792
793         // @method scaleBy(scale: Point): Point
794         // Multiply each coordinate of the current point by each coordinate of
795         // `scale`. In linear algebra terms, multiply the point by the
796         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
797         // defined by `scale`.
798         scaleBy: function (point) {
799                 return new Point(this.x * point.x, this.y * point.y);
800         },
801
802         // @method unscaleBy(scale: Point): Point
803         // Inverse of `scaleBy`. Divide each coordinate of the current point by
804         // each coordinate of `scale`.
805         unscaleBy: function (point) {
806                 return new Point(this.x / point.x, this.y / point.y);
807         },
808
809         // @method round(): Point
810         // Returns a copy of the current point with rounded coordinates.
811         round: function () {
812                 return this.clone()._round();
813         },
814
815         _round: function () {
816                 this.x = Math.round(this.x);
817                 this.y = Math.round(this.y);
818                 return this;
819         },
820
821         // @method floor(): Point
822         // Returns a copy of the current point with floored coordinates (rounded down).
823         floor: function () {
824                 return this.clone()._floor();
825         },
826
827         _floor: function () {
828                 this.x = Math.floor(this.x);
829                 this.y = Math.floor(this.y);
830                 return this;
831         },
832
833         // @method ceil(): Point
834         // Returns a copy of the current point with ceiled coordinates (rounded up).
835         ceil: function () {
836                 return this.clone()._ceil();
837         },
838
839         _ceil: function () {
840                 this.x = Math.ceil(this.x);
841                 this.y = Math.ceil(this.y);
842                 return this;
843         },
844
845         // @method trunc(): Point
846         // Returns a copy of the current point with truncated coordinates (rounded towards zero).
847         trunc: function () {
848                 return this.clone()._trunc();
849         },
850
851         _trunc: function () {
852                 this.x = trunc(this.x);
853                 this.y = trunc(this.y);
854                 return this;
855         },
856
857         // @method distanceTo(otherPoint: Point): Number
858         // Returns the cartesian distance between the current and the given points.
859         distanceTo: function (point) {
860                 point = toPoint(point);
861
862                 var x = point.x - this.x,
863                     y = point.y - this.y;
864
865                 return Math.sqrt(x * x + y * y);
866         },
867
868         // @method equals(otherPoint: Point): Boolean
869         // Returns `true` if the given point has the same coordinates.
870         equals: function (point) {
871                 point = toPoint(point);
872
873                 return point.x === this.x &&
874                        point.y === this.y;
875         },
876
877         // @method contains(otherPoint: Point): Boolean
878         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
879         contains: function (point) {
880                 point = toPoint(point);
881
882                 return Math.abs(point.x) <= Math.abs(this.x) &&
883                        Math.abs(point.y) <= Math.abs(this.y);
884         },
885
886         // @method toString(): String
887         // Returns a string representation of the point for debugging purposes.
888         toString: function () {
889                 return 'Point(' +
890                         formatNum(this.x) + ', ' +
891                         formatNum(this.y) + ')';
892         }
893 };
894
895 // @factory L.point(x: Number, y: Number, round?: Boolean)
896 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
897
898 // @alternative
899 // @factory L.point(coords: Number[])
900 // Expects an array of the form `[x, y]` instead.
901
902 // @alternative
903 // @factory L.point(coords: Object)
904 // Expects a plain object of the form `{x: Number, y: Number}` instead.
905 function toPoint(x, y, round) {
906         if (x instanceof Point) {
907                 return x;
908         }
909         if (isArray(x)) {
910                 return new Point(x[0], x[1]);
911         }
912         if (x === undefined || x === null) {
913                 return x;
914         }
915         if (typeof x === 'object' && 'x' in x && 'y' in x) {
916                 return new Point(x.x, x.y);
917         }
918         return new Point(x, y, round);
919 }
920
921 /*
922  * @class Bounds
923  * @aka L.Bounds
924  *
925  * Represents a rectangular area in pixel coordinates.
926  *
927  * @example
928  *
929  * ```js
930  * var p1 = L.point(10, 10),
931  * p2 = L.point(40, 60),
932  * bounds = L.bounds(p1, p2);
933  * ```
934  *
935  * 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:
936  *
937  * ```js
938  * otherBounds.intersects([[10, 10], [40, 60]]);
939  * ```
940  *
941  * Note that `Bounds` does not inherit from Leafet's `Class` object,
942  * which means new classes can't inherit from it, and new methods
943  * can't be added to it with the `include` function.
944  */
945
946 function Bounds(a, b) {
947         if (!a) { return; }
948
949         var points = b ? [a, b] : a;
950
951         for (var i = 0, len = points.length; i < len; i++) {
952                 this.extend(points[i]);
953         }
954 }
955
956 Bounds.prototype = {
957         // @method extend(point: Point): this
958         // Extends the bounds to contain the given point.
959         extend: function (point) { // (Point)
960                 point = toPoint(point);
961
962                 // @property min: Point
963                 // The top left corner of the rectangle.
964                 // @property max: Point
965                 // The bottom right corner of the rectangle.
966                 if (!this.min && !this.max) {
967                         this.min = point.clone();
968                         this.max = point.clone();
969                 } else {
970                         this.min.x = Math.min(point.x, this.min.x);
971                         this.max.x = Math.max(point.x, this.max.x);
972                         this.min.y = Math.min(point.y, this.min.y);
973                         this.max.y = Math.max(point.y, this.max.y);
974                 }
975                 return this;
976         },
977
978         // @method getCenter(round?: Boolean): Point
979         // Returns the center point of the bounds.
980         getCenter: function (round) {
981                 return new Point(
982                         (this.min.x + this.max.x) / 2,
983                         (this.min.y + this.max.y) / 2, round);
984         },
985
986         // @method getBottomLeft(): Point
987         // Returns the bottom-left point of the bounds.
988         getBottomLeft: function () {
989                 return new Point(this.min.x, this.max.y);
990         },
991
992         // @method getTopRight(): Point
993         // Returns the top-right point of the bounds.
994         getTopRight: function () { // -> Point
995                 return new Point(this.max.x, this.min.y);
996         },
997
998         // @method getTopLeft(): Point
999         // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
1000         getTopLeft: function () {
1001                 return this.min; // left, top
1002         },
1003
1004         // @method getBottomRight(): Point
1005         // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
1006         getBottomRight: function () {
1007                 return this.max; // right, bottom
1008         },
1009
1010         // @method getSize(): Point
1011         // Returns the size of the given bounds
1012         getSize: function () {
1013                 return this.max.subtract(this.min);
1014         },
1015
1016         // @method contains(otherBounds: Bounds): Boolean
1017         // Returns `true` if the rectangle contains the given one.
1018         // @alternative
1019         // @method contains(point: Point): Boolean
1020         // Returns `true` if the rectangle contains the given point.
1021         contains: function (obj) {
1022                 var min, max;
1023
1024                 if (typeof obj[0] === 'number' || obj instanceof Point) {
1025                         obj = toPoint(obj);
1026                 } else {
1027                         obj = toBounds(obj);
1028                 }
1029
1030                 if (obj instanceof Bounds) {
1031                         min = obj.min;
1032                         max = obj.max;
1033                 } else {
1034                         min = max = obj;
1035                 }
1036
1037                 return (min.x >= this.min.x) &&
1038                        (max.x <= this.max.x) &&
1039                        (min.y >= this.min.y) &&
1040                        (max.y <= this.max.y);
1041         },
1042
1043         // @method intersects(otherBounds: Bounds): Boolean
1044         // Returns `true` if the rectangle intersects the given bounds. Two bounds
1045         // intersect if they have at least one point in common.
1046         intersects: function (bounds) { // (Bounds) -> Boolean
1047                 bounds = toBounds(bounds);
1048
1049                 var min = this.min,
1050                     max = this.max,
1051                     min2 = bounds.min,
1052                     max2 = bounds.max,
1053                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1054                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1055
1056                 return xIntersects && yIntersects;
1057         },
1058
1059         // @method overlaps(otherBounds: Bounds): Boolean
1060         // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1061         // overlap if their intersection is an area.
1062         overlaps: function (bounds) { // (Bounds) -> Boolean
1063                 bounds = toBounds(bounds);
1064
1065                 var min = this.min,
1066                     max = this.max,
1067                     min2 = bounds.min,
1068                     max2 = bounds.max,
1069                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1070                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1071
1072                 return xOverlaps && yOverlaps;
1073         },
1074
1075         isValid: function () {
1076                 return !!(this.min && this.max);
1077         }
1078 };
1079
1080
1081 // @factory L.bounds(corner1: Point, corner2: Point)
1082 // Creates a Bounds object from two corners coordinate pairs.
1083 // @alternative
1084 // @factory L.bounds(points: Point[])
1085 // Creates a Bounds object from the given array of points.
1086 function toBounds(a, b) {
1087         if (!a || a instanceof Bounds) {
1088                 return a;
1089         }
1090         return new Bounds(a, b);
1091 }
1092
1093 /*
1094  * @class LatLngBounds
1095  * @aka L.LatLngBounds
1096  *
1097  * Represents a rectangular geographical area on a map.
1098  *
1099  * @example
1100  *
1101  * ```js
1102  * var corner1 = L.latLng(40.712, -74.227),
1103  * corner2 = L.latLng(40.774, -74.125),
1104  * bounds = L.latLngBounds(corner1, corner2);
1105  * ```
1106  *
1107  * 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:
1108  *
1109  * ```js
1110  * map.fitBounds([
1111  *      [40.712, -74.227],
1112  *      [40.774, -74.125]
1113  * ]);
1114  * ```
1115  *
1116  * 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.
1117  *
1118  * Note that `LatLngBounds` does not inherit from Leafet's `Class` object,
1119  * which means new classes can't inherit from it, and new methods
1120  * can't be added to it with the `include` function.
1121  */
1122
1123 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1124         if (!corner1) { return; }
1125
1126         var latlngs = corner2 ? [corner1, corner2] : corner1;
1127
1128         for (var i = 0, len = latlngs.length; i < len; i++) {
1129                 this.extend(latlngs[i]);
1130         }
1131 }
1132
1133 LatLngBounds.prototype = {
1134
1135         // @method extend(latlng: LatLng): this
1136         // Extend the bounds to contain the given point
1137
1138         // @alternative
1139         // @method extend(otherBounds: LatLngBounds): this
1140         // Extend the bounds to contain the given bounds
1141         extend: function (obj) {
1142                 var sw = this._southWest,
1143                     ne = this._northEast,
1144                     sw2, ne2;
1145
1146                 if (obj instanceof LatLng) {
1147                         sw2 = obj;
1148                         ne2 = obj;
1149
1150                 } else if (obj instanceof LatLngBounds) {
1151                         sw2 = obj._southWest;
1152                         ne2 = obj._northEast;
1153
1154                         if (!sw2 || !ne2) { return this; }
1155
1156                 } else {
1157                         return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1158                 }
1159
1160                 if (!sw && !ne) {
1161                         this._southWest = new LatLng(sw2.lat, sw2.lng);
1162                         this._northEast = new LatLng(ne2.lat, ne2.lng);
1163                 } else {
1164                         sw.lat = Math.min(sw2.lat, sw.lat);
1165                         sw.lng = Math.min(sw2.lng, sw.lng);
1166                         ne.lat = Math.max(ne2.lat, ne.lat);
1167                         ne.lng = Math.max(ne2.lng, ne.lng);
1168                 }
1169
1170                 return this;
1171         },
1172
1173         // @method pad(bufferRatio: Number): LatLngBounds
1174         // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
1175         // For example, a ratio of 0.5 extends the bounds by 50% in each direction.
1176         // Negative values will retract the bounds.
1177         pad: function (bufferRatio) {
1178                 var sw = this._southWest,
1179                     ne = this._northEast,
1180                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1181                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1182
1183                 return new LatLngBounds(
1184                         new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1185                         new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1186         },
1187
1188         // @method getCenter(): LatLng
1189         // Returns the center point of the bounds.
1190         getCenter: function () {
1191                 return new LatLng(
1192                         (this._southWest.lat + this._northEast.lat) / 2,
1193                         (this._southWest.lng + this._northEast.lng) / 2);
1194         },
1195
1196         // @method getSouthWest(): LatLng
1197         // Returns the south-west point of the bounds.
1198         getSouthWest: function () {
1199                 return this._southWest;
1200         },
1201
1202         // @method getNorthEast(): LatLng
1203         // Returns the north-east point of the bounds.
1204         getNorthEast: function () {
1205                 return this._northEast;
1206         },
1207
1208         // @method getNorthWest(): LatLng
1209         // Returns the north-west point of the bounds.
1210         getNorthWest: function () {
1211                 return new LatLng(this.getNorth(), this.getWest());
1212         },
1213
1214         // @method getSouthEast(): LatLng
1215         // Returns the south-east point of the bounds.
1216         getSouthEast: function () {
1217                 return new LatLng(this.getSouth(), this.getEast());
1218         },
1219
1220         // @method getWest(): Number
1221         // Returns the west longitude of the bounds
1222         getWest: function () {
1223                 return this._southWest.lng;
1224         },
1225
1226         // @method getSouth(): Number
1227         // Returns the south latitude of the bounds
1228         getSouth: function () {
1229                 return this._southWest.lat;
1230         },
1231
1232         // @method getEast(): Number
1233         // Returns the east longitude of the bounds
1234         getEast: function () {
1235                 return this._northEast.lng;
1236         },
1237
1238         // @method getNorth(): Number
1239         // Returns the north latitude of the bounds
1240         getNorth: function () {
1241                 return this._northEast.lat;
1242         },
1243
1244         // @method contains(otherBounds: LatLngBounds): Boolean
1245         // Returns `true` if the rectangle contains the given one.
1246
1247         // @alternative
1248         // @method contains (latlng: LatLng): Boolean
1249         // Returns `true` if the rectangle contains the given point.
1250         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1251                 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1252                         obj = toLatLng(obj);
1253                 } else {
1254                         obj = toLatLngBounds(obj);
1255                 }
1256
1257                 var sw = this._southWest,
1258                     ne = this._northEast,
1259                     sw2, ne2;
1260
1261                 if (obj instanceof LatLngBounds) {
1262                         sw2 = obj.getSouthWest();
1263                         ne2 = obj.getNorthEast();
1264                 } else {
1265                         sw2 = ne2 = obj;
1266                 }
1267
1268                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1269                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1270         },
1271
1272         // @method intersects(otherBounds: LatLngBounds): Boolean
1273         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1274         intersects: function (bounds) {
1275                 bounds = toLatLngBounds(bounds);
1276
1277                 var sw = this._southWest,
1278                     ne = this._northEast,
1279                     sw2 = bounds.getSouthWest(),
1280                     ne2 = bounds.getNorthEast(),
1281
1282                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1283                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1284
1285                 return latIntersects && lngIntersects;
1286         },
1287
1288         // @method overlaps(otherBounds: Bounds): Boolean
1289         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1290         overlaps: function (bounds) {
1291                 bounds = toLatLngBounds(bounds);
1292
1293                 var sw = this._southWest,
1294                     ne = this._northEast,
1295                     sw2 = bounds.getSouthWest(),
1296                     ne2 = bounds.getNorthEast(),
1297
1298                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1299                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1300
1301                 return latOverlaps && lngOverlaps;
1302         },
1303
1304         // @method toBBoxString(): String
1305         // 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.
1306         toBBoxString: function () {
1307                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1308         },
1309
1310         // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1311         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
1312         equals: function (bounds, maxMargin) {
1313                 if (!bounds) { return false; }
1314
1315                 bounds = toLatLngBounds(bounds);
1316
1317                 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1318                        this._northEast.equals(bounds.getNorthEast(), maxMargin);
1319         },
1320
1321         // @method isValid(): Boolean
1322         // Returns `true` if the bounds are properly initialized.
1323         isValid: function () {
1324                 return !!(this._southWest && this._northEast);
1325         }
1326 };
1327
1328 // TODO International date line?
1329
1330 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1331 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1332
1333 // @alternative
1334 // @factory L.latLngBounds(latlngs: LatLng[])
1335 // 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).
1336 function toLatLngBounds(a, b) {
1337         if (a instanceof LatLngBounds) {
1338                 return a;
1339         }
1340         return new LatLngBounds(a, b);
1341 }
1342
1343 /* @class LatLng
1344  * @aka L.LatLng
1345  *
1346  * Represents a geographical point with a certain latitude and longitude.
1347  *
1348  * @example
1349  *
1350  * ```
1351  * var latlng = L.latLng(50.5, 30.5);
1352  * ```
1353  *
1354  * 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:
1355  *
1356  * ```
1357  * map.panTo([50, 30]);
1358  * map.panTo({lon: 30, lat: 50});
1359  * map.panTo({lat: 50, lng: 30});
1360  * map.panTo(L.latLng(50, 30));
1361  * ```
1362  *
1363  * Note that `LatLng` does not inherit from Leaflet's `Class` object,
1364  * which means new classes can't inherit from it, and new methods
1365  * can't be added to it with the `include` function.
1366  */
1367
1368 function LatLng(lat, lng, alt) {
1369         if (isNaN(lat) || isNaN(lng)) {
1370                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1371         }
1372
1373         // @property lat: Number
1374         // Latitude in degrees
1375         this.lat = +lat;
1376
1377         // @property lng: Number
1378         // Longitude in degrees
1379         this.lng = +lng;
1380
1381         // @property alt: Number
1382         // Altitude in meters (optional)
1383         if (alt !== undefined) {
1384                 this.alt = +alt;
1385         }
1386 }
1387
1388 LatLng.prototype = {
1389         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1390         // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
1391         equals: function (obj, maxMargin) {
1392                 if (!obj) { return false; }
1393
1394                 obj = toLatLng(obj);
1395
1396                 var margin = Math.max(
1397                         Math.abs(this.lat - obj.lat),
1398                         Math.abs(this.lng - obj.lng));
1399
1400                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1401         },
1402
1403         // @method toString(): String
1404         // Returns a string representation of the point (for debugging purposes).
1405         toString: function (precision) {
1406                 return 'LatLng(' +
1407                         formatNum(this.lat, precision) + ', ' +
1408                         formatNum(this.lng, precision) + ')';
1409         },
1410
1411         // @method distanceTo(otherLatLng: LatLng): Number
1412         // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
1413         distanceTo: function (other) {
1414                 return Earth.distance(this, toLatLng(other));
1415         },
1416
1417         // @method wrap(): LatLng
1418         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1419         wrap: function () {
1420                 return Earth.wrapLatLng(this);
1421         },
1422
1423         // @method toBounds(sizeInMeters: Number): LatLngBounds
1424         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1425         toBounds: function (sizeInMeters) {
1426                 var latAccuracy = 180 * sizeInMeters / 40075017,
1427                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1428
1429                 return toLatLngBounds(
1430                         [this.lat - latAccuracy, this.lng - lngAccuracy],
1431                         [this.lat + latAccuracy, this.lng + lngAccuracy]);
1432         },
1433
1434         clone: function () {
1435                 return new LatLng(this.lat, this.lng, this.alt);
1436         }
1437 };
1438
1439
1440
1441 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1442 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1443
1444 // @alternative
1445 // @factory L.latLng(coords: Array): LatLng
1446 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1447
1448 // @alternative
1449 // @factory L.latLng(coords: Object): LatLng
1450 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1451
1452 function toLatLng(a, b, c) {
1453         if (a instanceof LatLng) {
1454                 return a;
1455         }
1456         if (isArray(a) && typeof a[0] !== 'object') {
1457                 if (a.length === 3) {
1458                         return new LatLng(a[0], a[1], a[2]);
1459                 }
1460                 if (a.length === 2) {
1461                         return new LatLng(a[0], a[1]);
1462                 }
1463                 return null;
1464         }
1465         if (a === undefined || a === null) {
1466                 return a;
1467         }
1468         if (typeof a === 'object' && 'lat' in a) {
1469                 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1470         }
1471         if (b === undefined) {
1472                 return null;
1473         }
1474         return new LatLng(a, b, c);
1475 }
1476
1477 /*
1478  * @namespace CRS
1479  * @crs L.CRS.Base
1480  * Object that defines coordinate reference systems for projecting
1481  * geographical points into pixel (screen) coordinates and back (and to
1482  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1483  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1484  *
1485  * Leaflet defines the most usual CRSs by default. If you want to use a
1486  * CRS not defined by default, take a look at the
1487  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1488  *
1489  * Note that the CRS instances do not inherit from Leafet's `Class` object,
1490  * and can't be instantiated. Also, new classes can't inherit from them,
1491  * and methods can't be added to them with the `include` function.
1492  */
1493
1494 var CRS = {
1495         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1496         // Projects geographical coordinates into pixel coordinates for a given zoom.
1497         latLngToPoint: function (latlng, zoom) {
1498                 var projectedPoint = this.projection.project(latlng),
1499                     scale = this.scale(zoom);
1500
1501                 return this.transformation._transform(projectedPoint, scale);
1502         },
1503
1504         // @method pointToLatLng(point: Point, zoom: Number): LatLng
1505         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1506         // zoom into geographical coordinates.
1507         pointToLatLng: function (point, zoom) {
1508                 var scale = this.scale(zoom),
1509                     untransformedPoint = this.transformation.untransform(point, scale);
1510
1511                 return this.projection.unproject(untransformedPoint);
1512         },
1513
1514         // @method project(latlng: LatLng): Point
1515         // Projects geographical coordinates into coordinates in units accepted for
1516         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1517         project: function (latlng) {
1518                 return this.projection.project(latlng);
1519         },
1520
1521         // @method unproject(point: Point): LatLng
1522         // Given a projected coordinate returns the corresponding LatLng.
1523         // The inverse of `project`.
1524         unproject: function (point) {
1525                 return this.projection.unproject(point);
1526         },
1527
1528         // @method scale(zoom: Number): Number
1529         // Returns the scale used when transforming projected coordinates into
1530         // pixel coordinates for a particular zoom. For example, it returns
1531         // `256 * 2^zoom` for Mercator-based CRS.
1532         scale: function (zoom) {
1533                 return 256 * Math.pow(2, zoom);
1534         },
1535
1536         // @method zoom(scale: Number): Number
1537         // Inverse of `scale()`, returns the zoom level corresponding to a scale
1538         // factor of `scale`.
1539         zoom: function (scale) {
1540                 return Math.log(scale / 256) / Math.LN2;
1541         },
1542
1543         // @method getProjectedBounds(zoom: Number): Bounds
1544         // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1545         getProjectedBounds: function (zoom) {
1546                 if (this.infinite) { return null; }
1547
1548                 var b = this.projection.bounds,
1549                     s = this.scale(zoom),
1550                     min = this.transformation.transform(b.min, s),
1551                     max = this.transformation.transform(b.max, s);
1552
1553                 return new Bounds(min, max);
1554         },
1555
1556         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1557         // Returns the distance between two geographical coordinates.
1558
1559         // @property code: String
1560         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1561         //
1562         // @property wrapLng: Number[]
1563         // An array of two numbers defining whether the longitude (horizontal) coordinate
1564         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1565         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1566         //
1567         // @property wrapLat: Number[]
1568         // Like `wrapLng`, but for the latitude (vertical) axis.
1569
1570         // wrapLng: [min, max],
1571         // wrapLat: [min, max],
1572
1573         // @property infinite: Boolean
1574         // If true, the coordinate space will be unbounded (infinite in both axes)
1575         infinite: false,
1576
1577         // @method wrapLatLng(latlng: LatLng): LatLng
1578         // Returns a `LatLng` where lat and lng has been wrapped according to the
1579         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1580         wrapLatLng: function (latlng) {
1581                 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1582                     lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1583                     alt = latlng.alt;
1584
1585                 return new LatLng(lat, lng, alt);
1586         },
1587
1588         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1589         // Returns a `LatLngBounds` with the same size as the given one, ensuring
1590         // that its center is within the CRS's bounds.
1591         // Only accepts actual `L.LatLngBounds` instances, not arrays.
1592         wrapLatLngBounds: function (bounds) {
1593                 var center = bounds.getCenter(),
1594                     newCenter = this.wrapLatLng(center),
1595                     latShift = center.lat - newCenter.lat,
1596                     lngShift = center.lng - newCenter.lng;
1597
1598                 if (latShift === 0 && lngShift === 0) {
1599                         return bounds;
1600                 }
1601
1602                 var sw = bounds.getSouthWest(),
1603                     ne = bounds.getNorthEast(),
1604                     newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1605                     newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1606
1607                 return new LatLngBounds(newSw, newNe);
1608         }
1609 };
1610
1611 /*
1612  * @namespace CRS
1613  * @crs L.CRS.Earth
1614  *
1615  * Serves as the base for CRS that are global such that they cover the earth.
1616  * Can only be used as the base for other CRS and cannot be used directly,
1617  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1618  * meters.
1619  */
1620
1621 var Earth = extend({}, CRS, {
1622         wrapLng: [-180, 180],
1623
1624         // Mean Earth Radius, as recommended for use by
1625         // the International Union of Geodesy and Geophysics,
1626         // see http://rosettacode.org/wiki/Haversine_formula
1627         R: 6371000,
1628
1629         // distance between two geographical points using spherical law of cosines approximation
1630         distance: function (latlng1, latlng2) {
1631                 var rad = Math.PI / 180,
1632                     lat1 = latlng1.lat * rad,
1633                     lat2 = latlng2.lat * rad,
1634                     sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
1635                     sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
1636                     a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
1637                     c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
1638                 return this.R * c;
1639         }
1640 });
1641
1642 /*
1643  * @namespace Projection
1644  * @projection L.Projection.SphericalMercator
1645  *
1646  * Spherical Mercator projection — the most common projection for online maps,
1647  * used by almost all free and commercial tile providers. Assumes that Earth is
1648  * a sphere. Used by the `EPSG:3857` CRS.
1649  */
1650
1651 var earthRadius = 6378137;
1652
1653 var SphericalMercator = {
1654
1655         R: earthRadius,
1656         MAX_LATITUDE: 85.0511287798,
1657
1658         project: function (latlng) {
1659                 var d = Math.PI / 180,
1660                     max = this.MAX_LATITUDE,
1661                     lat = Math.max(Math.min(max, latlng.lat), -max),
1662                     sin = Math.sin(lat * d);
1663
1664                 return new Point(
1665                         this.R * latlng.lng * d,
1666                         this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1667         },
1668
1669         unproject: function (point) {
1670                 var d = 180 / Math.PI;
1671
1672                 return new LatLng(
1673                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1674                         point.x * d / this.R);
1675         },
1676
1677         bounds: (function () {
1678                 var d = earthRadius * Math.PI;
1679                 return new Bounds([-d, -d], [d, d]);
1680         })()
1681 };
1682
1683 /*
1684  * @class Transformation
1685  * @aka L.Transformation
1686  *
1687  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1688  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1689  * the reverse. Used by Leaflet in its projections code.
1690  *
1691  * @example
1692  *
1693  * ```js
1694  * var transformation = L.transformation(2, 5, -1, 10),
1695  *      p = L.point(1, 2),
1696  *      p2 = transformation.transform(p), //  L.point(7, 8)
1697  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1698  * ```
1699  */
1700
1701
1702 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1703 // Creates a `Transformation` object with the given coefficients.
1704 function Transformation(a, b, c, d) {
1705         if (isArray(a)) {
1706                 // use array properties
1707                 this._a = a[0];
1708                 this._b = a[1];
1709                 this._c = a[2];
1710                 this._d = a[3];
1711                 return;
1712         }
1713         this._a = a;
1714         this._b = b;
1715         this._c = c;
1716         this._d = d;
1717 }
1718
1719 Transformation.prototype = {
1720         // @method transform(point: Point, scale?: Number): Point
1721         // Returns a transformed point, optionally multiplied by the given scale.
1722         // Only accepts actual `L.Point` instances, not arrays.
1723         transform: function (point, scale) { // (Point, Number) -> Point
1724                 return this._transform(point.clone(), scale);
1725         },
1726
1727         // destructive transform (faster)
1728         _transform: function (point, scale) {
1729                 scale = scale || 1;
1730                 point.x = scale * (this._a * point.x + this._b);
1731                 point.y = scale * (this._c * point.y + this._d);
1732                 return point;
1733         },
1734
1735         // @method untransform(point: Point, scale?: Number): Point
1736         // Returns the reverse transformation of the given point, optionally divided
1737         // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1738         untransform: function (point, scale) {
1739                 scale = scale || 1;
1740                 return new Point(
1741                         (point.x / scale - this._b) / this._a,
1742                         (point.y / scale - this._d) / this._c);
1743         }
1744 };
1745
1746 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1747
1748 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1749 // Instantiates a Transformation object with the given coefficients.
1750
1751 // @alternative
1752 // @factory L.transformation(coefficients: Array): Transformation
1753 // Expects an coefficients array of the form
1754 // `[a: Number, b: Number, c: Number, d: Number]`.
1755
1756 function toTransformation(a, b, c, d) {
1757         return new Transformation(a, b, c, d);
1758 }
1759
1760 /*
1761  * @namespace CRS
1762  * @crs L.CRS.EPSG3857
1763  *
1764  * The most common CRS for online maps, used by almost all free and commercial
1765  * tile providers. Uses Spherical Mercator projection. Set in by default in
1766  * Map's `crs` option.
1767  */
1768
1769 var EPSG3857 = extend({}, Earth, {
1770         code: 'EPSG:3857',
1771         projection: SphericalMercator,
1772
1773         transformation: (function () {
1774                 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1775                 return toTransformation(scale, 0.5, -scale, 0.5);
1776         }())
1777 });
1778
1779 var EPSG900913 = extend({}, EPSG3857, {
1780         code: 'EPSG:900913'
1781 });
1782
1783 // @namespace SVG; @section
1784 // There are several static functions which can be called without instantiating L.SVG:
1785
1786 // @function create(name: String): SVGElement
1787 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1788 // corresponding to the class name passed. For example, using 'line' will return
1789 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1790 function svgCreate(name) {
1791         return document.createElementNS('http://www.w3.org/2000/svg', name);
1792 }
1793
1794 // @function pointsToPath(rings: Point[], closed: Boolean): String
1795 // Generates a SVG path string for multiple rings, with each ring turning
1796 // into "M..L..L.." instructions
1797 function pointsToPath(rings, closed) {
1798         var str = '',
1799         i, j, len, len2, points, p;
1800
1801         for (i = 0, len = rings.length; i < len; i++) {
1802                 points = rings[i];
1803
1804                 for (j = 0, len2 = points.length; j < len2; j++) {
1805                         p = points[j];
1806                         str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1807                 }
1808
1809                 // closes the ring for polygons; "x" is VML syntax
1810                 str += closed ? (svg ? 'z' : 'x') : '';
1811         }
1812
1813         // SVG complains about empty path strings
1814         return str || 'M0 0';
1815 }
1816
1817 /*
1818  * @namespace Browser
1819  * @aka L.Browser
1820  *
1821  * A namespace with static properties for browser/feature detection used by Leaflet internally.
1822  *
1823  * @example
1824  *
1825  * ```js
1826  * if (L.Browser.ielt9) {
1827  *   alert('Upgrade your browser, dude!');
1828  * }
1829  * ```
1830  */
1831
1832 var style$1 = document.documentElement.style;
1833
1834 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1835 var ie = 'ActiveXObject' in window;
1836
1837 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1838 var ielt9 = ie && !document.addEventListener;
1839
1840 // @property edge: Boolean; `true` for the Edge web browser.
1841 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1842
1843 // @property webkit: Boolean;
1844 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1845 var webkit = userAgentContains('webkit');
1846
1847 // @property android: Boolean
1848 // `true` for any browser running on an Android platform.
1849 var android = userAgentContains('android');
1850
1851 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1852 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1853
1854 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1855 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1856 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1857 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1858
1859 // @property opera: Boolean; `true` for the Opera browser
1860 var opera = !!window.opera;
1861
1862 // @property chrome: Boolean; `true` for the Chrome browser.
1863 var chrome = userAgentContains('chrome');
1864
1865 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1866 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1867
1868 // @property safari: Boolean; `true` for the Safari browser.
1869 var safari = !chrome && userAgentContains('safari');
1870
1871 var phantom = userAgentContains('phantom');
1872
1873 // @property opera12: Boolean
1874 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1875 var opera12 = 'OTransition' in style$1;
1876
1877 // @property win: Boolean; `true` when the browser is running in a Windows platform
1878 var win = navigator.platform.indexOf('Win') === 0;
1879
1880 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1881 var ie3d = ie && ('transition' in style$1);
1882
1883 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1884 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1885
1886 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1887 var gecko3d = 'MozPerspective' in style$1;
1888
1889 // @property any3d: Boolean
1890 // `true` for all browsers supporting CSS transforms.
1891 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1892
1893 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1894 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1895
1896 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1897 var mobileWebkit = mobile && webkit;
1898
1899 // @property mobileWebkit3d: Boolean
1900 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1901 var mobileWebkit3d = mobile && webkit3d;
1902
1903 // @property msPointer: Boolean
1904 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1905 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1906
1907 // @property pointer: Boolean
1908 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1909 var pointer = !webkit && !!(window.PointerEvent || msPointer);
1910
1911 // @property touch: Boolean
1912 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1913 // This does not necessarily mean that the browser is running in a computer with
1914 // a touchscreen, it only means that the browser is capable of understanding
1915 // touch events.
1916 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1917                 (window.DocumentTouch && document instanceof window.DocumentTouch));
1918
1919 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1920 var mobileOpera = mobile && opera;
1921
1922 // @property mobileGecko: Boolean
1923 // `true` for gecko-based browsers running in a mobile device.
1924 var mobileGecko = mobile && gecko;
1925
1926 // @property retina: Boolean
1927 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1928 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1929
1930 // @property passiveEvents: Boolean
1931 // `true` for browsers that support passive events.
1932 var passiveEvents = (function () {
1933         var supportsPassiveOption = false;
1934         try {
1935                 var opts = Object.defineProperty({}, 'passive', {
1936                         get: function () {
1937                                 supportsPassiveOption = true;
1938                         }
1939                 });
1940                 window.addEventListener('testPassiveEventSupport', falseFn, opts);
1941                 window.removeEventListener('testPassiveEventSupport', falseFn, opts);
1942         } catch (e) {
1943                 // Errors can safely be ignored since this is only a browser support test.
1944         }
1945         return supportsPassiveOption;
1946 });
1947
1948 // @property canvas: Boolean
1949 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1950 var canvas = (function () {
1951         return !!document.createElement('canvas').getContext;
1952 }());
1953
1954 // @property svg: Boolean
1955 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1956 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1957
1958 // @property vml: Boolean
1959 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1960 var vml = !svg && (function () {
1961         try {
1962                 var div = document.createElement('div');
1963                 div.innerHTML = '<v:shape adj="1"/>';
1964
1965                 var shape = div.firstChild;
1966                 shape.style.behavior = 'url(#default#VML)';
1967
1968                 return shape && (typeof shape.adj === 'object');
1969
1970         } catch (e) {
1971                 return false;
1972         }
1973 }());
1974
1975
1976 function userAgentContains(str) {
1977         return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1978 }
1979
1980
1981 var Browser = (Object.freeze || Object)({
1982         ie: ie,
1983         ielt9: ielt9,
1984         edge: edge,
1985         webkit: webkit,
1986         android: android,
1987         android23: android23,
1988         androidStock: androidStock,
1989         opera: opera,
1990         chrome: chrome,
1991         gecko: gecko,
1992         safari: safari,
1993         phantom: phantom,
1994         opera12: opera12,
1995         win: win,
1996         ie3d: ie3d,
1997         webkit3d: webkit3d,
1998         gecko3d: gecko3d,
1999         any3d: any3d,
2000         mobile: mobile,
2001         mobileWebkit: mobileWebkit,
2002         mobileWebkit3d: mobileWebkit3d,
2003         msPointer: msPointer,
2004         pointer: pointer,
2005         touch: touch,
2006         mobileOpera: mobileOpera,
2007         mobileGecko: mobileGecko,
2008         retina: retina,
2009         passiveEvents: passiveEvents,
2010         canvas: canvas,
2011         svg: svg,
2012         vml: vml
2013 });
2014
2015 /*
2016  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
2017  */
2018
2019
2020 var POINTER_DOWN =   msPointer ? 'MSPointerDown'   : 'pointerdown';
2021 var POINTER_MOVE =   msPointer ? 'MSPointerMove'   : 'pointermove';
2022 var POINTER_UP =     msPointer ? 'MSPointerUp'     : 'pointerup';
2023 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2024 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2025
2026 var _pointers = {};
2027 var _pointerDocListener = false;
2028
2029 // DomEvent.DoubleTap needs to know about this
2030 var _pointersCount = 0;
2031
2032 // Provides a touch events wrapper for (ms)pointer events.
2033 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2034
2035 function addPointerListener(obj, type, handler, id) {
2036         if (type === 'touchstart') {
2037                 _addPointerStart(obj, handler, id);
2038
2039         } else if (type === 'touchmove') {
2040                 _addPointerMove(obj, handler, id);
2041
2042         } else if (type === 'touchend') {
2043                 _addPointerEnd(obj, handler, id);
2044         }
2045
2046         return this;
2047 }
2048
2049 function removePointerListener(obj, type, id) {
2050         var handler = obj['_leaflet_' + type + id];
2051
2052         if (type === 'touchstart') {
2053                 obj.removeEventListener(POINTER_DOWN, handler, false);
2054
2055         } else if (type === 'touchmove') {
2056                 obj.removeEventListener(POINTER_MOVE, handler, false);
2057
2058         } else if (type === 'touchend') {
2059                 obj.removeEventListener(POINTER_UP, handler, false);
2060                 obj.removeEventListener(POINTER_CANCEL, handler, false);
2061         }
2062
2063         return this;
2064 }
2065
2066 function _addPointerStart(obj, handler, id) {
2067         var onDown = bind(function (e) {
2068                 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2069                         // In IE11, some touch events needs to fire for form controls, or
2070                         // the controls will stop working. We keep a whitelist of tag names that
2071                         // need these events. For other target tags, we prevent default on the event.
2072                         if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2073                                 preventDefault(e);
2074                         } else {
2075                                 return;
2076                         }
2077                 }
2078
2079                 _handlePointer(e, handler);
2080         });
2081
2082         obj['_leaflet_touchstart' + id] = onDown;
2083         obj.addEventListener(POINTER_DOWN, onDown, false);
2084
2085         // need to keep track of what pointers and how many are active to provide e.touches emulation
2086         if (!_pointerDocListener) {
2087                 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2088                 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2089                 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2090                 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2091                 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2092
2093                 _pointerDocListener = true;
2094         }
2095 }
2096
2097 function _globalPointerDown(e) {
2098         _pointers[e.pointerId] = e;
2099         _pointersCount++;
2100 }
2101
2102 function _globalPointerMove(e) {
2103         if (_pointers[e.pointerId]) {
2104                 _pointers[e.pointerId] = e;
2105         }
2106 }
2107
2108 function _globalPointerUp(e) {
2109         delete _pointers[e.pointerId];
2110         _pointersCount--;
2111 }
2112
2113 function _handlePointer(e, handler) {
2114         e.touches = [];
2115         for (var i in _pointers) {
2116                 e.touches.push(_pointers[i]);
2117         }
2118         e.changedTouches = [e];
2119
2120         handler(e);
2121 }
2122
2123 function _addPointerMove(obj, handler, id) {
2124         var onMove = function (e) {
2125                 // don't fire touch moves when mouse isn't down
2126                 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2127
2128                 _handlePointer(e, handler);
2129         };
2130
2131         obj['_leaflet_touchmove' + id] = onMove;
2132         obj.addEventListener(POINTER_MOVE, onMove, false);
2133 }
2134
2135 function _addPointerEnd(obj, handler, id) {
2136         var onUp = function (e) {
2137                 _handlePointer(e, handler);
2138         };
2139
2140         obj['_leaflet_touchend' + id] = onUp;
2141         obj.addEventListener(POINTER_UP, onUp, false);
2142         obj.addEventListener(POINTER_CANCEL, onUp, false);
2143 }
2144
2145 /*
2146  * Extends the event handling code with double tap support for mobile browsers.
2147  */
2148
2149 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2150 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2151 var _pre = '_leaflet_';
2152
2153 // inspired by Zepto touch code by Thomas Fuchs
2154 function addDoubleTapListener(obj, handler, id) {
2155         var last, touch$$1,
2156             doubleTap = false,
2157             delay = 250;
2158
2159         function onTouchStart(e) {
2160                 var count;
2161
2162                 if (pointer) {
2163                         if ((!edge) || e.pointerType === 'mouse') { return; }
2164                         count = _pointersCount;
2165                 } else {
2166                         count = e.touches.length;
2167                 }
2168
2169                 if (count > 1) { return; }
2170
2171                 var now = Date.now(),
2172                     delta = now - (last || now);
2173
2174                 touch$$1 = e.touches ? e.touches[0] : e;
2175                 doubleTap = (delta > 0 && delta <= delay);
2176                 last = now;
2177         }
2178
2179         function onTouchEnd(e) {
2180                 if (doubleTap && !touch$$1.cancelBubble) {
2181                         if (pointer) {
2182                                 if ((!edge) || e.pointerType === 'mouse') { return; }
2183                                 // work around .type being readonly with MSPointer* events
2184                                 var newTouch = {},
2185                                     prop, i;
2186
2187                                 for (i in touch$$1) {
2188                                         prop = touch$$1[i];
2189                                         newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2190                                 }
2191                                 touch$$1 = newTouch;
2192                         }
2193                         touch$$1.type = 'dblclick';
2194                         touch$$1.button = 0;
2195                         handler(touch$$1);
2196                         last = null;
2197                 }
2198         }
2199
2200         obj[_pre + _touchstart + id] = onTouchStart;
2201         obj[_pre + _touchend + id] = onTouchEnd;
2202         obj[_pre + 'dblclick' + id] = handler;
2203
2204         obj.addEventListener(_touchstart, onTouchStart, passiveEvents ? {passive: false} : false);
2205         obj.addEventListener(_touchend, onTouchEnd, passiveEvents ? {passive: false} : false);
2206
2207         // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2208         // the browser doesn't fire touchend/pointerup events but does fire
2209         // native dblclicks. See #4127.
2210         // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2211         obj.addEventListener('dblclick', handler, false);
2212
2213         return this;
2214 }
2215
2216 function removeDoubleTapListener(obj, id) {
2217         var touchstart = obj[_pre + _touchstart + id],
2218             touchend = obj[_pre + _touchend + id],
2219             dblclick = obj[_pre + 'dblclick' + id];
2220
2221         obj.removeEventListener(_touchstart, touchstart, passiveEvents ? {passive: false} : false);
2222         obj.removeEventListener(_touchend, touchend, passiveEvents ? {passive: false} : false);
2223         if (!edge) {
2224                 obj.removeEventListener('dblclick', dblclick, false);
2225         }
2226
2227         return this;
2228 }
2229
2230 /*
2231  * @namespace DomUtil
2232  *
2233  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2234  * tree, used by Leaflet internally.
2235  *
2236  * Most functions expecting or returning a `HTMLElement` also work for
2237  * SVG elements. The only difference is that classes refer to CSS classes
2238  * in HTML and SVG classes in SVG.
2239  */
2240
2241
2242 // @property TRANSFORM: String
2243 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2244 var TRANSFORM = testProp(
2245         ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2246
2247 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2248 // the same for the transitionend event, in particular the Android 4.1 stock browser
2249
2250 // @property TRANSITION: String
2251 // Vendor-prefixed transition style name.
2252 var TRANSITION = testProp(
2253         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2254
2255 // @property TRANSITION_END: String
2256 // Vendor-prefixed transitionend event name.
2257 var TRANSITION_END =
2258         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2259
2260
2261 // @function get(id: String|HTMLElement): HTMLElement
2262 // Returns an element given its DOM id, or returns the element itself
2263 // if it was passed directly.
2264 function get(id) {
2265         return typeof id === 'string' ? document.getElementById(id) : id;
2266 }
2267
2268 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2269 // Returns the value for a certain style attribute on an element,
2270 // including computed values or values set through CSS.
2271 function getStyle(el, style) {
2272         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2273
2274         if ((!value || value === 'auto') && document.defaultView) {
2275                 var css = document.defaultView.getComputedStyle(el, null);
2276                 value = css ? css[style] : null;
2277         }
2278         return value === 'auto' ? null : value;
2279 }
2280
2281 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2282 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2283 function create$1(tagName, className, container) {
2284         var el = document.createElement(tagName);
2285         el.className = className || '';
2286
2287         if (container) {
2288                 container.appendChild(el);
2289         }
2290         return el;
2291 }
2292
2293 // @function remove(el: HTMLElement)
2294 // Removes `el` from its parent element
2295 function remove(el) {
2296         var parent = el.parentNode;
2297         if (parent) {
2298                 parent.removeChild(el);
2299         }
2300 }
2301
2302 // @function empty(el: HTMLElement)
2303 // Removes all of `el`'s children elements from `el`
2304 function empty(el) {
2305         while (el.firstChild) {
2306                 el.removeChild(el.firstChild);
2307         }
2308 }
2309
2310 // @function toFront(el: HTMLElement)
2311 // Makes `el` the last child of its parent, so it renders in front of the other children.
2312 function toFront(el) {
2313         var parent = el.parentNode;
2314         if (parent && parent.lastChild !== el) {
2315                 parent.appendChild(el);
2316         }
2317 }
2318
2319 // @function toBack(el: HTMLElement)
2320 // Makes `el` the first child of its parent, so it renders behind the other children.
2321 function toBack(el) {
2322         var parent = el.parentNode;
2323         if (parent && parent.firstChild !== el) {
2324                 parent.insertBefore(el, parent.firstChild);
2325         }
2326 }
2327
2328 // @function hasClass(el: HTMLElement, name: String): Boolean
2329 // Returns `true` if the element's class attribute contains `name`.
2330 function hasClass(el, name) {
2331         if (el.classList !== undefined) {
2332                 return el.classList.contains(name);
2333         }
2334         var className = getClass(el);
2335         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2336 }
2337
2338 // @function addClass(el: HTMLElement, name: String)
2339 // Adds `name` to the element's class attribute.
2340 function addClass(el, name) {
2341         if (el.classList !== undefined) {
2342                 var classes = splitWords(name);
2343                 for (var i = 0, len = classes.length; i < len; i++) {
2344                         el.classList.add(classes[i]);
2345                 }
2346         } else if (!hasClass(el, name)) {
2347                 var className = getClass(el);
2348                 setClass(el, (className ? className + ' ' : '') + name);
2349         }
2350 }
2351
2352 // @function removeClass(el: HTMLElement, name: String)
2353 // Removes `name` from the element's class attribute.
2354 function removeClass(el, name) {
2355         if (el.classList !== undefined) {
2356                 el.classList.remove(name);
2357         } else {
2358                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2359         }
2360 }
2361
2362 // @function setClass(el: HTMLElement, name: String)
2363 // Sets the element's class.
2364 function setClass(el, name) {
2365         if (el.className.baseVal === undefined) {
2366                 el.className = name;
2367         } else {
2368                 // in case of SVG element
2369                 el.className.baseVal = name;
2370         }
2371 }
2372
2373 // @function getClass(el: HTMLElement): String
2374 // Returns the element's class.
2375 function getClass(el) {
2376         // Check if the element is an SVGElementInstance and use the correspondingElement instead
2377         // (Required for linked SVG elements in IE11.)
2378         if (el.correspondingElement) {
2379                 el = el.correspondingElement;
2380         }
2381         return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2382 }
2383
2384 // @function setOpacity(el: HTMLElement, opacity: Number)
2385 // Set the opacity of an element (including old IE support).
2386 // `opacity` must be a number from `0` to `1`.
2387 function setOpacity(el, value) {
2388         if ('opacity' in el.style) {
2389                 el.style.opacity = value;
2390         } else if ('filter' in el.style) {
2391                 _setOpacityIE(el, value);
2392         }
2393 }
2394
2395 function _setOpacityIE(el, value) {
2396         var filter = false,
2397             filterName = 'DXImageTransform.Microsoft.Alpha';
2398
2399         // filters collection throws an error if we try to retrieve a filter that doesn't exist
2400         try {
2401                 filter = el.filters.item(filterName);
2402         } catch (e) {
2403                 // don't set opacity to 1 if we haven't already set an opacity,
2404                 // it isn't needed and breaks transparent pngs.
2405                 if (value === 1) { return; }
2406         }
2407
2408         value = Math.round(value * 100);
2409
2410         if (filter) {
2411                 filter.Enabled = (value !== 100);
2412                 filter.Opacity = value;
2413         } else {
2414                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2415         }
2416 }
2417
2418 // @function testProp(props: String[]): String|false
2419 // Goes through the array of style names and returns the first name
2420 // that is a valid style name for an element. If no such name is found,
2421 // it returns false. Useful for vendor-prefixed styles like `transform`.
2422 function testProp(props) {
2423         var style = document.documentElement.style;
2424
2425         for (var i = 0; i < props.length; i++) {
2426                 if (props[i] in style) {
2427                         return props[i];
2428                 }
2429         }
2430         return false;
2431 }
2432
2433 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2434 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2435 // and optionally scaled by `scale`. Does not have an effect if the
2436 // browser doesn't support 3D CSS transforms.
2437 function setTransform(el, offset, scale) {
2438         var pos = offset || new Point(0, 0);
2439
2440         el.style[TRANSFORM] =
2441                 (ie3d ?
2442                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2443                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2444                 (scale ? ' scale(' + scale + ')' : '');
2445 }
2446
2447 // @function setPosition(el: HTMLElement, position: Point)
2448 // Sets the position of `el` to coordinates specified by `position`,
2449 // using CSS translate or top/left positioning depending on the browser
2450 // (used by Leaflet internally to position its layers).
2451 function setPosition(el, point) {
2452
2453         /*eslint-disable */
2454         el._leaflet_pos = point;
2455         /* eslint-enable */
2456
2457         if (any3d) {
2458                 setTransform(el, point);
2459         } else {
2460                 el.style.left = point.x + 'px';
2461                 el.style.top = point.y + 'px';
2462         }
2463 }
2464
2465 // @function getPosition(el: HTMLElement): Point
2466 // Returns the coordinates of an element previously positioned with setPosition.
2467 function getPosition(el) {
2468         // this method is only used for elements previously positioned using setPosition,
2469         // so it's safe to cache the position for performance
2470
2471         return el._leaflet_pos || new Point(0, 0);
2472 }
2473
2474 // @function disableTextSelection()
2475 // Prevents the user from generating `selectstart` DOM events, usually generated
2476 // when the user drags the mouse through a page with text. Used internally
2477 // by Leaflet to override the behaviour of any click-and-drag interaction on
2478 // the map. Affects drag interactions on the whole document.
2479
2480 // @function enableTextSelection()
2481 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2482 var disableTextSelection;
2483 var enableTextSelection;
2484 var _userSelect;
2485 if ('onselectstart' in document) {
2486         disableTextSelection = function () {
2487                 on(window, 'selectstart', preventDefault);
2488         };
2489         enableTextSelection = function () {
2490                 off(window, 'selectstart', preventDefault);
2491         };
2492 } else {
2493         var userSelectProperty = testProp(
2494                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2495
2496         disableTextSelection = function () {
2497                 if (userSelectProperty) {
2498                         var style = document.documentElement.style;
2499                         _userSelect = style[userSelectProperty];
2500                         style[userSelectProperty] = 'none';
2501                 }
2502         };
2503         enableTextSelection = function () {
2504                 if (userSelectProperty) {
2505                         document.documentElement.style[userSelectProperty] = _userSelect;
2506                         _userSelect = undefined;
2507                 }
2508         };
2509 }
2510
2511 // @function disableImageDrag()
2512 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2513 // for `dragstart` DOM events, usually generated when the user drags an image.
2514 function disableImageDrag() {
2515         on(window, 'dragstart', preventDefault);
2516 }
2517
2518 // @function enableImageDrag()
2519 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2520 function enableImageDrag() {
2521         off(window, 'dragstart', preventDefault);
2522 }
2523
2524 var _outlineElement;
2525 var _outlineStyle;
2526 // @function preventOutline(el: HTMLElement)
2527 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2528 // of the element `el` invisible. Used internally by Leaflet to prevent
2529 // focusable elements from displaying an outline when the user performs a
2530 // drag interaction on them.
2531 function preventOutline(element) {
2532         while (element.tabIndex === -1) {
2533                 element = element.parentNode;
2534         }
2535         if (!element.style) { return; }
2536         restoreOutline();
2537         _outlineElement = element;
2538         _outlineStyle = element.style.outline;
2539         element.style.outline = 'none';
2540         on(window, 'keydown', restoreOutline);
2541 }
2542
2543 // @function restoreOutline()
2544 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2545 function restoreOutline() {
2546         if (!_outlineElement) { return; }
2547         _outlineElement.style.outline = _outlineStyle;
2548         _outlineElement = undefined;
2549         _outlineStyle = undefined;
2550         off(window, 'keydown', restoreOutline);
2551 }
2552
2553 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2554 // Finds the closest parent node which size (width and height) is not null.
2555 function getSizedParentNode(element) {
2556         do {
2557                 element = element.parentNode;
2558         } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2559         return element;
2560 }
2561
2562 // @function getScale(el: HTMLElement): Object
2563 // Computes the CSS scale currently applied on the element.
2564 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2565 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2566 function getScale(element) {
2567         var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2568
2569         return {
2570                 x: rect.width / element.offsetWidth || 1,
2571                 y: rect.height / element.offsetHeight || 1,
2572                 boundingClientRect: rect
2573         };
2574 }
2575
2576
2577 var DomUtil = (Object.freeze || Object)({
2578         TRANSFORM: TRANSFORM,
2579         TRANSITION: TRANSITION,
2580         TRANSITION_END: TRANSITION_END,
2581         get: get,
2582         getStyle: getStyle,
2583         create: create$1,
2584         remove: remove,
2585         empty: empty,
2586         toFront: toFront,
2587         toBack: toBack,
2588         hasClass: hasClass,
2589         addClass: addClass,
2590         removeClass: removeClass,
2591         setClass: setClass,
2592         getClass: getClass,
2593         setOpacity: setOpacity,
2594         testProp: testProp,
2595         setTransform: setTransform,
2596         setPosition: setPosition,
2597         getPosition: getPosition,
2598         disableTextSelection: disableTextSelection,
2599         enableTextSelection: enableTextSelection,
2600         disableImageDrag: disableImageDrag,
2601         enableImageDrag: enableImageDrag,
2602         preventOutline: preventOutline,
2603         restoreOutline: restoreOutline,
2604         getSizedParentNode: getSizedParentNode,
2605         getScale: getScale
2606 });
2607
2608 /*
2609  * @namespace DomEvent
2610  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2611  */
2612
2613 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2614
2615 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2616 // Adds a listener function (`fn`) to a particular DOM event type of the
2617 // element `el`. You can optionally specify the context of the listener
2618 // (object the `this` keyword will point to). You can also pass several
2619 // space-separated types (e.g. `'click dblclick'`).
2620
2621 // @alternative
2622 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2623 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2624 function on(obj, types, fn, context) {
2625
2626         if (typeof types === 'object') {
2627                 for (var type in types) {
2628                         addOne(obj, type, types[type], fn);
2629                 }
2630         } else {
2631                 types = splitWords(types);
2632
2633                 for (var i = 0, len = types.length; i < len; i++) {
2634                         addOne(obj, types[i], fn, context);
2635                 }
2636         }
2637
2638         return this;
2639 }
2640
2641 var eventsKey = '_leaflet_events';
2642
2643 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2644 // Removes a previously added listener function.
2645 // Note that if you passed a custom context to on, you must pass the same
2646 // context to `off` in order to remove the listener.
2647
2648 // @alternative
2649 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2650 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2651 function off(obj, types, fn, context) {
2652
2653         if (typeof types === 'object') {
2654                 for (var type in types) {
2655                         removeOne(obj, type, types[type], fn);
2656                 }
2657         } else if (types) {
2658                 types = splitWords(types);
2659
2660                 for (var i = 0, len = types.length; i < len; i++) {
2661                         removeOne(obj, types[i], fn, context);
2662                 }
2663         } else {
2664                 for (var j in obj[eventsKey]) {
2665                         removeOne(obj, j, obj[eventsKey][j]);
2666                 }
2667                 delete obj[eventsKey];
2668         }
2669
2670         return this;
2671 }
2672
2673 function addOne(obj, type, fn, context) {
2674         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2675
2676         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2677
2678         var handler = function (e) {
2679                 return fn.call(context || obj, e || window.event);
2680         };
2681
2682         var originalHandler = handler;
2683
2684         if (pointer && type.indexOf('touch') === 0) {
2685                 // Needs DomEvent.Pointer.js
2686                 addPointerListener(obj, type, handler, id);
2687
2688         } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2689                    !(pointer && chrome)) {
2690                 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2691                 // See #5180
2692                 addDoubleTapListener(obj, handler, id);
2693
2694         } else if ('addEventListener' in obj) {
2695
2696                 if (type === 'mousewheel') {
2697                         obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
2698
2699                 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2700                         handler = function (e) {
2701                                 e = e || window.event;
2702                                 if (isExternalTarget(obj, e)) {
2703                                         originalHandler(e);
2704                                 }
2705                         };
2706                         obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2707
2708                 } else {
2709                         if (type === 'click' && android) {
2710                                 handler = function (e) {
2711                                         filterClick(e, originalHandler);
2712                                 };
2713                         }
2714                         obj.addEventListener(type, handler, false);
2715                 }
2716
2717         } else if ('attachEvent' in obj) {
2718                 obj.attachEvent('on' + type, handler);
2719         }
2720
2721         obj[eventsKey] = obj[eventsKey] || {};
2722         obj[eventsKey][id] = handler;
2723 }
2724
2725 function removeOne(obj, type, fn, context) {
2726
2727         var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2728             handler = obj[eventsKey] && obj[eventsKey][id];
2729
2730         if (!handler) { return this; }
2731
2732         if (pointer && type.indexOf('touch') === 0) {
2733                 removePointerListener(obj, type, id);
2734
2735         } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2736                    !(pointer && chrome)) {
2737                 removeDoubleTapListener(obj, id);
2738
2739         } else if ('removeEventListener' in obj) {
2740
2741                 if (type === 'mousewheel') {
2742                         obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, passiveEvents ? {passive: false} : false);
2743
2744                 } else {
2745                         obj.removeEventListener(
2746                                 type === 'mouseenter' ? 'mouseover' :
2747                                 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2748                 }
2749
2750         } else if ('detachEvent' in obj) {
2751                 obj.detachEvent('on' + type, handler);
2752         }
2753
2754         obj[eventsKey][id] = null;
2755 }
2756
2757 // @function stopPropagation(ev: DOMEvent): this
2758 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2759 // ```js
2760 // L.DomEvent.on(div, 'click', function (ev) {
2761 //      L.DomEvent.stopPropagation(ev);
2762 // });
2763 // ```
2764 function stopPropagation(e) {
2765
2766         if (e.stopPropagation) {
2767                 e.stopPropagation();
2768         } else if (e.originalEvent) {  // In case of Leaflet event.
2769                 e.originalEvent._stopped = true;
2770         } else {
2771                 e.cancelBubble = true;
2772         }
2773         skipped(e);
2774
2775         return this;
2776 }
2777
2778 // @function disableScrollPropagation(el: HTMLElement): this
2779 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2780 function disableScrollPropagation(el) {
2781         addOne(el, 'mousewheel', stopPropagation);
2782         return this;
2783 }
2784
2785 // @function disableClickPropagation(el: HTMLElement): this
2786 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2787 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2788 function disableClickPropagation(el) {
2789         on(el, 'mousedown touchstart dblclick', stopPropagation);
2790         addOne(el, 'click', fakeStop);
2791         return this;
2792 }
2793
2794 // @function preventDefault(ev: DOMEvent): this
2795 // Prevents the default action of the DOM Event `ev` from happening (such as
2796 // following a link in the href of the a element, or doing a POST request
2797 // with page reload when a `<form>` is submitted).
2798 // Use it inside listener functions.
2799 function preventDefault(e) {
2800         if (e.preventDefault) {
2801                 e.preventDefault();
2802         } else {
2803                 e.returnValue = false;
2804         }
2805         return this;
2806 }
2807
2808 // @function stop(ev: DOMEvent): this
2809 // Does `stopPropagation` and `preventDefault` at the same time.
2810 function stop(e) {
2811         preventDefault(e);
2812         stopPropagation(e);
2813         return this;
2814 }
2815
2816 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2817 // Gets normalized mouse position from a DOM event relative to the
2818 // `container` (border excluded) or to the whole page if not specified.
2819 function getMousePosition(e, container) {
2820         if (!container) {
2821                 return new Point(e.clientX, e.clientY);
2822         }
2823
2824         var scale = getScale(container),
2825             offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)
2826
2827         return new Point(
2828                 // offset.left/top values are in page scale (like clientX/Y),
2829                 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2830                 (e.clientX - offset.left) / scale.x - container.clientLeft,
2831                 (e.clientY - offset.top) / scale.y - container.clientTop
2832         );
2833 }
2834
2835 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2836 // and Firefox scrolls device pixels, not CSS pixels
2837 var wheelPxFactor =
2838         (win && chrome) ? 2 * window.devicePixelRatio :
2839         gecko ? window.devicePixelRatio : 1;
2840
2841 // @function getWheelDelta(ev: DOMEvent): Number
2842 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2843 // pixels scrolled (negative if scrolling down).
2844 // Events from pointing devices without precise scrolling are mapped to
2845 // a best guess of 60 pixels.
2846 function getWheelDelta(e) {
2847         return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2848                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2849                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2850                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2851                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
2852                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2853                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2854                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2855                0;
2856 }
2857
2858 var skipEvents = {};
2859
2860 function fakeStop(e) {
2861         // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2862         skipEvents[e.type] = true;
2863 }
2864
2865 function skipped(e) {
2866         var events = skipEvents[e.type];
2867         // reset when checking, as it's only used in map container and propagates outside of the map
2868         skipEvents[e.type] = false;
2869         return events;
2870 }
2871
2872 // check if element really left/entered the event target (for mouseenter/mouseleave)
2873 function isExternalTarget(el, e) {
2874
2875         var related = e.relatedTarget;
2876
2877         if (!related) { return true; }
2878
2879         try {
2880                 while (related && (related !== el)) {
2881                         related = related.parentNode;
2882                 }
2883         } catch (err) {
2884                 return false;
2885         }
2886         return (related !== el);
2887 }
2888
2889 var lastClick;
2890
2891 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2892 function filterClick(e, handler) {
2893         var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2894             elapsed = lastClick && (timeStamp - lastClick);
2895
2896         // are they closer together than 500ms yet more than 100ms?
2897         // Android typically triggers them ~300ms apart while multiple listeners
2898         // on the same event should be triggered far faster;
2899         // or check if click is simulated on the element, and if it is, reject any non-simulated events
2900
2901         if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2902                 stop(e);
2903                 return;
2904         }
2905         lastClick = timeStamp;
2906
2907         handler(e);
2908 }
2909
2910
2911
2912
2913 var DomEvent = (Object.freeze || Object)({
2914         on: on,
2915         off: off,
2916         stopPropagation: stopPropagation,
2917         disableScrollPropagation: disableScrollPropagation,
2918         disableClickPropagation: disableClickPropagation,
2919         preventDefault: preventDefault,
2920         stop: stop,
2921         getMousePosition: getMousePosition,
2922         getWheelDelta: getWheelDelta,
2923         fakeStop: fakeStop,
2924         skipped: skipped,
2925         isExternalTarget: isExternalTarget,
2926         addListener: on,
2927         removeListener: off
2928 });
2929
2930 /*
2931  * @class PosAnimation
2932  * @aka L.PosAnimation
2933  * @inherits Evented
2934  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2935  *
2936  * @example
2937  * ```js
2938  * var fx = new L.PosAnimation();
2939  * fx.run(el, [300, 500], 0.5);
2940  * ```
2941  *
2942  * @constructor L.PosAnimation()
2943  * Creates a `PosAnimation` object.
2944  *
2945  */
2946
2947 var PosAnimation = Evented.extend({
2948
2949         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2950         // Run an animation of a given element to a new position, optionally setting
2951         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2952         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2953         // `0.5` by default).
2954         run: function (el, newPos, duration, easeLinearity) {
2955                 this.stop();
2956
2957                 this._el = el;
2958                 this._inProgress = true;
2959                 this._duration = duration || 0.25;
2960                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2961
2962                 this._startPos = getPosition(el);
2963                 this._offset = newPos.subtract(this._startPos);
2964                 this._startTime = +new Date();
2965
2966                 // @event start: Event
2967                 // Fired when the animation starts
2968                 this.fire('start');
2969
2970                 this._animate();
2971         },
2972
2973         // @method stop()
2974         // Stops the animation (if currently running).
2975         stop: function () {
2976                 if (!this._inProgress) { return; }
2977
2978                 this._step(true);
2979                 this._complete();
2980         },
2981
2982         _animate: function () {
2983                 // animation loop
2984                 this._animId = requestAnimFrame(this._animate, this);
2985                 this._step();
2986         },
2987
2988         _step: function (round) {
2989                 var elapsed = (+new Date()) - this._startTime,
2990                     duration = this._duration * 1000;
2991
2992                 if (elapsed < duration) {
2993                         this._runFrame(this._easeOut(elapsed / duration), round);
2994                 } else {
2995                         this._runFrame(1);
2996                         this._complete();
2997                 }
2998         },
2999
3000         _runFrame: function (progress, round) {
3001                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
3002                 if (round) {
3003                         pos._round();
3004                 }
3005                 setPosition(this._el, pos);
3006
3007                 // @event step: Event
3008                 // Fired continuously during the animation.
3009                 this.fire('step');
3010         },
3011
3012         _complete: function () {
3013                 cancelAnimFrame(this._animId);
3014
3015                 this._inProgress = false;
3016                 // @event end: Event
3017                 // Fired when the animation ends.
3018                 this.fire('end');
3019         },
3020
3021         _easeOut: function (t) {
3022                 return 1 - Math.pow(1 - t, this._easeOutPower);
3023         }
3024 });
3025
3026 /*
3027  * @class Map
3028  * @aka L.Map
3029  * @inherits Evented
3030  *
3031  * The central class of the API — it is used to create a map on a page and manipulate it.
3032  *
3033  * @example
3034  *
3035  * ```js
3036  * // initialize the map on the "map" div with a given center and zoom
3037  * var map = L.map('map', {
3038  *      center: [51.505, -0.09],
3039  *      zoom: 13
3040  * });
3041  * ```
3042  *
3043  */
3044
3045 var Map = Evented.extend({
3046
3047         options: {
3048                 // @section Map State Options
3049                 // @option crs: CRS = L.CRS.EPSG3857
3050                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3051                 // sure what it means.
3052                 crs: EPSG3857,
3053
3054                 // @option center: LatLng = undefined
3055                 // Initial geographic center of the map
3056                 center: undefined,
3057
3058                 // @option zoom: Number = undefined
3059                 // Initial map zoom level
3060                 zoom: undefined,
3061
3062                 // @option minZoom: Number = *
3063                 // Minimum zoom level of the map.
3064                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3065                 // the lowest of their `minZoom` options will be used instead.
3066                 minZoom: undefined,
3067
3068                 // @option maxZoom: Number = *
3069                 // Maximum zoom level of the map.
3070                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3071                 // the highest of their `maxZoom` options will be used instead.
3072                 maxZoom: undefined,
3073
3074                 // @option layers: Layer[] = []
3075                 // Array of layers that will be added to the map initially
3076                 layers: [],
3077
3078                 // @option maxBounds: LatLngBounds = null
3079                 // When this option is set, the map restricts the view to the given
3080                 // geographical bounds, bouncing the user back if the user tries to pan
3081                 // outside the view. To set the restriction dynamically, use
3082                 // [`setMaxBounds`](#map-setmaxbounds) method.
3083                 maxBounds: undefined,
3084
3085                 // @option renderer: Renderer = *
3086                 // The default method for drawing vector layers on the map. `L.SVG`
3087                 // or `L.Canvas` by default depending on browser support.
3088                 renderer: undefined,
3089
3090
3091                 // @section Animation Options
3092                 // @option zoomAnimation: Boolean = true
3093                 // Whether the map zoom animation is enabled. By default it's enabled
3094                 // in all browsers that support CSS3 Transitions except Android.
3095                 zoomAnimation: true,
3096
3097                 // @option zoomAnimationThreshold: Number = 4
3098                 // Won't animate zoom if the zoom difference exceeds this value.
3099                 zoomAnimationThreshold: 4,
3100
3101                 // @option fadeAnimation: Boolean = true
3102                 // Whether the tile fade animation is enabled. By default it's enabled
3103                 // in all browsers that support CSS3 Transitions except Android.
3104                 fadeAnimation: true,
3105
3106                 // @option markerZoomAnimation: Boolean = true
3107                 // Whether markers animate their zoom with the zoom animation, if disabled
3108                 // they will disappear for the length of the animation. By default it's
3109                 // enabled in all browsers that support CSS3 Transitions except Android.
3110                 markerZoomAnimation: true,
3111
3112                 // @option transform3DLimit: Number = 2^23
3113                 // Defines the maximum size of a CSS translation transform. The default
3114                 // value should not be changed unless a web browser positions layers in
3115                 // the wrong place after doing a large `panBy`.
3116                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3117
3118                 // @section Interaction Options
3119                 // @option zoomSnap: Number = 1
3120                 // Forces the map's zoom level to always be a multiple of this, particularly
3121                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3122                 // By default, the zoom level snaps to the nearest integer; lower values
3123                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3124                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3125                 zoomSnap: 1,
3126
3127                 // @option zoomDelta: Number = 1
3128                 // Controls how much the map's zoom level will change after a
3129                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3130                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3131                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3132                 zoomDelta: 1,
3133
3134                 // @option trackResize: Boolean = true
3135                 // Whether the map automatically handles browser window resize to update itself.
3136                 trackResize: true
3137         },
3138
3139         initialize: function (id, options) { // (HTMLElement or String, Object)
3140                 options = setOptions(this, options);
3141
3142                 // Make sure to assign internal flags at the beginning,
3143                 // to avoid inconsistent state in some edge cases.
3144                 this._handlers = [];
3145                 this._layers = {};
3146                 this._zoomBoundLayers = {};
3147                 this._sizeChanged = true;
3148
3149                 this._initContainer(id);
3150                 this._initLayout();
3151
3152                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3153                 this._onResize = bind(this._onResize, this);
3154
3155                 this._initEvents();
3156
3157                 if (options.maxBounds) {
3158                         this.setMaxBounds(options.maxBounds);
3159                 }
3160
3161                 if (options.zoom !== undefined) {
3162                         this._zoom = this._limitZoom(options.zoom);
3163                 }
3164
3165                 if (options.center && options.zoom !== undefined) {
3166                         this.setView(toLatLng(options.center), options.zoom, {reset: true});
3167                 }
3168
3169                 this.callInitHooks();
3170
3171                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3172                 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3173                                 this.options.zoomAnimation;
3174
3175                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3176                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3177                 if (this._zoomAnimated) {
3178                         this._createAnimProxy();
3179                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3180                 }
3181
3182                 this._addLayers(this.options.layers);
3183         },
3184
3185
3186         // @section Methods for modifying map state
3187
3188         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3189         // Sets the view of the map (geographical center and zoom) with the given
3190         // animation options.
3191         setView: function (center, zoom, options) {
3192
3193                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3194                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3195                 options = options || {};
3196
3197                 this._stop();
3198
3199                 if (this._loaded && !options.reset && options !== true) {
3200
3201                         if (options.animate !== undefined) {
3202                                 options.zoom = extend({animate: options.animate}, options.zoom);
3203                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3204                         }
3205
3206                         // try animating pan or zoom
3207                         var moved = (this._zoom !== zoom) ?
3208                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3209                                 this._tryAnimatedPan(center, options.pan);
3210
3211                         if (moved) {
3212                                 // prevent resize handler call, the view will refresh after animation anyway
3213                                 clearTimeout(this._sizeTimer);
3214                                 return this;
3215                         }
3216                 }
3217
3218                 // animation didn't start, just reset the map view
3219                 this._resetView(center, zoom);
3220
3221                 return this;
3222         },
3223
3224         // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3225         // Sets the zoom of the map.
3226         setZoom: function (zoom, options) {
3227                 if (!this._loaded) {
3228                         this._zoom = zoom;
3229                         return this;
3230                 }
3231                 return this.setView(this.getCenter(), zoom, {zoom: options});
3232         },
3233
3234         // @method zoomIn(delta?: Number, options?: Zoom options): this
3235         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3236         zoomIn: function (delta, options) {
3237                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3238                 return this.setZoom(this._zoom + delta, options);
3239         },
3240
3241         // @method zoomOut(delta?: Number, options?: Zoom options): this
3242         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3243         zoomOut: function (delta, options) {
3244                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3245                 return this.setZoom(this._zoom - delta, options);
3246         },
3247
3248         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3249         // Zooms the map while keeping a specified geographical point on the map
3250         // stationary (e.g. used internally for scroll zoom and double-click zoom).
3251         // @alternative
3252         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3253         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3254         setZoomAround: function (latlng, zoom, options) {
3255                 var scale = this.getZoomScale(zoom),
3256                     viewHalf = this.getSize().divideBy(2),
3257                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3258
3259                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3260                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3261
3262                 return this.setView(newCenter, zoom, {zoom: options});
3263         },
3264
3265         _getBoundsCenterZoom: function (bounds, options) {
3266
3267                 options = options || {};
3268                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3269
3270                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3271                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3272
3273                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3274
3275                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3276
3277                 if (zoom === Infinity) {
3278                         return {
3279                                 center: bounds.getCenter(),
3280                                 zoom: zoom
3281                         };
3282                 }
3283
3284                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3285
3286                     swPoint = this.project(bounds.getSouthWest(), zoom),
3287                     nePoint = this.project(bounds.getNorthEast(), zoom),
3288                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3289
3290                 return {
3291                         center: center,
3292                         zoom: zoom
3293                 };
3294         },
3295
3296         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3297         // Sets a map view that contains the given geographical bounds with the
3298         // maximum zoom level possible.
3299         fitBounds: function (bounds, options) {
3300
3301                 bounds = toLatLngBounds(bounds);
3302
3303                 if (!bounds.isValid()) {
3304                         throw new Error('Bounds are not valid.');
3305                 }
3306
3307                 var target = this._getBoundsCenterZoom(bounds, options);
3308                 return this.setView(target.center, target.zoom, options);
3309         },
3310
3311         // @method fitWorld(options?: fitBounds options): this
3312         // Sets a map view that mostly contains the whole world with the maximum
3313         // zoom level possible.
3314         fitWorld: function (options) {
3315                 return this.fitBounds([[-90, -180], [90, 180]], options);
3316         },
3317
3318         // @method panTo(latlng: LatLng, options?: Pan options): this
3319         // Pans the map to a given center.
3320         panTo: function (center, options) { // (LatLng)
3321                 return this.setView(center, this._zoom, {pan: options});
3322         },
3323
3324         // @method panBy(offset: Point, options?: Pan options): this
3325         // Pans the map by a given number of pixels (animated).
3326         panBy: function (offset, options) {
3327                 offset = toPoint(offset).round();
3328                 options = options || {};
3329
3330                 if (!offset.x && !offset.y) {
3331                         return this.fire('moveend');
3332                 }
3333                 // If we pan too far, Chrome gets issues with tiles
3334                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3335                 if (options.animate !== true && !this.getSize().contains(offset)) {
3336                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3337                         return this;
3338                 }
3339
3340                 if (!this._panAnim) {
3341                         this._panAnim = new PosAnimation();
3342
3343                         this._panAnim.on({
3344                                 'step': this._onPanTransitionStep,
3345                                 'end': this._onPanTransitionEnd
3346                         }, this);
3347                 }
3348
3349                 // don't fire movestart if animating inertia
3350                 if (!options.noMoveStart) {
3351                         this.fire('movestart');
3352                 }
3353
3354                 // animate pan unless animate: false specified
3355                 if (options.animate !== false) {
3356                         addClass(this._mapPane, 'leaflet-pan-anim');
3357
3358                         var newPos = this._getMapPanePos().subtract(offset).round();
3359                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3360                 } else {
3361                         this._rawPanBy(offset);
3362                         this.fire('move').fire('moveend');
3363                 }
3364
3365                 return this;
3366         },
3367
3368         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3369         // Sets the view of the map (geographical center and zoom) performing a smooth
3370         // pan-zoom animation.
3371         flyTo: function (targetCenter, targetZoom, options) {
3372
3373                 options = options || {};
3374                 if (options.animate === false || !any3d) {
3375                         return this.setView(targetCenter, targetZoom, options);
3376                 }
3377
3378                 this._stop();
3379
3380                 var from = this.project(this.getCenter()),
3381                     to = this.project(targetCenter),
3382                     size = this.getSize(),
3383                     startZoom = this._zoom;
3384
3385                 targetCenter = toLatLng(targetCenter);
3386                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3387
3388                 var w0 = Math.max(size.x, size.y),
3389                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3390                     u1 = (to.distanceTo(from)) || 1,
3391                     rho = 1.42,
3392                     rho2 = rho * rho;
3393
3394                 function r(i) {
3395                         var s1 = i ? -1 : 1,
3396                             s2 = i ? w1 : w0,
3397                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3398                             b1 = 2 * s2 * rho2 * u1,
3399                             b = t1 / b1,
3400                             sq = Math.sqrt(b * b + 1) - b;
3401
3402                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
3403                             // thus triggering an infinite loop in flyTo
3404                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
3405
3406                         return log;
3407                 }
3408
3409                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3410                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3411                 function tanh(n) { return sinh(n) / cosh(n); }
3412
3413                 var r0 = r(0);
3414
3415                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3416                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3417
3418                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3419
3420                 var start = Date.now(),
3421                     S = (r(1) - r0) / rho,
3422                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3423
3424                 function frame() {
3425                         var t = (Date.now() - start) / duration,
3426                             s = easeOut(t) * S;
3427
3428                         if (t <= 1) {
3429                                 this._flyToFrame = requestAnimFrame(frame, this);
3430
3431                                 this._move(
3432                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3433                                         this.getScaleZoom(w0 / w(s), startZoom),
3434                                         {flyTo: true});
3435
3436                         } else {
3437                                 this
3438                                         ._move(targetCenter, targetZoom)
3439                                         ._moveEnd(true);
3440                         }
3441                 }
3442
3443                 this._moveStart(true, options.noMoveStart);
3444
3445                 frame.call(this);
3446                 return this;
3447         },
3448
3449         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3450         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3451         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3452         flyToBounds: function (bounds, options) {
3453                 var target = this._getBoundsCenterZoom(bounds, options);
3454                 return this.flyTo(target.center, target.zoom, options);
3455         },
3456
3457         // @method setMaxBounds(bounds: Bounds): this
3458         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3459         setMaxBounds: function (bounds) {
3460                 bounds = toLatLngBounds(bounds);
3461
3462                 if (!bounds.isValid()) {
3463                         this.options.maxBounds = null;
3464                         return this.off('moveend', this._panInsideMaxBounds);
3465                 } else if (this.options.maxBounds) {
3466                         this.off('moveend', this._panInsideMaxBounds);
3467                 }
3468
3469                 this.options.maxBounds = bounds;
3470
3471                 if (this._loaded) {
3472                         this._panInsideMaxBounds();
3473                 }
3474
3475                 return this.on('moveend', this._panInsideMaxBounds);
3476         },
3477
3478         // @method setMinZoom(zoom: Number): this
3479         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3480         setMinZoom: function (zoom) {
3481                 var oldZoom = this.options.minZoom;
3482                 this.options.minZoom = zoom;
3483
3484                 if (this._loaded && oldZoom !== zoom) {
3485                         this.fire('zoomlevelschange');
3486
3487                         if (this.getZoom() < this.options.minZoom) {
3488                                 return this.setZoom(zoom);
3489                         }
3490                 }
3491
3492                 return this;
3493         },
3494
3495         // @method setMaxZoom(zoom: Number): this
3496         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3497         setMaxZoom: function (zoom) {
3498                 var oldZoom = this.options.maxZoom;
3499                 this.options.maxZoom = zoom;
3500
3501                 if (this._loaded && oldZoom !== zoom) {
3502                         this.fire('zoomlevelschange');
3503
3504                         if (this.getZoom() > this.options.maxZoom) {
3505                                 return this.setZoom(zoom);
3506                         }
3507                 }
3508
3509                 return this;
3510         },
3511
3512         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3513         // 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.
3514         panInsideBounds: function (bounds, options) {
3515                 this._enforcingBounds = true;
3516                 var center = this.getCenter(),
3517                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3518
3519                 if (!center.equals(newCenter)) {
3520                         this.panTo(newCenter, options);
3521                 }
3522
3523                 this._enforcingBounds = false;
3524                 return this;
3525         },
3526
3527         // @method panInside(latlng: LatLng, options?: options): this
3528         // Pans the map the minimum amount to make the `latlng` visible. Use
3529         // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3530         // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3531         // If `latlng` is already within the (optionally padded) display bounds,
3532         // the map will not be panned.
3533         panInside: function (latlng, options) {
3534                 options = options || {};
3535
3536                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3537                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3538                     center = this.getCenter(),
3539                     pixelCenter = this.project(center),
3540                     pixelPoint = this.project(latlng),
3541                     pixelBounds = this.getPixelBounds(),
3542                     halfPixelBounds = pixelBounds.getSize().divideBy(2),
3543                     paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3544
3545                 if (!paddedBounds.contains(pixelPoint)) {
3546                         this._enforcingBounds = true;
3547                         var diff = pixelCenter.subtract(pixelPoint),
3548                             newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3549
3550                         if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3551                                 newCenter.x = pixelCenter.x - diff.x;
3552                                 if (diff.x > 0) {
3553                                         newCenter.x += halfPixelBounds.x - paddingTL.x;
3554                                 } else {
3555                                         newCenter.x -= halfPixelBounds.x - paddingBR.x;
3556                                 }
3557                         }
3558                         if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3559                                 newCenter.y = pixelCenter.y - diff.y;
3560                                 if (diff.y > 0) {
3561                                         newCenter.y += halfPixelBounds.y - paddingTL.y;
3562                                 } else {
3563                                         newCenter.y -= halfPixelBounds.y - paddingBR.y;
3564                                 }
3565                         }
3566                         this.panTo(this.unproject(newCenter), options);
3567                         this._enforcingBounds = false;
3568                 }
3569                 return this;
3570         },
3571
3572         // @method invalidateSize(options: Zoom/pan options): this
3573         // Checks if the map container size changed and updates the map if so —
3574         // call it after you've changed the map size dynamically, also animating
3575         // pan by default. If `options.pan` is `false`, panning will not occur.
3576         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3577         // that it doesn't happen often even if the method is called many
3578         // times in a row.
3579
3580         // @alternative
3581         // @method invalidateSize(animate: Boolean): this
3582         // Checks if the map container size changed and updates the map if so —
3583         // call it after you've changed the map size dynamically, also animating
3584         // pan by default.
3585         invalidateSize: function (options) {
3586                 if (!this._loaded) { return this; }
3587
3588                 options = extend({
3589                         animate: false,
3590                         pan: true
3591                 }, options === true ? {animate: true} : options);
3592
3593                 var oldSize = this.getSize();
3594                 this._sizeChanged = true;
3595                 this._lastCenter = null;
3596
3597                 var newSize = this.getSize(),
3598                     oldCenter = oldSize.divideBy(2).round(),
3599                     newCenter = newSize.divideBy(2).round(),
3600                     offset = oldCenter.subtract(newCenter);
3601
3602                 if (!offset.x && !offset.y) { return this; }
3603
3604                 if (options.animate && options.pan) {
3605                         this.panBy(offset);
3606
3607                 } else {
3608                         if (options.pan) {
3609                                 this._rawPanBy(offset);
3610                         }
3611
3612                         this.fire('move');
3613
3614                         if (options.debounceMoveend) {
3615                                 clearTimeout(this._sizeTimer);
3616                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3617                         } else {
3618                                 this.fire('moveend');
3619                         }
3620                 }
3621
3622                 // @section Map state change events
3623                 // @event resize: ResizeEvent
3624                 // Fired when the map is resized.
3625                 return this.fire('resize', {
3626                         oldSize: oldSize,
3627                         newSize: newSize
3628                 });
3629         },
3630
3631         // @section Methods for modifying map state
3632         // @method stop(): this
3633         // Stops the currently running `panTo` or `flyTo` animation, if any.
3634         stop: function () {
3635                 this.setZoom(this._limitZoom(this._zoom));
3636                 if (!this.options.zoomSnap) {
3637                         this.fire('viewreset');
3638                 }
3639                 return this._stop();
3640         },
3641
3642         // @section Geolocation methods
3643         // @method locate(options?: Locate options): this
3644         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3645         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3646         // and optionally sets the map view to the user's location with respect to
3647         // detection accuracy (or to the world view if geolocation failed).
3648         // Note that, if your page doesn't use HTTPS, this method will fail in
3649         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3650         // See `Locate options` for more details.
3651         locate: function (options) {
3652
3653                 options = this._locateOptions = extend({
3654                         timeout: 10000,
3655                         watch: false
3656                         // setView: false
3657                         // maxZoom: <Number>
3658                         // maximumAge: 0
3659                         // enableHighAccuracy: false
3660                 }, options);
3661
3662                 if (!('geolocation' in navigator)) {
3663                         this._handleGeolocationError({
3664                                 code: 0,
3665                                 message: 'Geolocation not supported.'
3666                         });
3667                         return this;
3668                 }
3669
3670                 var onResponse = bind(this._handleGeolocationResponse, this),
3671                     onError = bind(this._handleGeolocationError, this);
3672
3673                 if (options.watch) {
3674                         this._locationWatchId =
3675                                 navigator.geolocation.watchPosition(onResponse, onError, options);
3676                 } else {
3677                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3678                 }
3679                 return this;
3680         },
3681
3682         // @method stopLocate(): this
3683         // Stops watching location previously initiated by `map.locate({watch: true})`
3684         // and aborts resetting the map view if map.locate was called with
3685         // `{setView: true}`.
3686         stopLocate: function () {
3687                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3688                         navigator.geolocation.clearWatch(this._locationWatchId);
3689                 }
3690                 if (this._locateOptions) {
3691                         this._locateOptions.setView = false;
3692                 }
3693                 return this;
3694         },
3695
3696         _handleGeolocationError: function (error) {
3697                 var c = error.code,
3698                     message = error.message ||
3699                             (c === 1 ? 'permission denied' :
3700                             (c === 2 ? 'position unavailable' : 'timeout'));
3701
3702                 if (this._locateOptions.setView && !this._loaded) {
3703                         this.fitWorld();
3704                 }
3705
3706                 // @section Location events
3707                 // @event locationerror: ErrorEvent
3708                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3709                 this.fire('locationerror', {
3710                         code: c,
3711                         message: 'Geolocation error: ' + message + '.'
3712                 });
3713         },
3714
3715         _handleGeolocationResponse: function (pos) {
3716                 var lat = pos.coords.latitude,
3717                     lng = pos.coords.longitude,
3718                     latlng = new LatLng(lat, lng),
3719                     bounds = latlng.toBounds(pos.coords.accuracy * 2),
3720                     options = this._locateOptions;
3721
3722                 if (options.setView) {
3723                         var zoom = this.getBoundsZoom(bounds);
3724                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3725                 }
3726
3727                 var data = {
3728                         latlng: latlng,
3729                         bounds: bounds,
3730                         timestamp: pos.timestamp
3731                 };
3732
3733                 for (var i in pos.coords) {
3734                         if (typeof pos.coords[i] === 'number') {
3735                                 data[i] = pos.coords[i];
3736                         }
3737                 }
3738
3739                 // @event locationfound: LocationEvent
3740                 // Fired when geolocation (using the [`locate`](#map-locate) method)
3741                 // went successfully.
3742                 this.fire('locationfound', data);
3743         },
3744
3745         // TODO Appropriate docs section?
3746         // @section Other Methods
3747         // @method addHandler(name: String, HandlerClass: Function): this
3748         // Adds a new `Handler` to the map, given its name and constructor function.
3749         addHandler: function (name, HandlerClass) {
3750                 if (!HandlerClass) { return this; }
3751
3752                 var handler = this[name] = new HandlerClass(this);
3753
3754                 this._handlers.push(handler);
3755
3756                 if (this.options[name]) {
3757                         handler.enable();
3758                 }
3759
3760                 return this;
3761         },
3762
3763         // @method remove(): this
3764         // Destroys the map and clears all related event listeners.
3765         remove: function () {
3766
3767                 this._initEvents(true);
3768
3769                 if (this._containerId !== this._container._leaflet_id) {
3770                         throw new Error('Map container is being reused by another instance');
3771                 }
3772
3773                 try {
3774                         // throws error in IE6-8
3775                         delete this._container._leaflet_id;
3776                         delete this._containerId;
3777                 } catch (e) {
3778                         /*eslint-disable */
3779                         this._container._leaflet_id = undefined;
3780                         /* eslint-enable */
3781                         this._containerId = undefined;
3782                 }
3783
3784                 if (this._locationWatchId !== undefined) {
3785                         this.stopLocate();
3786                 }
3787
3788                 this._stop();
3789
3790                 remove(this._mapPane);
3791
3792                 if (this._clearControlPos) {
3793                         this._clearControlPos();
3794                 }
3795                 if (this._resizeRequest) {
3796                         cancelAnimFrame(this._resizeRequest);
3797                         this._resizeRequest = null;
3798                 }
3799
3800                 this._clearHandlers();
3801
3802                 if (this._loaded) {
3803                         // @section Map state change events
3804                         // @event unload: Event
3805                         // Fired when the map is destroyed with [remove](#map-remove) method.
3806                         this.fire('unload');
3807                 }
3808
3809                 var i;
3810                 for (i in this._layers) {
3811                         this._layers[i].remove();
3812                 }
3813                 for (i in this._panes) {
3814                         remove(this._panes[i]);
3815                 }
3816
3817                 this._layers = [];
3818                 this._panes = [];
3819                 delete this._mapPane;
3820                 delete this._renderer;
3821
3822                 return this;
3823         },
3824
3825         // @section Other Methods
3826         // @method createPane(name: String, container?: HTMLElement): HTMLElement
3827         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3828         // then returns it. The pane is created as a child of `container`, or
3829         // as a child of the main map pane if not set.
3830         createPane: function (name, container) {
3831                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3832                     pane = create$1('div', className, container || this._mapPane);
3833
3834                 if (name) {
3835                         this._panes[name] = pane;
3836                 }
3837                 return pane;
3838         },
3839
3840         // @section Methods for Getting Map State
3841
3842         // @method getCenter(): LatLng
3843         // Returns the geographical center of the map view
3844         getCenter: function () {
3845                 this._checkIfLoaded();
3846
3847                 if (this._lastCenter && !this._moved()) {
3848                         return this._lastCenter;
3849                 }
3850                 return this.layerPointToLatLng(this._getCenterLayerPoint());
3851         },
3852
3853         // @method getZoom(): Number
3854         // Returns the current zoom level of the map view
3855         getZoom: function () {
3856                 return this._zoom;
3857         },
3858
3859         // @method getBounds(): LatLngBounds
3860         // Returns the geographical bounds visible in the current map view
3861         getBounds: function () {
3862                 var bounds = this.getPixelBounds(),
3863                     sw = this.unproject(bounds.getBottomLeft()),
3864                     ne = this.unproject(bounds.getTopRight());
3865
3866                 return new LatLngBounds(sw, ne);
3867         },
3868
3869         // @method getMinZoom(): Number
3870         // 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.
3871         getMinZoom: function () {
3872                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3873         },
3874
3875         // @method getMaxZoom(): Number
3876         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3877         getMaxZoom: function () {
3878                 return this.options.maxZoom === undefined ?
3879                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3880                         this.options.maxZoom;
3881         },
3882
3883         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3884         // Returns the maximum zoom level on which the given bounds fit to the map
3885         // view in its entirety. If `inside` (optional) is set to `true`, the method
3886         // instead returns the minimum zoom level on which the map view fits into
3887         // the given bounds in its entirety.
3888         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3889                 bounds = toLatLngBounds(bounds);
3890                 padding = toPoint(padding || [0, 0]);
3891
3892                 var zoom = this.getZoom() || 0,
3893                     min = this.getMinZoom(),
3894                     max = this.getMaxZoom(),
3895                     nw = bounds.getNorthWest(),
3896                     se = bounds.getSouthEast(),
3897                     size = this.getSize().subtract(padding),
3898                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3899                     snap = any3d ? this.options.zoomSnap : 1,
3900                     scalex = size.x / boundsSize.x,
3901                     scaley = size.y / boundsSize.y,
3902                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3903
3904                 zoom = this.getScaleZoom(scale, zoom);
3905
3906                 if (snap) {
3907                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3908                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3909                 }
3910
3911                 return Math.max(min, Math.min(max, zoom));
3912         },
3913
3914         // @method getSize(): Point
3915         // Returns the current size of the map container (in pixels).
3916         getSize: function () {
3917                 if (!this._size || this._sizeChanged) {
3918                         this._size = new Point(
3919                                 this._container.clientWidth || 0,
3920                                 this._container.clientHeight || 0);
3921
3922                         this._sizeChanged = false;
3923                 }
3924                 return this._size.clone();
3925         },
3926
3927         // @method getPixelBounds(): Bounds
3928         // Returns the bounds of the current map view in projected pixel
3929         // coordinates (sometimes useful in layer and overlay implementations).
3930         getPixelBounds: function (center, zoom) {
3931                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3932                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3933         },
3934
3935         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3936         // the map pane? "left point of the map layer" can be confusing, specially
3937         // since there can be negative offsets.
3938         // @method getPixelOrigin(): Point
3939         // Returns the projected pixel coordinates of the top left point of
3940         // the map layer (useful in custom layer and overlay implementations).
3941         getPixelOrigin: function () {
3942                 this._checkIfLoaded();
3943                 return this._pixelOrigin;
3944         },
3945
3946         // @method getPixelWorldBounds(zoom?: Number): Bounds
3947         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3948         // If `zoom` is omitted, the map's current zoom level is used.
3949         getPixelWorldBounds: function (zoom) {
3950                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3951         },
3952
3953         // @section Other Methods
3954
3955         // @method getPane(pane: String|HTMLElement): HTMLElement
3956         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3957         getPane: function (pane) {
3958                 return typeof pane === 'string' ? this._panes[pane] : pane;
3959         },
3960
3961         // @method getPanes(): Object
3962         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3963         // the panes as values.
3964         getPanes: function () {
3965                 return this._panes;
3966         },
3967
3968         // @method getContainer: HTMLElement
3969         // Returns the HTML element that contains the map.
3970         getContainer: function () {
3971                 return this._container;
3972         },
3973
3974
3975         // @section Conversion Methods
3976
3977         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3978         // Returns the scale factor to be applied to a map transition from zoom level
3979         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3980         getZoomScale: function (toZoom, fromZoom) {
3981                 // TODO replace with universal implementation after refactoring projections
3982                 var crs = this.options.crs;
3983                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3984                 return crs.scale(toZoom) / crs.scale(fromZoom);
3985         },
3986
3987         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3988         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3989         // level and everything is scaled by a factor of `scale`. Inverse of
3990         // [`getZoomScale`](#map-getZoomScale).
3991         getScaleZoom: function (scale, fromZoom) {
3992                 var crs = this.options.crs;
3993                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3994                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3995                 return isNaN(zoom) ? Infinity : zoom;
3996         },
3997
3998         // @method project(latlng: LatLng, zoom: Number): Point
3999         // Projects a geographical coordinate `LatLng` according to the projection
4000         // of the map's CRS, then scales it according to `zoom` and the CRS's
4001         // `Transformation`. The result is pixel coordinate relative to
4002         // the CRS origin.
4003         project: function (latlng, zoom) {
4004                 zoom = zoom === undefined ? this._zoom : zoom;
4005                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
4006         },
4007
4008         // @method unproject(point: Point, zoom: Number): LatLng
4009         // Inverse of [`project`](#map-project).
4010         unproject: function (point, zoom) {
4011                 zoom = zoom === undefined ? this._zoom : zoom;
4012                 return this.options.crs.pointToLatLng(toPoint(point), zoom);
4013         },
4014
4015         // @method layerPointToLatLng(point: Point): LatLng
4016         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4017         // returns the corresponding geographical coordinate (for the current zoom level).
4018         layerPointToLatLng: function (point) {
4019                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4020                 return this.unproject(projectedPoint);
4021         },
4022
4023         // @method latLngToLayerPoint(latlng: LatLng): Point
4024         // Given a geographical coordinate, returns the corresponding pixel coordinate
4025         // relative to the [origin pixel](#map-getpixelorigin).
4026         latLngToLayerPoint: function (latlng) {
4027                 var projectedPoint = this.project(toLatLng(latlng))._round();
4028                 return projectedPoint._subtract(this.getPixelOrigin());
4029         },
4030
4031         // @method wrapLatLng(latlng: LatLng): LatLng
4032         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4033         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4034         // CRS's bounds.
4035         // By default this means longitude is wrapped around the dateline so its
4036         // value is between -180 and +180 degrees.
4037         wrapLatLng: function (latlng) {
4038                 return this.options.crs.wrapLatLng(toLatLng(latlng));
4039         },
4040
4041         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4042         // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4043         // its center is within the CRS's bounds.
4044         // By default this means the center longitude is wrapped around the dateline so its
4045         // value is between -180 and +180 degrees, and the majority of the bounds
4046         // overlaps the CRS's bounds.
4047         wrapLatLngBounds: function (latlng) {
4048                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4049         },
4050
4051         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4052         // Returns the distance between two geographical coordinates according to
4053         // the map's CRS. By default this measures distance in meters.
4054         distance: function (latlng1, latlng2) {
4055                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4056         },
4057
4058         // @method containerPointToLayerPoint(point: Point): Point
4059         // Given a pixel coordinate relative to the map container, returns the corresponding
4060         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4061         containerPointToLayerPoint: function (point) { // (Point)
4062                 return toPoint(point).subtract(this._getMapPanePos());
4063         },
4064
4065         // @method layerPointToContainerPoint(point: Point): Point
4066         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4067         // returns the corresponding pixel coordinate relative to the map container.
4068         layerPointToContainerPoint: function (point) { // (Point)
4069                 return toPoint(point).add(this._getMapPanePos());
4070         },
4071
4072         // @method containerPointToLatLng(point: Point): LatLng
4073         // Given a pixel coordinate relative to the map container, returns
4074         // the corresponding geographical coordinate (for the current zoom level).
4075         containerPointToLatLng: function (point) {
4076                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4077                 return this.layerPointToLatLng(layerPoint);
4078         },
4079
4080         // @method latLngToContainerPoint(latlng: LatLng): Point
4081         // Given a geographical coordinate, returns the corresponding pixel coordinate
4082         // relative to the map container.
4083         latLngToContainerPoint: function (latlng) {
4084                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4085         },
4086
4087         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4088         // Given a MouseEvent object, returns the pixel coordinate relative to the
4089         // map container where the event took place.
4090         mouseEventToContainerPoint: function (e) {
4091                 return getMousePosition(e, this._container);
4092         },
4093
4094         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4095         // Given a MouseEvent object, returns the pixel coordinate relative to
4096         // the [origin pixel](#map-getpixelorigin) where the event took place.
4097         mouseEventToLayerPoint: function (e) {
4098                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4099         },
4100
4101         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4102         // Given a MouseEvent object, returns geographical coordinate where the
4103         // event took place.
4104         mouseEventToLatLng: function (e) { // (MouseEvent)
4105                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4106         },
4107
4108
4109         // map initialization methods
4110
4111         _initContainer: function (id) {
4112                 var container = this._container = get(id);
4113
4114                 if (!container) {
4115                         throw new Error('Map container not found.');
4116                 } else if (container._leaflet_id) {
4117                         throw new Error('Map container is already initialized.');
4118                 }
4119
4120                 on(container, 'scroll', this._onScroll, this);
4121                 this._containerId = stamp(container);
4122         },
4123
4124         _initLayout: function () {
4125                 var container = this._container;
4126
4127                 this._fadeAnimated = this.options.fadeAnimation && any3d;
4128
4129                 addClass(container, 'leaflet-container' +
4130                         (touch ? ' leaflet-touch' : '') +
4131                         (retina ? ' leaflet-retina' : '') +
4132                         (ielt9 ? ' leaflet-oldie' : '') +
4133                         (safari ? ' leaflet-safari' : '') +
4134                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4135
4136                 var position = getStyle(container, 'position');
4137
4138                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4139                         container.style.position = 'relative';
4140                 }
4141
4142                 this._initPanes();
4143
4144                 if (this._initControlPos) {
4145                         this._initControlPos();
4146                 }
4147         },
4148
4149         _initPanes: function () {
4150                 var panes = this._panes = {};
4151                 this._paneRenderers = {};
4152
4153                 // @section
4154                 //
4155                 // Panes are DOM elements used to control the ordering of layers on the map. You
4156                 // can access panes with [`map.getPane`](#map-getpane) or
4157                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4158                 // [`map.createPane`](#map-createpane) method.
4159                 //
4160                 // Every map has the following default panes that differ only in zIndex.
4161                 //
4162                 // @pane mapPane: HTMLElement = 'auto'
4163                 // Pane that contains all other map panes
4164
4165                 this._mapPane = this.createPane('mapPane', this._container);
4166                 setPosition(this._mapPane, new Point(0, 0));
4167
4168                 // @pane tilePane: HTMLElement = 200
4169                 // Pane for `GridLayer`s and `TileLayer`s
4170                 this.createPane('tilePane');
4171                 // @pane overlayPane: HTMLElement = 400
4172                 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4173                 this.createPane('shadowPane');
4174                 // @pane shadowPane: HTMLElement = 500
4175                 // Pane for overlay shadows (e.g. `Marker` shadows)
4176                 this.createPane('overlayPane');
4177                 // @pane markerPane: HTMLElement = 600
4178                 // Pane for `Icon`s of `Marker`s
4179                 this.createPane('markerPane');
4180                 // @pane tooltipPane: HTMLElement = 650
4181                 // Pane for `Tooltip`s.
4182                 this.createPane('tooltipPane');
4183                 // @pane popupPane: HTMLElement = 700
4184                 // Pane for `Popup`s.
4185                 this.createPane('popupPane');
4186
4187                 if (!this.options.markerZoomAnimation) {
4188                         addClass(panes.markerPane, 'leaflet-zoom-hide');
4189                         addClass(panes.shadowPane, 'leaflet-zoom-hide');
4190                 }
4191         },
4192
4193
4194         // private methods that modify map state
4195
4196         // @section Map state change events
4197         _resetView: function (center, zoom) {
4198                 setPosition(this._mapPane, new Point(0, 0));
4199
4200                 var loading = !this._loaded;
4201                 this._loaded = true;
4202                 zoom = this._limitZoom(zoom);
4203
4204                 this.fire('viewprereset');
4205
4206                 var zoomChanged = this._zoom !== zoom;
4207                 this
4208                         ._moveStart(zoomChanged, false)
4209                         ._move(center, zoom)
4210                         ._moveEnd(zoomChanged);
4211
4212                 // @event viewreset: Event
4213                 // Fired when the map needs to redraw its content (this usually happens
4214                 // on map zoom or load). Very useful for creating custom overlays.
4215                 this.fire('viewreset');
4216
4217                 // @event load: Event
4218                 // Fired when the map is initialized (when its center and zoom are set
4219                 // for the first time).
4220                 if (loading) {
4221                         this.fire('load');
4222                 }
4223         },
4224
4225         _moveStart: function (zoomChanged, noMoveStart) {
4226                 // @event zoomstart: Event
4227                 // Fired when the map zoom is about to change (e.g. before zoom animation).
4228                 // @event movestart: Event
4229                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4230                 if (zoomChanged) {
4231                         this.fire('zoomstart');
4232                 }
4233                 if (!noMoveStart) {
4234                         this.fire('movestart');
4235                 }
4236                 return this;
4237         },
4238
4239         _move: function (center, zoom, data) {
4240                 if (zoom === undefined) {
4241                         zoom = this._zoom;
4242                 }
4243                 var zoomChanged = this._zoom !== zoom;
4244
4245                 this._zoom = zoom;
4246                 this._lastCenter = center;
4247                 this._pixelOrigin = this._getNewPixelOrigin(center);
4248
4249                 // @event zoom: Event
4250                 // Fired repeatedly during any change in zoom level, including zoom
4251                 // and fly animations.
4252                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
4253                         this.fire('zoom', data);
4254                 }
4255
4256                 // @event move: Event
4257                 // Fired repeatedly during any movement of the map, including pan and
4258                 // fly animations.
4259                 return this.fire('move', data);
4260         },
4261
4262         _moveEnd: function (zoomChanged) {
4263                 // @event zoomend: Event
4264                 // Fired when the map has changed, after any animations.
4265                 if (zoomChanged) {
4266                         this.fire('zoomend');
4267                 }
4268
4269                 // @event moveend: Event
4270                 // Fired when the center of the map stops changing (e.g. user stopped
4271                 // dragging the map).
4272                 return this.fire('moveend');
4273         },
4274
4275         _stop: function () {
4276                 cancelAnimFrame(this._flyToFrame);
4277                 if (this._panAnim) {
4278                         this._panAnim.stop();
4279                 }
4280                 return this;
4281         },
4282
4283         _rawPanBy: function (offset) {
4284                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4285         },
4286
4287         _getZoomSpan: function () {
4288                 return this.getMaxZoom() - this.getMinZoom();
4289         },
4290
4291         _panInsideMaxBounds: function () {
4292                 if (!this._enforcingBounds) {
4293                         this.panInsideBounds(this.options.maxBounds);
4294                 }
4295         },
4296
4297         _checkIfLoaded: function () {
4298                 if (!this._loaded) {
4299                         throw new Error('Set map center and zoom first.');
4300                 }
4301         },
4302
4303         // DOM event handling
4304
4305         // @section Interaction events
4306         _initEvents: function (remove$$1) {
4307                 this._targets = {};
4308                 this._targets[stamp(this._container)] = this;
4309
4310                 var onOff = remove$$1 ? off : on;
4311
4312                 // @event click: MouseEvent
4313                 // Fired when the user clicks (or taps) the map.
4314                 // @event dblclick: MouseEvent
4315                 // Fired when the user double-clicks (or double-taps) the map.
4316                 // @event mousedown: MouseEvent
4317                 // Fired when the user pushes the mouse button on the map.
4318                 // @event mouseup: MouseEvent
4319                 // Fired when the user releases the mouse button on the map.
4320                 // @event mouseover: MouseEvent
4321                 // Fired when the mouse enters the map.
4322                 // @event mouseout: MouseEvent
4323                 // Fired when the mouse leaves the map.
4324                 // @event mousemove: MouseEvent
4325                 // Fired while the mouse moves over the map.
4326                 // @event contextmenu: MouseEvent
4327                 // Fired when the user pushes the right mouse button on the map, prevents
4328                 // default browser context menu from showing if there are listeners on
4329                 // this event. Also fired on mobile when the user holds a single touch
4330                 // for a second (also called long press).
4331                 // @event keypress: KeyboardEvent
4332                 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4333                 // @event keydown: KeyboardEvent
4334                 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4335                 // the `keydown` event is fired for keys that produce a character value and for keys
4336                 // that do not produce a character value.
4337                 // @event keyup: KeyboardEvent
4338                 // Fired when the user releases a key from the keyboard while the map is focused.
4339                 onOff(this._container, 'click dblclick mousedown mouseup ' +
4340                         'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4341
4342                 if (this.options.trackResize) {
4343                         onOff(window, 'resize', this._onResize, this);
4344                 }
4345
4346                 if (any3d && this.options.transform3DLimit) {
4347                         (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4348                 }
4349         },
4350
4351         _onResize: function () {
4352                 cancelAnimFrame(this._resizeRequest);
4353                 this._resizeRequest = requestAnimFrame(
4354                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
4355         },
4356
4357         _onScroll: function () {
4358                 this._container.scrollTop  = 0;
4359                 this._container.scrollLeft = 0;
4360         },
4361
4362         _onMoveEnd: function () {
4363                 var pos = this._getMapPanePos();
4364                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4365                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4366                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4367                         this._resetView(this.getCenter(), this.getZoom());
4368                 }
4369         },
4370
4371         _findEventTargets: function (e, type) {
4372                 var targets = [],
4373                     target,
4374                     isHover = type === 'mouseout' || type === 'mouseover',
4375                     src = e.target || e.srcElement,
4376                     dragging = false;
4377
4378                 while (src) {
4379                         target = this._targets[stamp(src)];
4380                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4381                                 // Prevent firing click after you just dragged an object.
4382                                 dragging = true;
4383                                 break;
4384                         }
4385                         if (target && target.listens(type, true)) {
4386                                 if (isHover && !isExternalTarget(src, e)) { break; }
4387                                 targets.push(target);
4388                                 if (isHover) { break; }
4389                         }
4390                         if (src === this._container) { break; }
4391                         src = src.parentNode;
4392                 }
4393                 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4394                         targets = [this];
4395                 }
4396                 return targets;
4397         },
4398
4399         _handleDOMEvent: function (e) {
4400                 if (!this._loaded || skipped(e)) { return; }
4401
4402                 var type = e.type;
4403
4404                 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4405                         // prevents outline when clicking on keyboard-focusable element
4406                         preventOutline(e.target || e.srcElement);
4407                 }
4408
4409                 this._fireDOMEvent(e, type);
4410         },
4411
4412         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4413
4414         _fireDOMEvent: function (e, type, targets) {
4415
4416                 if (e.type === 'click') {
4417                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4418                         // @event preclick: MouseEvent
4419                         // Fired before mouse click on the map (sometimes useful when you
4420                         // want something to happen on click before any existing click
4421                         // handlers start running).
4422                         var synth = extend({}, e);
4423                         synth.type = 'preclick';
4424                         this._fireDOMEvent(synth, synth.type, targets);
4425                 }
4426
4427                 if (e._stopped) { return; }
4428
4429                 // Find the layer the event is propagating from and its parents.
4430                 targets = (targets || []).concat(this._findEventTargets(e, type));
4431
4432                 if (!targets.length) { return; }
4433
4434                 var target = targets[0];
4435                 if (type === 'contextmenu' && target.listens(type, true)) {
4436                         preventDefault(e);
4437                 }
4438
4439                 var data = {
4440                         originalEvent: e
4441                 };
4442
4443                 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4444                         var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4445                         data.containerPoint = isMarker ?
4446                                 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4447                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4448                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4449                 }
4450
4451                 for (var i = 0; i < targets.length; i++) {
4452                         targets[i].fire(type, data, true);
4453                         if (data.originalEvent._stopped ||
4454                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4455                 }
4456         },
4457
4458         _draggableMoved: function (obj) {
4459                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4460                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4461         },
4462
4463         _clearHandlers: function () {
4464                 for (var i = 0, len = this._handlers.length; i < len; i++) {
4465                         this._handlers[i].disable();
4466                 }
4467         },
4468
4469         // @section Other Methods
4470
4471         // @method whenReady(fn: Function, context?: Object): this
4472         // Runs the given function `fn` when the map gets initialized with
4473         // a view (center and zoom) and at least one layer, or immediately
4474         // if it's already initialized, optionally passing a function context.
4475         whenReady: function (callback, context) {
4476                 if (this._loaded) {
4477                         callback.call(context || this, {target: this});
4478                 } else {
4479                         this.on('load', callback, context);
4480                 }
4481                 return this;
4482         },
4483
4484
4485         // private methods for getting map state
4486
4487         _getMapPanePos: function () {
4488                 return getPosition(this._mapPane) || new Point(0, 0);
4489         },
4490
4491         _moved: function () {
4492                 var pos = this._getMapPanePos();
4493                 return pos && !pos.equals([0, 0]);
4494         },
4495
4496         _getTopLeftPoint: function (center, zoom) {
4497                 var pixelOrigin = center && zoom !== undefined ?
4498                         this._getNewPixelOrigin(center, zoom) :
4499                         this.getPixelOrigin();
4500                 return pixelOrigin.subtract(this._getMapPanePos());
4501         },
4502
4503         _getNewPixelOrigin: function (center, zoom) {
4504                 var viewHalf = this.getSize()._divideBy(2);
4505                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4506         },
4507
4508         _latLngToNewLayerPoint: function (latlng, zoom, center) {
4509                 var topLeft = this._getNewPixelOrigin(center, zoom);
4510                 return this.project(latlng, zoom)._subtract(topLeft);
4511         },
4512
4513         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4514                 var topLeft = this._getNewPixelOrigin(center, zoom);
4515                 return toBounds([
4516                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4517                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4518                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4519                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4520                 ]);
4521         },
4522
4523         // layer point of the current center
4524         _getCenterLayerPoint: function () {
4525                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4526         },
4527
4528         // offset of the specified place to the current center in pixels
4529         _getCenterOffset: function (latlng) {
4530                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4531         },
4532
4533         // adjust center for view to get inside bounds
4534         _limitCenter: function (center, zoom, bounds) {
4535
4536                 if (!bounds) { return center; }
4537
4538                 var centerPoint = this.project(center, zoom),
4539                     viewHalf = this.getSize().divideBy(2),
4540                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4541                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4542
4543                 // If offset is less than a pixel, ignore.
4544                 // This prevents unstable projections from getting into
4545                 // an infinite loop of tiny offsets.
4546                 if (offset.round().equals([0, 0])) {
4547                         return center;
4548                 }
4549
4550                 return this.unproject(centerPoint.add(offset), zoom);
4551         },
4552
4553         // adjust offset for view to get inside bounds
4554         _limitOffset: function (offset, bounds) {
4555                 if (!bounds) { return offset; }
4556
4557                 var viewBounds = this.getPixelBounds(),
4558                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4559
4560                 return offset.add(this._getBoundsOffset(newBounds, bounds));
4561         },
4562
4563         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4564         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4565                 var projectedMaxBounds = toBounds(
4566                         this.project(maxBounds.getNorthEast(), zoom),
4567                         this.project(maxBounds.getSouthWest(), zoom)
4568                     ),
4569                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4570                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4571
4572                     dx = this._rebound(minOffset.x, -maxOffset.x),
4573                     dy = this._rebound(minOffset.y, -maxOffset.y);
4574
4575                 return new Point(dx, dy);
4576         },
4577
4578         _rebound: function (left, right) {
4579                 return left + right > 0 ?
4580                         Math.round(left - right) / 2 :
4581                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4582         },
4583
4584         _limitZoom: function (zoom) {
4585                 var min = this.getMinZoom(),
4586                     max = this.getMaxZoom(),
4587                     snap = any3d ? this.options.zoomSnap : 1;
4588                 if (snap) {
4589                         zoom = Math.round(zoom / snap) * snap;
4590                 }
4591                 return Math.max(min, Math.min(max, zoom));
4592         },
4593
4594         _onPanTransitionStep: function () {
4595                 this.fire('move');
4596         },
4597
4598         _onPanTransitionEnd: function () {
4599                 removeClass(this._mapPane, 'leaflet-pan-anim');
4600                 this.fire('moveend');
4601         },
4602
4603         _tryAnimatedPan: function (center, options) {
4604                 // difference between the new and current centers in pixels
4605                 var offset = this._getCenterOffset(center)._trunc();
4606
4607                 // don't animate too far unless animate: true specified in options
4608                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4609
4610                 this.panBy(offset, options);
4611
4612                 return true;
4613         },
4614
4615         _createAnimProxy: function () {
4616
4617                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4618                 this._panes.mapPane.appendChild(proxy);
4619
4620                 this.on('zoomanim', function (e) {
4621                         var prop = TRANSFORM,
4622                             transform = this._proxy.style[prop];
4623
4624                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4625
4626                         // workaround for case when transform is the same and so transitionend event is not fired
4627                         if (transform === this._proxy.style[prop] && this._animatingZoom) {
4628                                 this._onZoomTransitionEnd();
4629                         }
4630                 }, this);
4631
4632                 this.on('load moveend', this._animMoveEnd, this);
4633
4634                 this._on('unload', this._destroyAnimProxy, this);
4635         },
4636
4637         _destroyAnimProxy: function () {
4638                 remove(this._proxy);
4639                 this.off('load moveend', this._animMoveEnd, this);
4640                 delete this._proxy;
4641         },
4642
4643         _animMoveEnd: function () {
4644                 var c = this.getCenter(),
4645                     z = this.getZoom();
4646                 setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4647         },
4648
4649         _catchTransitionEnd: function (e) {
4650                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4651                         this._onZoomTransitionEnd();
4652                 }
4653         },
4654
4655         _nothingToAnimate: function () {
4656                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4657         },
4658
4659         _tryAnimatedZoom: function (center, zoom, options) {
4660
4661                 if (this._animatingZoom) { return true; }
4662
4663                 options = options || {};
4664
4665                 // don't animate if disabled, not supported or zoom difference is too large
4666                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4667                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4668
4669                 // offset is the pixel coords of the zoom origin relative to the current center
4670                 var scale = this.getZoomScale(zoom),
4671                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4672
4673                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4674                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4675
4676                 requestAnimFrame(function () {
4677                         this
4678                             ._moveStart(true, false)
4679                             ._animateZoom(center, zoom, true);
4680                 }, this);
4681
4682                 return true;
4683         },
4684
4685         _animateZoom: function (center, zoom, startAnim, noUpdate) {
4686                 if (!this._mapPane) { return; }
4687
4688                 if (startAnim) {
4689                         this._animatingZoom = true;
4690
4691                         // remember what center/zoom to set after animation
4692                         this._animateToCenter = center;
4693                         this._animateToZoom = zoom;
4694
4695                         addClass(this._mapPane, 'leaflet-zoom-anim');
4696                 }
4697
4698                 // @section Other Events
4699                 // @event zoomanim: ZoomAnimEvent
4700                 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4701                 this.fire('zoomanim', {
4702                         center: center,
4703                         zoom: zoom,
4704                         noUpdate: noUpdate
4705                 });
4706
4707                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4708                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4709         },
4710
4711         _onZoomTransitionEnd: function () {
4712                 if (!this._animatingZoom) { return; }
4713
4714                 if (this._mapPane) {
4715                         removeClass(this._mapPane, 'leaflet-zoom-anim');
4716                 }
4717
4718                 this._animatingZoom = false;
4719
4720                 this._move(this._animateToCenter, this._animateToZoom);
4721
4722                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4723                 requestAnimFrame(function () {
4724                         this._moveEnd(true);
4725                 }, this);
4726         }
4727 });
4728
4729 // @section
4730
4731 // @factory L.map(id: String, options?: Map options)
4732 // Instantiates a map object given the DOM ID of a `<div>` element
4733 // and optionally an object literal with `Map options`.
4734 //
4735 // @alternative
4736 // @factory L.map(el: HTMLElement, options?: Map options)
4737 // Instantiates a map object given an instance of a `<div>` HTML element
4738 // and optionally an object literal with `Map options`.
4739 function createMap(id, options) {
4740         return new Map(id, options);
4741 }
4742
4743 /*
4744  * @class Control
4745  * @aka L.Control
4746  * @inherits Class
4747  *
4748  * L.Control is a base class for implementing map controls. Handles positioning.
4749  * All other controls extend from this class.
4750  */
4751
4752 var Control = Class.extend({
4753         // @section
4754         // @aka Control options
4755         options: {
4756                 // @option position: String = 'topright'
4757                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4758                 // `'topright'`, `'bottomleft'` or `'bottomright'`
4759                 position: 'topright'
4760         },
4761
4762         initialize: function (options) {
4763                 setOptions(this, options);
4764         },
4765
4766         /* @section
4767          * Classes extending L.Control will inherit the following methods:
4768          *
4769          * @method getPosition: string
4770          * Returns the position of the control.
4771          */
4772         getPosition: function () {
4773                 return this.options.position;
4774         },
4775
4776         // @method setPosition(position: string): this
4777         // Sets the position of the control.
4778         setPosition: function (position) {
4779                 var map = this._map;
4780
4781                 if (map) {
4782                         map.removeControl(this);
4783                 }
4784
4785                 this.options.position = position;
4786
4787                 if (map) {
4788                         map.addControl(this);
4789                 }
4790
4791                 return this;
4792         },
4793
4794         // @method getContainer: HTMLElement
4795         // Returns the HTMLElement that contains the control.
4796         getContainer: function () {
4797                 return this._container;
4798         },
4799
4800         // @method addTo(map: Map): this
4801         // Adds the control to the given map.
4802         addTo: function (map) {
4803                 this.remove();
4804                 this._map = map;
4805
4806                 var container = this._container = this.onAdd(map),
4807                     pos = this.getPosition(),
4808                     corner = map._controlCorners[pos];
4809
4810                 addClass(container, 'leaflet-control');
4811
4812                 if (pos.indexOf('bottom') !== -1) {
4813                         corner.insertBefore(container, corner.firstChild);
4814                 } else {
4815                         corner.appendChild(container);
4816                 }
4817
4818                 this._map.on('unload', this.remove, this);
4819
4820                 return this;
4821         },
4822
4823         // @method remove: this
4824         // Removes the control from the map it is currently active on.
4825         remove: function () {
4826                 if (!this._map) {
4827                         return this;
4828                 }
4829
4830                 remove(this._container);
4831
4832                 if (this.onRemove) {
4833                         this.onRemove(this._map);
4834                 }
4835
4836                 this._map.off('unload', this.remove, this);
4837                 this._map = null;
4838
4839                 return this;
4840         },
4841
4842         _refocusOnMap: function (e) {
4843                 // if map exists and event is not a keyboard event
4844                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4845                         this._map.getContainer().focus();
4846                 }
4847         }
4848 });
4849
4850 var control = function (options) {
4851         return new Control(options);
4852 };
4853
4854 /* @section Extension methods
4855  * @uninheritable
4856  *
4857  * Every control should extend from `L.Control` and (re-)implement the following methods.
4858  *
4859  * @method onAdd(map: Map): HTMLElement
4860  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4861  *
4862  * @method onRemove(map: Map)
4863  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4864  */
4865
4866 /* @namespace Map
4867  * @section Methods for Layers and Controls
4868  */
4869 Map.include({
4870         // @method addControl(control: Control): this
4871         // Adds the given control to the map
4872         addControl: function (control) {
4873                 control.addTo(this);
4874                 return this;
4875         },
4876
4877         // @method removeControl(control: Control): this
4878         // Removes the given control from the map
4879         removeControl: function (control) {
4880                 control.remove();
4881                 return this;
4882         },
4883
4884         _initControlPos: function () {
4885                 var corners = this._controlCorners = {},
4886                     l = 'leaflet-',
4887                     container = this._controlContainer =
4888                             create$1('div', l + 'control-container', this._container);
4889
4890                 function createCorner(vSide, hSide) {
4891                         var className = l + vSide + ' ' + l + hSide;
4892
4893                         corners[vSide + hSide] = create$1('div', className, container);
4894                 }
4895
4896                 createCorner('top', 'left');
4897                 createCorner('top', 'right');
4898                 createCorner('bottom', 'left');
4899                 createCorner('bottom', 'right');
4900         },
4901
4902         _clearControlPos: function () {
4903                 for (var i in this._controlCorners) {
4904                         remove(this._controlCorners[i]);
4905                 }
4906                 remove(this._controlContainer);
4907                 delete this._controlCorners;
4908                 delete this._controlContainer;
4909         }
4910 });
4911
4912 /*
4913  * @class Control.Layers
4914  * @aka L.Control.Layers
4915  * @inherits Control
4916  *
4917  * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control/)). Extends `Control`.
4918  *
4919  * @example
4920  *
4921  * ```js
4922  * var baseLayers = {
4923  *      "Mapbox": mapbox,
4924  *      "OpenStreetMap": osm
4925  * };
4926  *
4927  * var overlays = {
4928  *      "Marker": marker,
4929  *      "Roads": roadsLayer
4930  * };
4931  *
4932  * L.control.layers(baseLayers, overlays).addTo(map);
4933  * ```
4934  *
4935  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4936  *
4937  * ```js
4938  * {
4939  *     "<someName1>": layer1,
4940  *     "<someName2>": layer2
4941  * }
4942  * ```
4943  *
4944  * The layer names can contain HTML, which allows you to add additional styling to the items:
4945  *
4946  * ```js
4947  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4948  * ```
4949  */
4950
4951 var Layers = Control.extend({
4952         // @section
4953         // @aka Control.Layers options
4954         options: {
4955                 // @option collapsed: Boolean = true
4956                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4957                 collapsed: true,
4958                 position: 'topright',
4959
4960                 // @option autoZIndex: Boolean = true
4961                 // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
4962                 autoZIndex: true,
4963
4964                 // @option hideSingleBase: Boolean = false
4965                 // If `true`, the base layers in the control will be hidden when there is only one.
4966                 hideSingleBase: false,
4967
4968                 // @option sortLayers: Boolean = false
4969                 // Whether to sort the layers. When `false`, layers will keep the order
4970                 // in which they were added to the control.
4971                 sortLayers: false,
4972
4973                 // @option sortFunction: Function = *
4974                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4975                 // that will be used for sorting the layers, when `sortLayers` is `true`.
4976                 // The function receives both the `L.Layer` instances and their names, as in
4977                 // `sortFunction(layerA, layerB, nameA, nameB)`.
4978                 // By default, it sorts layers alphabetically by their name.
4979                 sortFunction: function (layerA, layerB, nameA, nameB) {
4980                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4981                 }
4982         },
4983
4984         initialize: function (baseLayers, overlays, options) {
4985                 setOptions(this, options);
4986
4987                 this._layerControlInputs = [];
4988                 this._layers = [];
4989                 this._lastZIndex = 0;
4990                 this._handlingClick = false;
4991
4992                 for (var i in baseLayers) {
4993                         this._addLayer(baseLayers[i], i);
4994                 }
4995
4996                 for (i in overlays) {
4997                         this._addLayer(overlays[i], i, true);
4998                 }
4999         },
5000
5001         onAdd: function (map) {
5002                 this._initLayout();
5003                 this._update();
5004
5005                 this._map = map;
5006                 map.on('zoomend', this._checkDisabledLayers, this);
5007
5008                 for (var i = 0; i < this._layers.length; i++) {
5009                         this._layers[i].layer.on('add remove', this._onLayerChange, this);
5010                 }
5011
5012                 return this._container;
5013         },
5014
5015         addTo: function (map) {
5016                 Control.prototype.addTo.call(this, map);
5017                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
5018                 return this._expandIfNotCollapsed();
5019         },
5020
5021         onRemove: function () {
5022                 this._map.off('zoomend', this._checkDisabledLayers, this);
5023
5024                 for (var i = 0; i < this._layers.length; i++) {
5025                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
5026                 }
5027         },
5028
5029         // @method addBaseLayer(layer: Layer, name: String): this
5030         // Adds a base layer (radio button entry) with the given name to the control.
5031         addBaseLayer: function (layer, name) {
5032                 this._addLayer(layer, name);
5033                 return (this._map) ? this._update() : this;
5034         },
5035
5036         // @method addOverlay(layer: Layer, name: String): this
5037         // Adds an overlay (checkbox entry) with the given name to the control.
5038         addOverlay: function (layer, name) {
5039                 this._addLayer(layer, name, true);
5040                 return (this._map) ? this._update() : this;
5041         },
5042
5043         // @method removeLayer(layer: Layer): this
5044         // Remove the given layer from the control.
5045         removeLayer: function (layer) {
5046                 layer.off('add remove', this._onLayerChange, this);
5047
5048                 var obj = this._getLayer(stamp(layer));
5049                 if (obj) {
5050                         this._layers.splice(this._layers.indexOf(obj), 1);
5051                 }
5052                 return (this._map) ? this._update() : this;
5053         },
5054
5055         // @method expand(): this
5056         // Expand the control container if collapsed.
5057         expand: function () {
5058                 addClass(this._container, 'leaflet-control-layers-expanded');
5059                 this._section.style.height = null;
5060                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5061                 if (acceptableHeight < this._section.clientHeight) {
5062                         addClass(this._section, 'leaflet-control-layers-scrollbar');
5063                         this._section.style.height = acceptableHeight + 'px';
5064                 } else {
5065                         removeClass(this._section, 'leaflet-control-layers-scrollbar');
5066                 }
5067                 this._checkDisabledLayers();
5068                 return this;
5069         },
5070
5071         // @method collapse(): this
5072         // Collapse the control container if expanded.
5073         collapse: function () {
5074                 removeClass(this._container, 'leaflet-control-layers-expanded');
5075                 return this;
5076         },
5077
5078         _initLayout: function () {
5079                 var className = 'leaflet-control-layers',
5080                     container = this._container = create$1('div', className),
5081                     collapsed = this.options.collapsed;
5082
5083                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5084                 container.setAttribute('aria-haspopup', true);
5085
5086                 disableClickPropagation(container);
5087                 disableScrollPropagation(container);
5088
5089                 var section = this._section = create$1('section', className + '-list');
5090
5091                 if (collapsed) {
5092                         this._map.on('click', this.collapse, this);
5093
5094                         if (!android) {
5095                                 on(container, {
5096                                         mouseenter: this.expand,
5097                                         mouseleave: this.collapse
5098                                 }, this);
5099                         }
5100                 }
5101
5102                 var link = this._layersLink = create$1('a', className + '-toggle', container);
5103                 link.href = '#';
5104                 link.title = 'Layers';
5105
5106                 if (touch) {
5107                         on(link, 'click', stop);
5108                         on(link, 'click', this.expand, this);
5109                 } else {
5110                         on(link, 'focus', this.expand, this);
5111                 }
5112
5113                 if (!collapsed) {
5114                         this.expand();
5115                 }
5116
5117                 this._baseLayersList = create$1('div', className + '-base', section);
5118                 this._separator = create$1('div', className + '-separator', section);
5119                 this._overlaysList = create$1('div', className + '-overlays', section);
5120
5121                 container.appendChild(section);
5122         },
5123
5124         _getLayer: function (id) {
5125                 for (var i = 0; i < this._layers.length; i++) {
5126
5127                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5128                                 return this._layers[i];
5129                         }
5130                 }
5131         },
5132
5133         _addLayer: function (layer, name, overlay) {
5134                 if (this._map) {
5135                         layer.on('add remove', this._onLayerChange, this);
5136                 }
5137
5138                 this._layers.push({
5139                         layer: layer,
5140                         name: name,
5141                         overlay: overlay
5142                 });
5143
5144                 if (this.options.sortLayers) {
5145                         this._layers.sort(bind(function (a, b) {
5146                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5147                         }, this));
5148                 }
5149
5150                 if (this.options.autoZIndex && layer.setZIndex) {
5151                         this._lastZIndex++;
5152                         layer.setZIndex(this._lastZIndex);
5153                 }
5154
5155                 this._expandIfNotCollapsed();
5156         },
5157
5158         _update: function () {
5159                 if (!this._container) { return this; }
5160
5161                 empty(this._baseLayersList);
5162                 empty(this._overlaysList);
5163
5164                 this._layerControlInputs = [];
5165                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5166
5167                 for (i = 0; i < this._layers.length; i++) {
5168                         obj = this._layers[i];
5169                         this._addItem(obj);
5170                         overlaysPresent = overlaysPresent || obj.overlay;
5171                         baseLayersPresent = baseLayersPresent || !obj.overlay;
5172                         baseLayersCount += !obj.overlay ? 1 : 0;
5173                 }
5174
5175                 // Hide base layers section if there's only one layer.
5176                 if (this.options.hideSingleBase) {
5177                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5178                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5179                 }
5180
5181                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5182
5183                 return this;
5184         },
5185
5186         _onLayerChange: function (e) {
5187                 if (!this._handlingClick) {
5188                         this._update();
5189                 }
5190
5191                 var obj = this._getLayer(stamp(e.target));
5192
5193                 // @namespace Map
5194                 // @section Layer events
5195                 // @event baselayerchange: LayersControlEvent
5196                 // Fired when the base layer is changed through the [layer control](#control-layers).
5197                 // @event overlayadd: LayersControlEvent
5198                 // Fired when an overlay is selected through the [layer control](#control-layers).
5199                 // @event overlayremove: LayersControlEvent
5200                 // Fired when an overlay is deselected through the [layer control](#control-layers).
5201                 // @namespace Control.Layers
5202                 var type = obj.overlay ?
5203                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5204                         (e.type === 'add' ? 'baselayerchange' : null);
5205
5206                 if (type) {
5207                         this._map.fire(type, obj);
5208                 }
5209         },
5210
5211         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5212         _createRadioElement: function (name, checked) {
5213
5214                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5215                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5216
5217                 var radioFragment = document.createElement('div');
5218                 radioFragment.innerHTML = radioHtml;
5219
5220                 return radioFragment.firstChild;
5221         },
5222
5223         _addItem: function (obj) {
5224                 var label = document.createElement('label'),
5225                     checked = this._map.hasLayer(obj.layer),
5226                     input;
5227
5228                 if (obj.overlay) {
5229                         input = document.createElement('input');
5230                         input.type = 'checkbox';
5231                         input.className = 'leaflet-control-layers-selector';
5232                         input.defaultChecked = checked;
5233                 } else {
5234                         input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5235                 }
5236
5237                 this._layerControlInputs.push(input);
5238                 input.layerId = stamp(obj.layer);
5239
5240                 on(input, 'click', this._onInputClick, this);
5241
5242                 var name = document.createElement('span');
5243                 name.innerHTML = ' ' + obj.name;
5244
5245                 // Helps from preventing layer control flicker when checkboxes are disabled
5246                 // https://github.com/Leaflet/Leaflet/issues/2771
5247                 var holder = document.createElement('div');
5248
5249                 label.appendChild(holder);
5250                 holder.appendChild(input);
5251                 holder.appendChild(name);
5252
5253                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5254                 container.appendChild(label);
5255
5256                 this._checkDisabledLayers();
5257                 return label;
5258         },
5259
5260         _onInputClick: function () {
5261                 var inputs = this._layerControlInputs,
5262                     input, layer;
5263                 var addedLayers = [],
5264                     removedLayers = [];
5265
5266                 this._handlingClick = true;
5267
5268                 for (var i = inputs.length - 1; i >= 0; i--) {
5269                         input = inputs[i];
5270                         layer = this._getLayer(input.layerId).layer;
5271
5272                         if (input.checked) {
5273                                 addedLayers.push(layer);
5274                         } else if (!input.checked) {
5275                                 removedLayers.push(layer);
5276                         }
5277                 }
5278
5279                 // Bugfix issue 2318: Should remove all old layers before readding new ones
5280                 for (i = 0; i < removedLayers.length; i++) {
5281                         if (this._map.hasLayer(removedLayers[i])) {
5282                                 this._map.removeLayer(removedLayers[i]);
5283                         }
5284                 }
5285                 for (i = 0; i < addedLayers.length; i++) {
5286                         if (!this._map.hasLayer(addedLayers[i])) {
5287                                 this._map.addLayer(addedLayers[i]);
5288                         }
5289                 }
5290
5291                 this._handlingClick = false;
5292
5293                 this._refocusOnMap();
5294         },
5295
5296         _checkDisabledLayers: function () {
5297                 var inputs = this._layerControlInputs,
5298                     input,
5299                     layer,
5300                     zoom = this._map.getZoom();
5301
5302                 for (var i = inputs.length - 1; i >= 0; i--) {
5303                         input = inputs[i];
5304                         layer = this._getLayer(input.layerId).layer;
5305                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5306                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5307
5308                 }
5309         },
5310
5311         _expandIfNotCollapsed: function () {
5312                 if (this._map && !this.options.collapsed) {
5313                         this.expand();
5314                 }
5315                 return this;
5316         },
5317
5318         _expand: function () {
5319                 // Backward compatibility, remove me in 1.1.
5320                 return this.expand();
5321         },
5322
5323         _collapse: function () {
5324                 // Backward compatibility, remove me in 1.1.
5325                 return this.collapse();
5326         }
5327
5328 });
5329
5330
5331 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5332 // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
5333 var layers = function (baseLayers, overlays, options) {
5334         return new Layers(baseLayers, overlays, options);
5335 };
5336
5337 /*
5338  * @class Control.Zoom
5339  * @aka L.Control.Zoom
5340  * @inherits Control
5341  *
5342  * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
5343  */
5344
5345 var Zoom = Control.extend({
5346         // @section
5347         // @aka Control.Zoom options
5348         options: {
5349                 position: 'topleft',
5350
5351                 // @option zoomInText: String = '+'
5352                 // The text set on the 'zoom in' button.
5353                 zoomInText: '+',
5354
5355                 // @option zoomInTitle: String = 'Zoom in'
5356                 // The title set on the 'zoom in' button.
5357                 zoomInTitle: 'Zoom in',
5358
5359                 // @option zoomOutText: String = '&#x2212;'
5360                 // The text set on the 'zoom out' button.
5361                 zoomOutText: '&#x2212;',
5362
5363                 // @option zoomOutTitle: String = 'Zoom out'
5364                 // The title set on the 'zoom out' button.
5365                 zoomOutTitle: 'Zoom out'
5366         },
5367
5368         onAdd: function (map) {
5369                 var zoomName = 'leaflet-control-zoom',
5370                     container = create$1('div', zoomName + ' leaflet-bar'),
5371                     options = this.options;
5372
5373                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
5374                         zoomName + '-in',  container, this._zoomIn);
5375                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5376                         zoomName + '-out', container, this._zoomOut);
5377
5378                 this._updateDisabled();
5379                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5380
5381                 return container;
5382         },
5383
5384         onRemove: function (map) {
5385                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5386         },
5387
5388         disable: function () {
5389                 this._disabled = true;
5390                 this._updateDisabled();
5391                 return this;
5392         },
5393
5394         enable: function () {
5395                 this._disabled = false;
5396                 this._updateDisabled();
5397                 return this;
5398         },
5399
5400         _zoomIn: function (e) {
5401                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5402                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5403                 }
5404         },
5405
5406         _zoomOut: function (e) {
5407                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5408                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5409                 }
5410         },
5411
5412         _createButton: function (html, title, className, container, fn) {
5413                 var link = create$1('a', className, container);
5414                 link.innerHTML = html;
5415                 link.href = '#';
5416                 link.title = title;
5417
5418                 /*
5419                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5420                  */
5421                 link.setAttribute('role', 'button');
5422                 link.setAttribute('aria-label', title);
5423
5424                 disableClickPropagation(link);
5425                 on(link, 'click', stop);
5426                 on(link, 'click', fn, this);
5427                 on(link, 'click', this._refocusOnMap, this);
5428
5429                 return link;
5430         },
5431
5432         _updateDisabled: function () {
5433                 var map = this._map,
5434                     className = 'leaflet-disabled';
5435
5436                 removeClass(this._zoomInButton, className);
5437                 removeClass(this._zoomOutButton, className);
5438
5439                 if (this._disabled || map._zoom === map.getMinZoom()) {
5440                         addClass(this._zoomOutButton, className);
5441                 }
5442                 if (this._disabled || map._zoom === map.getMaxZoom()) {
5443                         addClass(this._zoomInButton, className);
5444                 }
5445         }
5446 });
5447
5448 // @namespace Map
5449 // @section Control options
5450 // @option zoomControl: Boolean = true
5451 // Whether a [zoom control](#control-zoom) is added to the map by default.
5452 Map.mergeOptions({
5453         zoomControl: true
5454 });
5455
5456 Map.addInitHook(function () {
5457         if (this.options.zoomControl) {
5458                 // @section Controls
5459                 // @property zoomControl: Control.Zoom
5460                 // The default zoom control (only available if the
5461                 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5462                 this.zoomControl = new Zoom();
5463                 this.addControl(this.zoomControl);
5464         }
5465 });
5466
5467 // @namespace Control.Zoom
5468 // @factory L.control.zoom(options: Control.Zoom options)
5469 // Creates a zoom control
5470 var zoom = function (options) {
5471         return new Zoom(options);
5472 };
5473
5474 /*
5475  * @class Control.Scale
5476  * @aka L.Control.Scale
5477  * @inherits Control
5478  *
5479  * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
5480  *
5481  * @example
5482  *
5483  * ```js
5484  * L.control.scale().addTo(map);
5485  * ```
5486  */
5487
5488 var Scale = Control.extend({
5489         // @section
5490         // @aka Control.Scale options
5491         options: {
5492                 position: 'bottomleft',
5493
5494                 // @option maxWidth: Number = 100
5495                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5496                 maxWidth: 100,
5497
5498                 // @option metric: Boolean = True
5499                 // Whether to show the metric scale line (m/km).
5500                 metric: true,
5501
5502                 // @option imperial: Boolean = True
5503                 // Whether to show the imperial scale line (mi/ft).
5504                 imperial: true
5505
5506                 // @option updateWhenIdle: Boolean = false
5507                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5508         },
5509
5510         onAdd: function (map) {
5511                 var className = 'leaflet-control-scale',
5512                     container = create$1('div', className),
5513                     options = this.options;
5514
5515                 this._addScales(options, className + '-line', container);
5516
5517                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5518                 map.whenReady(this._update, this);
5519
5520                 return container;
5521         },
5522
5523         onRemove: function (map) {
5524                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5525         },
5526
5527         _addScales: function (options, className, container) {
5528                 if (options.metric) {
5529                         this._mScale = create$1('div', className, container);
5530                 }
5531                 if (options.imperial) {
5532                         this._iScale = create$1('div', className, container);
5533                 }
5534         },
5535
5536         _update: function () {
5537                 var map = this._map,
5538                     y = map.getSize().y / 2;
5539
5540                 var maxMeters = map.distance(
5541                         map.containerPointToLatLng([0, y]),
5542                         map.containerPointToLatLng([this.options.maxWidth, y]));
5543
5544                 this._updateScales(maxMeters);
5545         },
5546
5547         _updateScales: function (maxMeters) {
5548                 if (this.options.metric && maxMeters) {
5549                         this._updateMetric(maxMeters);
5550                 }
5551                 if (this.options.imperial && maxMeters) {
5552                         this._updateImperial(maxMeters);
5553                 }
5554         },
5555
5556         _updateMetric: function (maxMeters) {
5557                 var meters = this._getRoundNum(maxMeters),
5558                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5559
5560                 this._updateScale(this._mScale, label, meters / maxMeters);
5561         },
5562
5563         _updateImperial: function (maxMeters) {
5564                 var maxFeet = maxMeters * 3.2808399,
5565                     maxMiles, miles, feet;
5566
5567                 if (maxFeet > 5280) {
5568                         maxMiles = maxFeet / 5280;
5569                         miles = this._getRoundNum(maxMiles);
5570                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5571
5572                 } else {
5573                         feet = this._getRoundNum(maxFeet);
5574                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5575                 }
5576         },
5577
5578         _updateScale: function (scale, text, ratio) {
5579                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5580                 scale.innerHTML = text;
5581         },
5582
5583         _getRoundNum: function (num) {
5584                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5585                     d = num / pow10;
5586
5587                 d = d >= 10 ? 10 :
5588                     d >= 5 ? 5 :
5589                     d >= 3 ? 3 :
5590                     d >= 2 ? 2 : 1;
5591
5592                 return pow10 * d;
5593         }
5594 });
5595
5596
5597 // @factory L.control.scale(options?: Control.Scale options)
5598 // Creates an scale control with the given options.
5599 var scale = function (options) {
5600         return new Scale(options);
5601 };
5602
5603 /*
5604  * @class Control.Attribution
5605  * @aka L.Control.Attribution
5606  * @inherits Control
5607  *
5608  * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
5609  */
5610
5611 var Attribution = Control.extend({
5612         // @section
5613         // @aka Control.Attribution options
5614         options: {
5615                 position: 'bottomright',
5616
5617                 // @option prefix: String = 'Leaflet'
5618                 // The HTML text shown before the attributions. Pass `false` to disable.
5619                 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5620         },
5621
5622         initialize: function (options) {
5623                 setOptions(this, options);
5624
5625                 this._attributions = {};
5626         },
5627
5628         onAdd: function (map) {
5629                 map.attributionControl = this;
5630                 this._container = create$1('div', 'leaflet-control-attribution');
5631                 disableClickPropagation(this._container);
5632
5633                 // TODO ugly, refactor
5634                 for (var i in map._layers) {
5635                         if (map._layers[i].getAttribution) {
5636                                 this.addAttribution(map._layers[i].getAttribution());
5637                         }
5638                 }
5639
5640                 this._update();
5641
5642                 return this._container;
5643         },
5644
5645         // @method setPrefix(prefix: String): this
5646         // Sets the text before the attributions.
5647         setPrefix: function (prefix) {
5648                 this.options.prefix = prefix;
5649                 this._update();
5650                 return this;
5651         },
5652
5653         // @method addAttribution(text: String): this
5654         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5655         addAttribution: function (text) {
5656                 if (!text) { return this; }
5657
5658                 if (!this._attributions[text]) {
5659                         this._attributions[text] = 0;
5660                 }
5661                 this._attributions[text]++;
5662
5663                 this._update();
5664
5665                 return this;
5666         },
5667
5668         // @method removeAttribution(text: String): this
5669         // Removes an attribution text.
5670         removeAttribution: function (text) {
5671                 if (!text) { return this; }
5672
5673                 if (this._attributions[text]) {
5674                         this._attributions[text]--;
5675                         this._update();
5676                 }
5677
5678                 return this;
5679         },
5680
5681         _update: function () {
5682                 if (!this._map) { return; }
5683
5684                 var attribs = [];
5685
5686                 for (var i in this._attributions) {
5687                         if (this._attributions[i]) {
5688                                 attribs.push(i);
5689                         }
5690                 }
5691
5692                 var prefixAndAttribs = [];
5693
5694                 if (this.options.prefix) {
5695                         prefixAndAttribs.push(this.options.prefix);
5696                 }
5697                 if (attribs.length) {
5698                         prefixAndAttribs.push(attribs.join(', '));
5699                 }
5700
5701                 this._container.innerHTML = prefixAndAttribs.join(' | ');
5702         }
5703 });
5704
5705 // @namespace Map
5706 // @section Control options
5707 // @option attributionControl: Boolean = true
5708 // Whether a [attribution control](#control-attribution) is added to the map by default.
5709 Map.mergeOptions({
5710         attributionControl: true
5711 });
5712
5713 Map.addInitHook(function () {
5714         if (this.options.attributionControl) {
5715                 new Attribution().addTo(this);
5716         }
5717 });
5718
5719 // @namespace Control.Attribution
5720 // @factory L.control.attribution(options: Control.Attribution options)
5721 // Creates an attribution control.
5722 var attribution = function (options) {
5723         return new Attribution(options);
5724 };
5725
5726 Control.Layers = Layers;
5727 Control.Zoom = Zoom;
5728 Control.Scale = Scale;
5729 Control.Attribution = Attribution;
5730
5731 control.layers = layers;
5732 control.zoom = zoom;
5733 control.scale = scale;
5734 control.attribution = attribution;
5735
5736 /*
5737         L.Handler is a base class for handler classes that are used internally to inject
5738         interaction features like dragging to classes like Map and Marker.
5739 */
5740
5741 // @class Handler
5742 // @aka L.Handler
5743 // Abstract class for map interaction handlers
5744
5745 var Handler = Class.extend({
5746         initialize: function (map) {
5747                 this._map = map;
5748         },
5749
5750         // @method enable(): this
5751         // Enables the handler
5752         enable: function () {
5753                 if (this._enabled) { return this; }
5754
5755                 this._enabled = true;
5756                 this.addHooks();
5757                 return this;
5758         },
5759
5760         // @method disable(): this
5761         // Disables the handler
5762         disable: function () {
5763                 if (!this._enabled) { return this; }
5764
5765                 this._enabled = false;
5766                 this.removeHooks();
5767                 return this;
5768         },
5769
5770         // @method enabled(): Boolean
5771         // Returns `true` if the handler is enabled
5772         enabled: function () {
5773                 return !!this._enabled;
5774         }
5775
5776         // @section Extension methods
5777         // Classes inheriting from `Handler` must implement the two following methods:
5778         // @method addHooks()
5779         // Called when the handler is enabled, should add event hooks.
5780         // @method removeHooks()
5781         // Called when the handler is disabled, should remove the event hooks added previously.
5782 });
5783
5784 // @section There is static function which can be called without instantiating L.Handler:
5785 // @function addTo(map: Map, name: String): this
5786 // Adds a new Handler to the given map with the given name.
5787 Handler.addTo = function (map, name) {
5788         map.addHandler(name, this);
5789         return this;
5790 };
5791
5792 var Mixin = {Events: Events};
5793
5794 /*
5795  * @class Draggable
5796  * @aka L.Draggable
5797  * @inherits Evented
5798  *
5799  * A class for making DOM elements draggable (including touch support).
5800  * Used internally for map and marker dragging. Only works for elements
5801  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5802  *
5803  * @example
5804  * ```js
5805  * var draggable = new L.Draggable(elementToDrag);
5806  * draggable.enable();
5807  * ```
5808  */
5809
5810 var START = touch ? 'touchstart mousedown' : 'mousedown';
5811 var END = {
5812         mousedown: 'mouseup',
5813         touchstart: 'touchend',
5814         pointerdown: 'touchend',
5815         MSPointerDown: 'touchend'
5816 };
5817 var MOVE = {
5818         mousedown: 'mousemove',
5819         touchstart: 'touchmove',
5820         pointerdown: 'touchmove',
5821         MSPointerDown: 'touchmove'
5822 };
5823
5824
5825 var Draggable = Evented.extend({
5826
5827         options: {
5828                 // @section
5829                 // @aka Draggable options
5830                 // @option clickTolerance: Number = 3
5831                 // The max number of pixels a user can shift the mouse pointer during a click
5832                 // for it to be considered a valid click (as opposed to a mouse drag).
5833                 clickTolerance: 3
5834         },
5835
5836         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5837         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5838         initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5839                 setOptions(this, options);
5840
5841                 this._element = element;
5842                 this._dragStartTarget = dragStartTarget || element;
5843                 this._preventOutline = preventOutline$$1;
5844         },
5845
5846         // @method enable()
5847         // Enables the dragging ability
5848         enable: function () {
5849                 if (this._enabled) { return; }
5850
5851                 on(this._dragStartTarget, START, this._onDown, this);
5852
5853                 this._enabled = true;
5854         },
5855
5856         // @method disable()
5857         // Disables the dragging ability
5858         disable: function () {
5859                 if (!this._enabled) { return; }
5860
5861                 // If we're currently dragging this draggable,
5862                 // disabling it counts as first ending the drag.
5863                 if (Draggable._dragging === this) {
5864                         this.finishDrag();
5865                 }
5866
5867                 off(this._dragStartTarget, START, this._onDown, this);
5868
5869                 this._enabled = false;
5870                 this._moved = false;
5871         },
5872
5873         _onDown: function (e) {
5874                 // Ignore simulated events, since we handle both touch and
5875                 // mouse explicitly; otherwise we risk getting duplicates of
5876                 // touch events, see #4315.
5877                 // Also ignore the event if disabled; this happens in IE11
5878                 // under some circumstances, see #3666.
5879                 if (e._simulated || !this._enabled) { return; }
5880
5881                 this._moved = false;
5882
5883                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5884
5885                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5886                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.
5887
5888                 if (this._preventOutline) {
5889                         preventOutline(this._element);
5890                 }
5891
5892                 disableImageDrag();
5893                 disableTextSelection();
5894
5895                 if (this._moving) { return; }
5896
5897                 // @event down: Event
5898                 // Fired when a drag is about to start.
5899                 this.fire('down');
5900
5901                 var first = e.touches ? e.touches[0] : e,
5902                     sizedParent = getSizedParentNode(this._element);
5903
5904                 this._startPoint = new Point(first.clientX, first.clientY);
5905
5906                 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5907                 this._parentScale = getScale(sizedParent);
5908
5909                 on(document, MOVE[e.type], this._onMove, this);
5910                 on(document, END[e.type], this._onUp, this);
5911         },
5912
5913         _onMove: function (e) {
5914                 // Ignore simulated events, since we handle both touch and
5915                 // mouse explicitly; otherwise we risk getting duplicates of
5916                 // touch events, see #4315.
5917                 // Also ignore the event if disabled; this happens in IE11
5918                 // under some circumstances, see #3666.
5919                 if (e._simulated || !this._enabled) { return; }
5920
5921                 if (e.touches && e.touches.length > 1) {
5922                         this._moved = true;
5923                         return;
5924                 }
5925
5926                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5927                     offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5928
5929                 if (!offset.x && !offset.y) { return; }
5930                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5931
5932                 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5933                 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5934                 // and we can use the cached value for the scale.
5935                 offset.x /= this._parentScale.x;
5936                 offset.y /= this._parentScale.y;
5937
5938                 preventDefault(e);
5939
5940                 if (!this._moved) {
5941                         // @event dragstart: Event
5942                         // Fired when a drag starts
5943                         this.fire('dragstart');
5944
5945                         this._moved = true;
5946                         this._startPos = getPosition(this._element).subtract(offset);
5947
5948                         addClass(document.body, 'leaflet-dragging');
5949
5950                         this._lastTarget = e.target || e.srcElement;
5951                         // IE and Edge do not give the <use> element, so fetch it
5952                         // if necessary
5953                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5954                                 this._lastTarget = this._lastTarget.correspondingUseElement;
5955                         }
5956                         addClass(this._lastTarget, 'leaflet-drag-target');
5957                 }
5958
5959                 this._newPos = this._startPos.add(offset);
5960                 this._moving = true;
5961
5962                 cancelAnimFrame(this._animRequest);
5963                 this._lastEvent = e;
5964                 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5965         },
5966
5967         _updatePosition: function () {
5968                 var e = {originalEvent: this._lastEvent};
5969
5970                 // @event predrag: Event
5971                 // Fired continuously during dragging *before* each corresponding
5972                 // update of the element's position.
5973                 this.fire('predrag', e);
5974                 setPosition(this._element, this._newPos);
5975
5976                 // @event drag: Event
5977                 // Fired continuously during dragging.
5978                 this.fire('drag', e);
5979         },
5980
5981         _onUp: function (e) {
5982                 // Ignore simulated events, since we handle both touch and
5983                 // mouse explicitly; otherwise we risk getting duplicates of
5984                 // touch events, see #4315.
5985                 // Also ignore the event if disabled; this happens in IE11
5986                 // under some circumstances, see #3666.
5987                 if (e._simulated || !this._enabled) { return; }
5988                 this.finishDrag();
5989         },
5990
5991         finishDrag: function () {
5992                 removeClass(document.body, 'leaflet-dragging');
5993
5994                 if (this._lastTarget) {
5995                         removeClass(this._lastTarget, 'leaflet-drag-target');
5996                         this._lastTarget = null;
5997                 }
5998
5999                 for (var i in MOVE) {
6000                         off(document, MOVE[i], this._onMove, this);
6001                         off(document, END[i], this._onUp, this);
6002                 }
6003
6004                 enableImageDrag();
6005                 enableTextSelection();
6006
6007                 if (this._moved && this._moving) {
6008                         // ensure drag is not fired after dragend
6009                         cancelAnimFrame(this._animRequest);
6010
6011                         // @event dragend: DragEndEvent
6012                         // Fired when the drag ends.
6013                         this.fire('dragend', {
6014                                 distance: this._newPos.distanceTo(this._startPos)
6015                         });
6016                 }
6017
6018                 this._moving = false;
6019                 Draggable._dragging = false;
6020         }
6021
6022 });
6023
6024 /*
6025  * @namespace LineUtil
6026  *
6027  * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6028  */
6029
6030 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6031 // Improves rendering performance dramatically by lessening the number of points to draw.
6032
6033 // @function simplify(points: Point[], tolerance: Number): Point[]
6034 // Dramatically reduces the number of points in a polyline while retaining
6035 // its shape and returns a new array of simplified points, using the
6036 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6037 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6038 // each zoom level and also reducing visual noise. tolerance affects the amount of
6039 // simplification (lesser value means higher quality but slower and with more points).
6040 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6041 function simplify(points, tolerance) {
6042         if (!tolerance || !points.length) {
6043                 return points.slice();
6044         }
6045
6046         var sqTolerance = tolerance * tolerance;
6047
6048             // stage 1: vertex reduction
6049             points = _reducePoints(points, sqTolerance);
6050
6051             // stage 2: Douglas-Peucker simplification
6052             points = _simplifyDP(points, sqTolerance);
6053
6054         return points;
6055 }
6056
6057 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6058 // Returns the distance between point `p` and segment `p1` to `p2`.
6059 function pointToSegmentDistance(p, p1, p2) {
6060         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6061 }
6062
6063 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6064 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6065 function closestPointOnSegment(p, p1, p2) {
6066         return _sqClosestPointOnSegment(p, p1, p2);
6067 }
6068
6069 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6070 function _simplifyDP(points, sqTolerance) {
6071
6072         var len = points.length,
6073             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6074             markers = new ArrayConstructor(len);
6075
6076             markers[0] = markers[len - 1] = 1;
6077
6078         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6079
6080         var i,
6081             newPoints = [];
6082
6083         for (i = 0; i < len; i++) {
6084                 if (markers[i]) {
6085                         newPoints.push(points[i]);
6086                 }
6087         }
6088
6089         return newPoints;
6090 }
6091
6092 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6093
6094         var maxSqDist = 0,
6095         index, i, sqDist;
6096
6097         for (i = first + 1; i <= last - 1; i++) {
6098                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6099
6100                 if (sqDist > maxSqDist) {
6101                         index = i;
6102                         maxSqDist = sqDist;
6103                 }
6104         }
6105
6106         if (maxSqDist > sqTolerance) {
6107                 markers[index] = 1;
6108
6109                 _simplifyDPStep(points, markers, sqTolerance, first, index);
6110                 _simplifyDPStep(points, markers, sqTolerance, index, last);
6111         }
6112 }
6113
6114 // reduce points that are too close to each other to a single point
6115 function _reducePoints(points, sqTolerance) {
6116         var reducedPoints = [points[0]];
6117
6118         for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6119                 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6120                         reducedPoints.push(points[i]);
6121                         prev = i;
6122                 }
6123         }
6124         if (prev < len - 1) {
6125                 reducedPoints.push(points[len - 1]);
6126         }
6127         return reducedPoints;
6128 }
6129
6130 var _lastCode;
6131
6132 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6133 // Clips the segment a to b by rectangular bounds with the
6134 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6135 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6136 // points that are on the screen or near, increasing performance.
6137 function clipSegment(a, b, bounds, useLastCode, round) {
6138         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6139             codeB = _getBitCode(b, bounds),
6140
6141             codeOut, p, newCode;
6142
6143             // save 2nd code to avoid calculating it on the next segment
6144             _lastCode = codeB;
6145
6146         while (true) {
6147                 // if a,b is inside the clip window (trivial accept)
6148                 if (!(codeA | codeB)) {
6149                         return [a, b];
6150                 }
6151
6152                 // if a,b is outside the clip window (trivial reject)
6153                 if (codeA & codeB) {
6154                         return false;
6155                 }
6156
6157                 // other cases
6158                 codeOut = codeA || codeB;
6159                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6160                 newCode = _getBitCode(p, bounds);
6161
6162                 if (codeOut === codeA) {
6163                         a = p;
6164                         codeA = newCode;
6165                 } else {
6166                         b = p;
6167                         codeB = newCode;
6168                 }
6169         }
6170 }
6171
6172 function _getEdgeIntersection(a, b, code, bounds, round) {
6173         var dx = b.x - a.x,
6174             dy = b.y - a.y,
6175             min = bounds.min,
6176             max = bounds.max,
6177             x, y;
6178
6179         if (code & 8) { // top
6180                 x = a.x + dx * (max.y - a.y) / dy;
6181                 y = max.y;
6182
6183         } else if (code & 4) { // bottom
6184                 x = a.x + dx * (min.y - a.y) / dy;
6185                 y = min.y;
6186
6187         } else if (code & 2) { // right
6188                 x = max.x;
6189                 y = a.y + dy * (max.x - a.x) / dx;
6190
6191         } else if (code & 1) { // left
6192                 x = min.x;
6193                 y = a.y + dy * (min.x - a.x) / dx;
6194         }
6195
6196         return new Point(x, y, round);
6197 }
6198
6199 function _getBitCode(p, bounds) {
6200         var code = 0;
6201
6202         if (p.x < bounds.min.x) { // left
6203                 code |= 1;
6204         } else if (p.x > bounds.max.x) { // right
6205                 code |= 2;
6206         }
6207
6208         if (p.y < bounds.min.y) { // bottom
6209                 code |= 4;
6210         } else if (p.y > bounds.max.y) { // top
6211                 code |= 8;
6212         }
6213
6214         return code;
6215 }
6216
6217 // square distance (to avoid unnecessary Math.sqrt calls)
6218 function _sqDist(p1, p2) {
6219         var dx = p2.x - p1.x,
6220             dy = p2.y - p1.y;
6221         return dx * dx + dy * dy;
6222 }
6223
6224 // return closest point on segment or distance to that point
6225 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6226         var x = p1.x,
6227             y = p1.y,
6228             dx = p2.x - x,
6229             dy = p2.y - y,
6230             dot = dx * dx + dy * dy,
6231             t;
6232
6233         if (dot > 0) {
6234                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6235
6236                 if (t > 1) {
6237                         x = p2.x;
6238                         y = p2.y;
6239                 } else if (t > 0) {
6240                         x += dx * t;
6241                         y += dy * t;
6242                 }
6243         }
6244
6245         dx = p.x - x;
6246         dy = p.y - y;
6247
6248         return sqDist ? dx * dx + dy * dy : new Point(x, y);
6249 }
6250
6251
6252 // @function isFlat(latlngs: LatLng[]): Boolean
6253 // Returns true if `latlngs` is a flat array, false is nested.
6254 function isFlat(latlngs) {
6255         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6256 }
6257
6258 function _flat(latlngs) {
6259         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6260         return isFlat(latlngs);
6261 }
6262
6263
6264 var LineUtil = (Object.freeze || Object)({
6265         simplify: simplify,
6266         pointToSegmentDistance: pointToSegmentDistance,
6267         closestPointOnSegment: closestPointOnSegment,
6268         clipSegment: clipSegment,
6269         _getEdgeIntersection: _getEdgeIntersection,
6270         _getBitCode: _getBitCode,
6271         _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6272         isFlat: isFlat,
6273         _flat: _flat
6274 });
6275
6276 /*
6277  * @namespace PolyUtil
6278  * Various utility functions for polygon geometries.
6279  */
6280
6281 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6282  * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
6283  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6284  * performance. Note that polygon points needs different algorithm for clipping
6285  * than polyline, so there's a separate method for it.
6286  */
6287 function clipPolygon(points, bounds, round) {
6288         var clippedPoints,
6289             edges = [1, 4, 2, 8],
6290             i, j, k,
6291             a, b,
6292             len, edge, p;
6293
6294         for (i = 0, len = points.length; i < len; i++) {
6295                 points[i]._code = _getBitCode(points[i], bounds);
6296         }
6297
6298         // for each edge (left, bottom, right, top)
6299         for (k = 0; k < 4; k++) {
6300                 edge = edges[k];
6301                 clippedPoints = [];
6302
6303                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6304                         a = points[i];
6305                         b = points[j];
6306
6307                         // if a is inside the clip window
6308                         if (!(a._code & edge)) {
6309                                 // if b is outside the clip window (a->b goes out of screen)
6310                                 if (b._code & edge) {
6311                                         p = _getEdgeIntersection(b, a, edge, bounds, round);
6312                                         p._code = _getBitCode(p, bounds);
6313                                         clippedPoints.push(p);
6314                                 }
6315                                 clippedPoints.push(a);
6316
6317                         // else if b is inside the clip window (a->b enters the screen)
6318                         } else if (!(b._code & edge)) {
6319                                 p = _getEdgeIntersection(b, a, edge, bounds, round);
6320                                 p._code = _getBitCode(p, bounds);
6321                                 clippedPoints.push(p);
6322                         }
6323                 }
6324                 points = clippedPoints;
6325         }
6326
6327         return points;
6328 }
6329
6330
6331 var PolyUtil = (Object.freeze || Object)({
6332         clipPolygon: clipPolygon
6333 });
6334
6335 /*
6336  * @namespace Projection
6337  * @section
6338  * Leaflet comes with a set of already defined Projections out of the box:
6339  *
6340  * @projection L.Projection.LonLat
6341  *
6342  * Equirectangular, or Plate Carree projection — the most simple projection,
6343  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6344  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6345  * `EPSG:4326` and `Simple` CRS.
6346  */
6347
6348 var LonLat = {
6349         project: function (latlng) {
6350                 return new Point(latlng.lng, latlng.lat);
6351         },
6352
6353         unproject: function (point) {
6354                 return new LatLng(point.y, point.x);
6355         },
6356
6357         bounds: new Bounds([-180, -90], [180, 90])
6358 };
6359
6360 /*
6361  * @namespace Projection
6362  * @projection L.Projection.Mercator
6363  *
6364  * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6365  */
6366
6367 var Mercator = {
6368         R: 6378137,
6369         R_MINOR: 6356752.314245179,
6370
6371         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6372
6373         project: function (latlng) {
6374                 var d = Math.PI / 180,
6375                     r = this.R,
6376                     y = latlng.lat * d,
6377                     tmp = this.R_MINOR / r,
6378                     e = Math.sqrt(1 - tmp * tmp),
6379                     con = e * Math.sin(y);
6380
6381                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6382                 y = -r * Math.log(Math.max(ts, 1E-10));
6383
6384                 return new Point(latlng.lng * d * r, y);
6385         },
6386
6387         unproject: function (point) {
6388                 var d = 180 / Math.PI,
6389                     r = this.R,
6390                     tmp = this.R_MINOR / r,
6391                     e = Math.sqrt(1 - tmp * tmp),
6392                     ts = Math.exp(-point.y / r),
6393                     phi = Math.PI / 2 - 2 * Math.atan(ts);
6394
6395                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6396                         con = e * Math.sin(phi);
6397                         con = Math.pow((1 - con) / (1 + con), e / 2);
6398                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6399                         phi += dphi;
6400                 }
6401
6402                 return new LatLng(phi * d, point.x * d / r);
6403         }
6404 };
6405
6406 /*
6407  * @class Projection
6408
6409  * An object with methods for projecting geographical coordinates of the world onto
6410  * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6411
6412  * @property bounds: Bounds
6413  * The bounds (specified in CRS units) where the projection is valid
6414
6415  * @method project(latlng: LatLng): Point
6416  * Projects geographical coordinates into a 2D point.
6417  * Only accepts actual `L.LatLng` instances, not arrays.
6418
6419  * @method unproject(point: Point): LatLng
6420  * The inverse of `project`. Projects a 2D point into a geographical location.
6421  * Only accepts actual `L.Point` instances, not arrays.
6422
6423  * Note that the projection instances do not inherit from Leafet's `Class` object,
6424  * and can't be instantiated. Also, new classes can't inherit from them,
6425  * and methods can't be added to them with the `include` function.
6426
6427  */
6428
6429
6430
6431
6432 var index = (Object.freeze || Object)({
6433         LonLat: LonLat,
6434         Mercator: Mercator,
6435         SphericalMercator: SphericalMercator
6436 });
6437
6438 /*
6439  * @namespace CRS
6440  * @crs L.CRS.EPSG3395
6441  *
6442  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6443  */
6444 var EPSG3395 = extend({}, Earth, {
6445         code: 'EPSG:3395',
6446         projection: Mercator,
6447
6448         transformation: (function () {
6449                 var scale = 0.5 / (Math.PI * Mercator.R);
6450                 return toTransformation(scale, 0.5, -scale, 0.5);
6451         }())
6452 });
6453
6454 /*
6455  * @namespace CRS
6456  * @crs L.CRS.EPSG4326
6457  *
6458  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6459  *
6460  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6461  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
6462  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6463  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6464  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6465  */
6466
6467 var EPSG4326 = extend({}, Earth, {
6468         code: 'EPSG:4326',
6469         projection: LonLat,
6470         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6471 });
6472
6473 /*
6474  * @namespace CRS
6475  * @crs L.CRS.Simple
6476  *
6477  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6478  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6479  * axis should still be inverted (going from bottom to top). `distance()` returns
6480  * simple euclidean distance.
6481  */
6482
6483 var Simple = extend({}, CRS, {
6484         projection: LonLat,
6485         transformation: toTransformation(1, 0, -1, 0),
6486
6487         scale: function (zoom) {
6488                 return Math.pow(2, zoom);
6489         },
6490
6491         zoom: function (scale) {
6492                 return Math.log(scale) / Math.LN2;
6493         },
6494
6495         distance: function (latlng1, latlng2) {
6496                 var dx = latlng2.lng - latlng1.lng,
6497                     dy = latlng2.lat - latlng1.lat;
6498
6499                 return Math.sqrt(dx * dx + dy * dy);
6500         },
6501
6502         infinite: true
6503 });
6504
6505 CRS.Earth = Earth;
6506 CRS.EPSG3395 = EPSG3395;
6507 CRS.EPSG3857 = EPSG3857;
6508 CRS.EPSG900913 = EPSG900913;
6509 CRS.EPSG4326 = EPSG4326;
6510 CRS.Simple = Simple;
6511
6512 /*
6513  * @class Layer
6514  * @inherits Evented
6515  * @aka L.Layer
6516  * @aka ILayer
6517  *
6518  * A set of methods from the Layer base class that all Leaflet layers use.
6519  * Inherits all methods, options and events from `L.Evented`.
6520  *
6521  * @example
6522  *
6523  * ```js
6524  * var layer = L.marker(latlng).addTo(map);
6525  * layer.addTo(map);
6526  * layer.remove();
6527  * ```
6528  *
6529  * @event add: Event
6530  * Fired after the layer is added to a map
6531  *
6532  * @event remove: Event
6533  * Fired after the layer is removed from a map
6534  */
6535
6536
6537 var Layer = Evented.extend({
6538
6539         // Classes extending `L.Layer` will inherit the following options:
6540         options: {
6541                 // @option pane: String = 'overlayPane'
6542                 // 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.
6543                 pane: 'overlayPane',
6544
6545                 // @option attribution: String = null
6546                 // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
6547                 attribution: null,
6548
6549                 bubblingMouseEvents: true
6550         },
6551
6552         /* @section
6553          * Classes extending `L.Layer` will inherit the following methods:
6554          *
6555          * @method addTo(map: Map|LayerGroup): this
6556          * Adds the layer to the given map or layer group.
6557          */
6558         addTo: function (map) {
6559                 map.addLayer(this);
6560                 return this;
6561         },
6562
6563         // @method remove: this
6564         // Removes the layer from the map it is currently active on.
6565         remove: function () {
6566                 return this.removeFrom(this._map || this._mapToAdd);
6567         },
6568
6569         // @method removeFrom(map: Map): this
6570         // Removes the layer from the given map
6571         removeFrom: function (obj) {
6572                 if (obj) {
6573                         obj.removeLayer(this);
6574                 }
6575                 return this;
6576         },
6577
6578         // @method getPane(name? : String): HTMLElement
6579         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6580         getPane: function (name) {
6581                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6582         },
6583
6584         addInteractiveTarget: function (targetEl) {
6585                 this._map._targets[stamp(targetEl)] = this;
6586                 return this;
6587         },
6588
6589         removeInteractiveTarget: function (targetEl) {
6590                 delete this._map._targets[stamp(targetEl)];
6591                 return this;
6592         },
6593
6594         // @method getAttribution: String
6595         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6596         getAttribution: function () {
6597                 return this.options.attribution;
6598         },
6599
6600         _layerAdd: function (e) {
6601                 var map = e.target;
6602
6603                 // check in case layer gets added and then removed before the map is ready
6604                 if (!map.hasLayer(this)) { return; }
6605
6606                 this._map = map;
6607                 this._zoomAnimated = map._zoomAnimated;
6608
6609                 if (this.getEvents) {
6610                         var events = this.getEvents();
6611                         map.on(events, this);
6612                         this.once('remove', function () {
6613                                 map.off(events, this);
6614                         }, this);
6615                 }
6616
6617                 this.onAdd(map);
6618
6619                 if (this.getAttribution && map.attributionControl) {
6620                         map.attributionControl.addAttribution(this.getAttribution());
6621                 }
6622
6623                 this.fire('add');
6624                 map.fire('layeradd', {layer: this});
6625         }
6626 });
6627
6628 /* @section Extension methods
6629  * @uninheritable
6630  *
6631  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6632  *
6633  * @method onAdd(map: Map): this
6634  * 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).
6635  *
6636  * @method onRemove(map: Map): this
6637  * 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).
6638  *
6639  * @method getEvents(): Object
6640  * 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.
6641  *
6642  * @method getAttribution(): String
6643  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6644  *
6645  * @method beforeAdd(map: Map): this
6646  * 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.
6647  */
6648
6649
6650 /* @namespace Map
6651  * @section Layer events
6652  *
6653  * @event layeradd: LayerEvent
6654  * Fired when a new layer is added to the map.
6655  *
6656  * @event layerremove: LayerEvent
6657  * Fired when some layer is removed from the map
6658  *
6659  * @section Methods for Layers and Controls
6660  */
6661 Map.include({
6662         // @method addLayer(layer: Layer): this
6663         // Adds the given layer to the map
6664         addLayer: function (layer) {
6665                 if (!layer._layerAdd) {
6666                         throw new Error('The provided object is not a Layer.');
6667                 }
6668
6669                 var id = stamp(layer);
6670                 if (this._layers[id]) { return this; }
6671                 this._layers[id] = layer;
6672
6673                 layer._mapToAdd = this;
6674
6675                 if (layer.beforeAdd) {
6676                         layer.beforeAdd(this);
6677                 }
6678
6679                 this.whenReady(layer._layerAdd, layer);
6680
6681                 return this;
6682         },
6683
6684         // @method removeLayer(layer: Layer): this
6685         // Removes the given layer from the map.
6686         removeLayer: function (layer) {
6687                 var id = stamp(layer);
6688
6689                 if (!this._layers[id]) { return this; }
6690
6691                 if (this._loaded) {
6692                         layer.onRemove(this);
6693                 }
6694
6695                 if (layer.getAttribution && this.attributionControl) {
6696                         this.attributionControl.removeAttribution(layer.getAttribution());
6697                 }
6698
6699                 delete this._layers[id];
6700
6701                 if (this._loaded) {
6702                         this.fire('layerremove', {layer: layer});
6703                         layer.fire('remove');
6704                 }
6705
6706                 layer._map = layer._mapToAdd = null;
6707
6708                 return this;
6709         },
6710
6711         // @method hasLayer(layer: Layer): Boolean
6712         // Returns `true` if the given layer is currently added to the map
6713         hasLayer: function (layer) {
6714                 return !!layer && (stamp(layer) in this._layers);
6715         },
6716
6717         /* @method eachLayer(fn: Function, context?: Object): this
6718          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6719          * ```
6720          * map.eachLayer(function(layer){
6721          *     layer.bindPopup('Hello');
6722          * });
6723          * ```
6724          */
6725         eachLayer: function (method, context) {
6726                 for (var i in this._layers) {
6727                         method.call(context, this._layers[i]);
6728                 }
6729                 return this;
6730         },
6731
6732         _addLayers: function (layers) {
6733                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6734
6735                 for (var i = 0, len = layers.length; i < len; i++) {
6736                         this.addLayer(layers[i]);
6737                 }
6738         },
6739
6740         _addZoomLimit: function (layer) {
6741                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6742                         this._zoomBoundLayers[stamp(layer)] = layer;
6743                         this._updateZoomLevels();
6744                 }
6745         },
6746
6747         _removeZoomLimit: function (layer) {
6748                 var id = stamp(layer);
6749
6750                 if (this._zoomBoundLayers[id]) {
6751                         delete this._zoomBoundLayers[id];
6752                         this._updateZoomLevels();
6753                 }
6754         },
6755
6756         _updateZoomLevels: function () {
6757                 var minZoom = Infinity,
6758                     maxZoom = -Infinity,
6759                     oldZoomSpan = this._getZoomSpan();
6760
6761                 for (var i in this._zoomBoundLayers) {
6762                         var options = this._zoomBoundLayers[i].options;
6763
6764                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6765                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6766                 }
6767
6768                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6769                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6770
6771                 // @section Map state change events
6772                 // @event zoomlevelschange: Event
6773                 // Fired when the number of zoomlevels on the map is changed due
6774                 // to adding or removing a layer.
6775                 if (oldZoomSpan !== this._getZoomSpan()) {
6776                         this.fire('zoomlevelschange');
6777                 }
6778
6779                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6780                         this.setZoom(this._layersMaxZoom);
6781                 }
6782                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6783                         this.setZoom(this._layersMinZoom);
6784                 }
6785         }
6786 });
6787
6788 /*
6789  * @class LayerGroup
6790  * @aka L.LayerGroup
6791  * @inherits Layer
6792  *
6793  * Used to group several layers and handle them as one. If you add it to the map,
6794  * any layers added or removed from the group will be added/removed on the map as
6795  * well. Extends `Layer`.
6796  *
6797  * @example
6798  *
6799  * ```js
6800  * L.layerGroup([marker1, marker2])
6801  *      .addLayer(polyline)
6802  *      .addTo(map);
6803  * ```
6804  */
6805
6806 var LayerGroup = Layer.extend({
6807
6808         initialize: function (layers, options) {
6809                 setOptions(this, options);
6810
6811                 this._layers = {};
6812
6813                 var i, len;
6814
6815                 if (layers) {
6816                         for (i = 0, len = layers.length; i < len; i++) {
6817                                 this.addLayer(layers[i]);
6818                         }
6819                 }
6820         },
6821
6822         // @method addLayer(layer: Layer): this
6823         // Adds the given layer to the group.
6824         addLayer: function (layer) {
6825                 var id = this.getLayerId(layer);
6826
6827                 this._layers[id] = layer;
6828
6829                 if (this._map) {
6830                         this._map.addLayer(layer);
6831                 }
6832
6833                 return this;
6834         },
6835
6836         // @method removeLayer(layer: Layer): this
6837         // Removes the given layer from the group.
6838         // @alternative
6839         // @method removeLayer(id: Number): this
6840         // Removes the layer with the given internal ID from the group.
6841         removeLayer: function (layer) {
6842                 var id = layer in this._layers ? layer : this.getLayerId(layer);
6843
6844                 if (this._map && this._layers[id]) {
6845                         this._map.removeLayer(this._layers[id]);
6846                 }
6847
6848                 delete this._layers[id];
6849
6850                 return this;
6851         },
6852
6853         // @method hasLayer(layer: Layer): Boolean
6854         // Returns `true` if the given layer is currently added to the group.
6855         // @alternative
6856         // @method hasLayer(id: Number): Boolean
6857         // Returns `true` if the given internal ID is currently added to the group.
6858         hasLayer: function (layer) {
6859                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6860         },
6861
6862         // @method clearLayers(): this
6863         // Removes all the layers from the group.
6864         clearLayers: function () {
6865                 return this.eachLayer(this.removeLayer, this);
6866         },
6867
6868         // @method invoke(methodName: String, …): this
6869         // Calls `methodName` on every layer contained in this group, passing any
6870         // additional parameters. Has no effect if the layers contained do not
6871         // implement `methodName`.
6872         invoke: function (methodName) {
6873                 var args = Array.prototype.slice.call(arguments, 1),
6874                     i, layer;
6875
6876                 for (i in this._layers) {
6877                         layer = this._layers[i];
6878
6879                         if (layer[methodName]) {
6880                                 layer[methodName].apply(layer, args);
6881                         }
6882                 }
6883
6884                 return this;
6885         },
6886
6887         onAdd: function (map) {
6888                 this.eachLayer(map.addLayer, map);
6889         },
6890
6891         onRemove: function (map) {
6892                 this.eachLayer(map.removeLayer, map);
6893         },
6894
6895         // @method eachLayer(fn: Function, context?: Object): this
6896         // Iterates over the layers of the group, optionally specifying context of the iterator function.
6897         // ```js
6898         // group.eachLayer(function (layer) {
6899         //      layer.bindPopup('Hello');
6900         // });
6901         // ```
6902         eachLayer: function (method, context) {
6903                 for (var i in this._layers) {
6904                         method.call(context, this._layers[i]);
6905                 }
6906                 return this;
6907         },
6908
6909         // @method getLayer(id: Number): Layer
6910         // Returns the layer with the given internal ID.
6911         getLayer: function (id) {
6912                 return this._layers[id];
6913         },
6914
6915         // @method getLayers(): Layer[]
6916         // Returns an array of all the layers added to the group.
6917         getLayers: function () {
6918                 var layers = [];
6919                 this.eachLayer(layers.push, layers);
6920                 return layers;
6921         },
6922
6923         // @method setZIndex(zIndex: Number): this
6924         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6925         setZIndex: function (zIndex) {
6926                 return this.invoke('setZIndex', zIndex);
6927         },
6928
6929         // @method getLayerId(layer: Layer): Number
6930         // Returns the internal ID for a layer
6931         getLayerId: function (layer) {
6932                 return stamp(layer);
6933         }
6934 });
6935
6936
6937 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6938 // Create a layer group, optionally given an initial set of layers and an `options` object.
6939 var layerGroup = function (layers, options) {
6940         return new LayerGroup(layers, options);
6941 };
6942
6943 /*
6944  * @class FeatureGroup
6945  * @aka L.FeatureGroup
6946  * @inherits LayerGroup
6947  *
6948  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6949  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6950  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
6951  * handler, it will handle events from any of the layers. This includes mouse events
6952  * and custom events.
6953  *  * Has `layeradd` and `layerremove` events
6954  *
6955  * @example
6956  *
6957  * ```js
6958  * L.featureGroup([marker1, marker2, polyline])
6959  *      .bindPopup('Hello world!')
6960  *      .on('click', function() { alert('Clicked on a member of the group!'); })
6961  *      .addTo(map);
6962  * ```
6963  */
6964
6965 var FeatureGroup = LayerGroup.extend({
6966
6967         addLayer: function (layer) {
6968                 if (this.hasLayer(layer)) {
6969                         return this;
6970                 }
6971
6972                 layer.addEventParent(this);
6973
6974                 LayerGroup.prototype.addLayer.call(this, layer);
6975
6976                 // @event layeradd: LayerEvent
6977                 // Fired when a layer is added to this `FeatureGroup`
6978                 return this.fire('layeradd', {layer: layer});
6979         },
6980
6981         removeLayer: function (layer) {
6982                 if (!this.hasLayer(layer)) {
6983                         return this;
6984                 }
6985                 if (layer in this._layers) {
6986                         layer = this._layers[layer];
6987                 }
6988
6989                 layer.removeEventParent(this);
6990
6991                 LayerGroup.prototype.removeLayer.call(this, layer);
6992
6993                 // @event layerremove: LayerEvent
6994                 // Fired when a layer is removed from this `FeatureGroup`
6995                 return this.fire('layerremove', {layer: layer});
6996         },
6997
6998         // @method setStyle(style: Path options): this
6999         // Sets the given path options to each layer of the group that has a `setStyle` method.
7000         setStyle: function (style) {
7001                 return this.invoke('setStyle', style);
7002         },
7003
7004         // @method bringToFront(): this
7005         // Brings the layer group to the top of all other layers
7006         bringToFront: function () {
7007                 return this.invoke('bringToFront');
7008         },
7009
7010         // @method bringToBack(): this
7011         // Brings the layer group to the back of all other layers
7012         bringToBack: function () {
7013                 return this.invoke('bringToBack');
7014         },
7015
7016         // @method getBounds(): LatLngBounds
7017         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7018         getBounds: function () {
7019                 var bounds = new LatLngBounds();
7020
7021                 for (var id in this._layers) {
7022                         var layer = this._layers[id];
7023                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7024                 }
7025                 return bounds;
7026         }
7027 });
7028
7029 // @factory L.featureGroup(layers: Layer[])
7030 // Create a feature group, optionally given an initial set of layers.
7031 var featureGroup = function (layers) {
7032         return new FeatureGroup(layers);
7033 };
7034
7035 /*
7036  * @class Icon
7037  * @aka L.Icon
7038  *
7039  * Represents an icon to provide when creating a marker.
7040  *
7041  * @example
7042  *
7043  * ```js
7044  * var myIcon = L.icon({
7045  *     iconUrl: 'my-icon.png',
7046  *     iconRetinaUrl: 'my-icon@2x.png',
7047  *     iconSize: [38, 95],
7048  *     iconAnchor: [22, 94],
7049  *     popupAnchor: [-3, -76],
7050  *     shadowUrl: 'my-icon-shadow.png',
7051  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
7052  *     shadowSize: [68, 95],
7053  *     shadowAnchor: [22, 94]
7054  * });
7055  *
7056  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7057  * ```
7058  *
7059  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7060  *
7061  */
7062
7063 var Icon = Class.extend({
7064
7065         /* @section
7066          * @aka Icon options
7067          *
7068          * @option iconUrl: String = null
7069          * **(required)** The URL to the icon image (absolute or relative to your script path).
7070          *
7071          * @option iconRetinaUrl: String = null
7072          * The URL to a retina sized version of the icon image (absolute or relative to your
7073          * script path). Used for Retina screen devices.
7074          *
7075          * @option iconSize: Point = null
7076          * Size of the icon image in pixels.
7077          *
7078          * @option iconAnchor: Point = null
7079          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7080          * will be aligned so that this point is at the marker's geographical location. Centered
7081          * by default if size is specified, also can be set in CSS with negative margins.
7082          *
7083          * @option popupAnchor: Point = [0, 0]
7084          * The coordinates of the point from which popups will "open", relative to the icon anchor.
7085          *
7086          * @option tooltipAnchor: Point = [0, 0]
7087          * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7088          *
7089          * @option shadowUrl: String = null
7090          * The URL to the icon shadow image. If not specified, no shadow image will be created.
7091          *
7092          * @option shadowRetinaUrl: String = null
7093          *
7094          * @option shadowSize: Point = null
7095          * Size of the shadow image in pixels.
7096          *
7097          * @option shadowAnchor: Point = null
7098          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7099          * as iconAnchor if not specified).
7100          *
7101          * @option className: String = ''
7102          * A custom class name to assign to both icon and shadow images. Empty by default.
7103          */
7104
7105         options: {
7106                 popupAnchor: [0, 0],
7107                 tooltipAnchor: [0, 0]
7108         },
7109
7110         initialize: function (options) {
7111                 setOptions(this, options);
7112         },
7113
7114         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7115         // Called internally when the icon has to be shown, returns a `<img>` HTML element
7116         // styled according to the options.
7117         createIcon: function (oldIcon) {
7118                 return this._createIcon('icon', oldIcon);
7119         },
7120
7121         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7122         // As `createIcon`, but for the shadow beneath it.
7123         createShadow: function (oldIcon) {
7124                 return this._createIcon('shadow', oldIcon);
7125         },
7126
7127         _createIcon: function (name, oldIcon) {
7128                 var src = this._getIconUrl(name);
7129
7130                 if (!src) {
7131                         if (name === 'icon') {
7132                                 throw new Error('iconUrl not set in Icon options (see the docs).');
7133                         }
7134                         return null;
7135                 }
7136
7137                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7138                 this._setIconStyles(img, name);
7139
7140                 return img;
7141         },
7142
7143         _setIconStyles: function (img, name) {
7144                 var options = this.options;
7145                 var sizeOption = options[name + 'Size'];
7146
7147                 if (typeof sizeOption === 'number') {
7148                         sizeOption = [sizeOption, sizeOption];
7149                 }
7150
7151                 var size = toPoint(sizeOption),
7152                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7153                             size && size.divideBy(2, true));
7154
7155                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7156
7157                 if (anchor) {
7158                         img.style.marginLeft = (-anchor.x) + 'px';
7159                         img.style.marginTop  = (-anchor.y) + 'px';
7160                 }
7161
7162                 if (size) {
7163                         img.style.width  = size.x + 'px';
7164                         img.style.height = size.y + 'px';
7165                 }
7166         },
7167
7168         _createImg: function (src, el) {
7169                 el = el || document.createElement('img');
7170                 el.src = src;
7171                 return el;
7172         },
7173
7174         _getIconUrl: function (name) {
7175                 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7176         }
7177 });
7178
7179
7180 // @factory L.icon(options: Icon options)
7181 // Creates an icon instance with the given options.
7182 function icon(options) {
7183         return new Icon(options);
7184 }
7185
7186 /*
7187  * @miniclass Icon.Default (Icon)
7188  * @aka L.Icon.Default
7189  * @section
7190  *
7191  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7192  * no icon is specified. Points to the blue marker image distributed with Leaflet
7193  * releases.
7194  *
7195  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7196  * (which is a set of `Icon options`).
7197  *
7198  * If you want to _completely_ replace the default icon, override the
7199  * `L.Marker.prototype.options.icon` with your own icon instead.
7200  */
7201
7202 var IconDefault = Icon.extend({
7203
7204         options: {
7205                 iconUrl:       'marker-icon.png',
7206                 iconRetinaUrl: 'marker-icon-2x.png',
7207                 shadowUrl:     'marker-shadow.png',
7208                 iconSize:    [25, 41],
7209                 iconAnchor:  [12, 41],
7210                 popupAnchor: [1, -34],
7211                 tooltipAnchor: [16, -28],
7212                 shadowSize:  [41, 41]
7213         },
7214
7215         _getIconUrl: function (name) {
7216                 if (!IconDefault.imagePath) {   // Deprecated, backwards-compatibility only
7217                         IconDefault.imagePath = this._detectIconPath();
7218                 }
7219
7220                 // @option imagePath: String
7221                 // `Icon.Default` will try to auto-detect the location of the
7222                 // blue icon images. If you are placing these images in a non-standard
7223                 // way, set this option to point to the right path.
7224                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7225         },
7226
7227         _detectIconPath: function () {
7228                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7229                 var path = getStyle(el, 'background-image') ||
7230                            getStyle(el, 'backgroundImage');     // IE8
7231
7232                 document.body.removeChild(el);
7233
7234                 if (path === null || path.indexOf('url') !== 0) {
7235                         path = '';
7236                 } else {
7237                         path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7238                 }
7239
7240                 return path;
7241         }
7242 });
7243
7244 /*
7245  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7246  */
7247
7248
7249 /* @namespace Marker
7250  * @section Interaction handlers
7251  *
7252  * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
7253  *
7254  * ```js
7255  * marker.dragging.disable();
7256  * ```
7257  *
7258  * @property dragging: Handler
7259  * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7260  */
7261
7262 var MarkerDrag = Handler.extend({
7263         initialize: function (marker) {
7264                 this._marker = marker;
7265         },
7266
7267         addHooks: function () {
7268                 var icon = this._marker._icon;
7269
7270                 if (!this._draggable) {
7271                         this._draggable = new Draggable(icon, icon, true);
7272                 }
7273
7274                 this._draggable.on({
7275                         dragstart: this._onDragStart,
7276                         predrag: this._onPreDrag,
7277                         drag: this._onDrag,
7278                         dragend: this._onDragEnd
7279                 }, this).enable();
7280
7281                 addClass(icon, 'leaflet-marker-draggable');
7282         },
7283
7284         removeHooks: function () {
7285                 this._draggable.off({
7286                         dragstart: this._onDragStart,
7287                         predrag: this._onPreDrag,
7288                         drag: this._onDrag,
7289                         dragend: this._onDragEnd
7290                 }, this).disable();
7291
7292                 if (this._marker._icon) {
7293                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7294                 }
7295         },
7296
7297         moved: function () {
7298                 return this._draggable && this._draggable._moved;
7299         },
7300
7301         _adjustPan: function (e) {
7302                 var marker = this._marker,
7303                     map = marker._map,
7304                     speed = this._marker.options.autoPanSpeed,
7305                     padding = this._marker.options.autoPanPadding,
7306                     iconPos = getPosition(marker._icon),
7307                     bounds = map.getPixelBounds(),
7308                     origin = map.getPixelOrigin();
7309
7310                 var panBounds = toBounds(
7311                         bounds.min._subtract(origin).add(padding),
7312                         bounds.max._subtract(origin).subtract(padding)
7313                 );
7314
7315                 if (!panBounds.contains(iconPos)) {
7316                         // Compute incremental movement
7317                         var movement = toPoint(
7318                                 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7319                                 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7320
7321                                 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7322                                 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7323                         ).multiplyBy(speed);
7324
7325                         map.panBy(movement, {animate: false});
7326
7327                         this._draggable._newPos._add(movement);
7328                         this._draggable._startPos._add(movement);
7329
7330                         setPosition(marker._icon, this._draggable._newPos);
7331                         this._onDrag(e);
7332
7333                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7334                 }
7335         },
7336
7337         _onDragStart: function () {
7338                 // @section Dragging events
7339                 // @event dragstart: Event
7340                 // Fired when the user starts dragging the marker.
7341
7342                 // @event movestart: Event
7343                 // Fired when the marker starts moving (because of dragging).
7344
7345                 this._oldLatLng = this._marker.getLatLng();
7346                 this._marker
7347                     .closePopup()
7348                     .fire('movestart')
7349                     .fire('dragstart');
7350         },
7351
7352         _onPreDrag: function (e) {
7353                 if (this._marker.options.autoPan) {
7354                         cancelAnimFrame(this._panRequest);
7355                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7356                 }
7357         },
7358
7359         _onDrag: function (e) {
7360                 var marker = this._marker,
7361                     shadow = marker._shadow,
7362                     iconPos = getPosition(marker._icon),
7363                     latlng = marker._map.layerPointToLatLng(iconPos);
7364
7365                 // update shadow position
7366                 if (shadow) {
7367                         setPosition(shadow, iconPos);
7368                 }
7369
7370                 marker._latlng = latlng;
7371                 e.latlng = latlng;
7372                 e.oldLatLng = this._oldLatLng;
7373
7374                 // @event drag: Event
7375                 // Fired repeatedly while the user drags the marker.
7376                 marker
7377                     .fire('move', e)
7378                     .fire('drag', e);
7379         },
7380
7381         _onDragEnd: function (e) {
7382                 // @event dragend: DragEndEvent
7383                 // Fired when the user stops dragging the marker.
7384
7385                  cancelAnimFrame(this._panRequest);
7386
7387                 // @event moveend: Event
7388                 // Fired when the marker stops moving (because of dragging).
7389                 delete this._oldLatLng;
7390                 this._marker
7391                     .fire('moveend')
7392                     .fire('dragend', e);
7393         }
7394 });
7395
7396 /*
7397  * @class Marker
7398  * @inherits Interactive layer
7399  * @aka L.Marker
7400  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7401  *
7402  * @example
7403  *
7404  * ```js
7405  * L.marker([50.5, 30.5]).addTo(map);
7406  * ```
7407  */
7408
7409 var Marker = Layer.extend({
7410
7411         // @section
7412         // @aka Marker options
7413         options: {
7414                 // @option icon: Icon = *
7415                 // Icon instance to use for rendering the marker.
7416                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7417                 // If not specified, a common instance of `L.Icon.Default` is used.
7418                 icon: new IconDefault(),
7419
7420                 // Option inherited from "Interactive layer" abstract class
7421                 interactive: true,
7422
7423                 // @option keyboard: Boolean = true
7424                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7425                 keyboard: true,
7426
7427                 // @option title: String = ''
7428                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7429                 title: '',
7430
7431                 // @option alt: String = ''
7432                 // Text for the `alt` attribute of the icon image (useful for accessibility).
7433                 alt: '',
7434
7435                 // @option zIndexOffset: Number = 0
7436                 // 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).
7437                 zIndexOffset: 0,
7438
7439                 // @option opacity: Number = 1.0
7440                 // The opacity of the marker.
7441                 opacity: 1,
7442
7443                 // @option riseOnHover: Boolean = false
7444                 // If `true`, the marker will get on top of others when you hover the mouse over it.
7445                 riseOnHover: false,
7446
7447                 // @option riseOffset: Number = 250
7448                 // The z-index offset used for the `riseOnHover` feature.
7449                 riseOffset: 250,
7450
7451                 // @option pane: String = 'markerPane'
7452                 // `Map pane` where the markers icon will be added.
7453                 pane: 'markerPane',
7454
7455                 // @option pane: String = 'shadowPane'
7456                 // `Map pane` where the markers shadow will be added.
7457                 shadowPane: 'shadowPane',
7458
7459                 // @option bubblingMouseEvents: Boolean = false
7460                 // When `true`, a mouse event on this marker will trigger the same event on the map
7461                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7462                 bubblingMouseEvents: false,
7463
7464                 // @section Draggable marker options
7465                 // @option draggable: Boolean = false
7466                 // Whether the marker is draggable with mouse/touch or not.
7467                 draggable: false,
7468
7469                 // @option autoPan: Boolean = false
7470                 // Whether to pan the map when dragging this marker near its edge or not.
7471                 autoPan: false,
7472
7473                 // @option autoPanPadding: Point = Point(50, 50)
7474                 // Distance (in pixels to the left/right and to the top/bottom) of the
7475                 // map edge to start panning the map.
7476                 autoPanPadding: [50, 50],
7477
7478                 // @option autoPanSpeed: Number = 10
7479                 // Number of pixels the map should pan by.
7480                 autoPanSpeed: 10
7481         },
7482
7483         /* @section
7484          *
7485          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7486          */
7487
7488         initialize: function (latlng, options) {
7489                 setOptions(this, options);
7490                 this._latlng = toLatLng(latlng);
7491         },
7492
7493         onAdd: function (map) {
7494                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7495
7496                 if (this._zoomAnimated) {
7497                         map.on('zoomanim', this._animateZoom, this);
7498                 }
7499
7500                 this._initIcon();
7501                 this.update();
7502         },
7503
7504         onRemove: function (map) {
7505                 if (this.dragging && this.dragging.enabled()) {
7506                         this.options.draggable = true;
7507                         this.dragging.removeHooks();
7508                 }
7509                 delete this.dragging;
7510
7511                 if (this._zoomAnimated) {
7512                         map.off('zoomanim', this._animateZoom, this);
7513                 }
7514
7515                 this._removeIcon();
7516                 this._removeShadow();
7517         },
7518
7519         getEvents: function () {
7520                 return {
7521                         zoom: this.update,
7522                         viewreset: this.update
7523                 };
7524         },
7525
7526         // @method getLatLng: LatLng
7527         // Returns the current geographical position of the marker.
7528         getLatLng: function () {
7529                 return this._latlng;
7530         },
7531
7532         // @method setLatLng(latlng: LatLng): this
7533         // Changes the marker position to the given point.
7534         setLatLng: function (latlng) {
7535                 var oldLatLng = this._latlng;
7536                 this._latlng = toLatLng(latlng);
7537                 this.update();
7538
7539                 // @event move: Event
7540                 // 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`.
7541                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7542         },
7543
7544         // @method setZIndexOffset(offset: Number): this
7545         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7546         setZIndexOffset: function (offset) {
7547                 this.options.zIndexOffset = offset;
7548                 return this.update();
7549         },
7550
7551         // @method getIcon: Icon
7552         // Returns the current icon used by the marker
7553         getIcon: function () {
7554                 return this.options.icon;
7555         },
7556
7557         // @method setIcon(icon: Icon): this
7558         // Changes the marker icon.
7559         setIcon: function (icon) {
7560
7561                 this.options.icon = icon;
7562
7563                 if (this._map) {
7564                         this._initIcon();
7565                         this.update();
7566                 }
7567
7568                 if (this._popup) {
7569                         this.bindPopup(this._popup, this._popup.options);
7570                 }
7571
7572                 return this;
7573         },
7574
7575         getElement: function () {
7576                 return this._icon;
7577         },
7578
7579         update: function () {
7580
7581                 if (this._icon && this._map) {
7582                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
7583                         this._setPos(pos);
7584                 }
7585
7586                 return this;
7587         },
7588
7589         _initIcon: function () {
7590                 var options = this.options,
7591                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7592
7593                 var icon = options.icon.createIcon(this._icon),
7594                     addIcon = false;
7595
7596                 // if we're not reusing the icon, remove the old one and init new one
7597                 if (icon !== this._icon) {
7598                         if (this._icon) {
7599                                 this._removeIcon();
7600                         }
7601                         addIcon = true;
7602
7603                         if (options.title) {
7604                                 icon.title = options.title;
7605                         }
7606
7607                         if (icon.tagName === 'IMG') {
7608                                 icon.alt = options.alt || '';
7609                         }
7610                 }
7611
7612                 addClass(icon, classToAdd);
7613
7614                 if (options.keyboard) {
7615                         icon.tabIndex = '0';
7616                 }
7617
7618                 this._icon = icon;
7619
7620                 if (options.riseOnHover) {
7621                         this.on({
7622                                 mouseover: this._bringToFront,
7623                                 mouseout: this._resetZIndex
7624                         });
7625                 }
7626
7627                 var newShadow = options.icon.createShadow(this._shadow),
7628                     addShadow = false;
7629
7630                 if (newShadow !== this._shadow) {
7631                         this._removeShadow();
7632                         addShadow = true;
7633                 }
7634
7635                 if (newShadow) {
7636                         addClass(newShadow, classToAdd);
7637                         newShadow.alt = '';
7638                 }
7639                 this._shadow = newShadow;
7640
7641
7642                 if (options.opacity < 1) {
7643                         this._updateOpacity();
7644                 }
7645
7646
7647                 if (addIcon) {
7648                         this.getPane().appendChild(this._icon);
7649                 }
7650                 this._initInteraction();
7651                 if (newShadow && addShadow) {
7652                         this.getPane(options.shadowPane).appendChild(this._shadow);
7653                 }
7654         },
7655
7656         _removeIcon: function () {
7657                 if (this.options.riseOnHover) {
7658                         this.off({
7659                                 mouseover: this._bringToFront,
7660                                 mouseout: this._resetZIndex
7661                         });
7662                 }
7663
7664                 remove(this._icon);
7665                 this.removeInteractiveTarget(this._icon);
7666
7667                 this._icon = null;
7668         },
7669
7670         _removeShadow: function () {
7671                 if (this._shadow) {
7672                         remove(this._shadow);
7673                 }
7674                 this._shadow = null;
7675         },
7676
7677         _setPos: function (pos) {
7678
7679                 if (this._icon) {
7680                         setPosition(this._icon, pos);
7681                 }
7682
7683                 if (this._shadow) {
7684                         setPosition(this._shadow, pos);
7685                 }
7686
7687                 this._zIndex = pos.y + this.options.zIndexOffset;
7688
7689                 this._resetZIndex();
7690         },
7691
7692         _updateZIndex: function (offset) {
7693                 if (this._icon) {
7694                         this._icon.style.zIndex = this._zIndex + offset;
7695                 }
7696         },
7697
7698         _animateZoom: function (opt) {
7699                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7700
7701                 this._setPos(pos);
7702         },
7703
7704         _initInteraction: function () {
7705
7706                 if (!this.options.interactive) { return; }
7707
7708                 addClass(this._icon, 'leaflet-interactive');
7709
7710                 this.addInteractiveTarget(this._icon);
7711
7712                 if (MarkerDrag) {
7713                         var draggable = this.options.draggable;
7714                         if (this.dragging) {
7715                                 draggable = this.dragging.enabled();
7716                                 this.dragging.disable();
7717                         }
7718
7719                         this.dragging = new MarkerDrag(this);
7720
7721                         if (draggable) {
7722                                 this.dragging.enable();
7723                         }
7724                 }
7725         },
7726
7727         // @method setOpacity(opacity: Number): this
7728         // Changes the opacity of the marker.
7729         setOpacity: function (opacity) {
7730                 this.options.opacity = opacity;
7731                 if (this._map) {
7732                         this._updateOpacity();
7733                 }
7734
7735                 return this;
7736         },
7737
7738         _updateOpacity: function () {
7739                 var opacity = this.options.opacity;
7740
7741                 if (this._icon) {
7742                         setOpacity(this._icon, opacity);
7743                 }
7744
7745                 if (this._shadow) {
7746                         setOpacity(this._shadow, opacity);
7747                 }
7748         },
7749
7750         _bringToFront: function () {
7751                 this._updateZIndex(this.options.riseOffset);
7752         },
7753
7754         _resetZIndex: function () {
7755                 this._updateZIndex(0);
7756         },
7757
7758         _getPopupAnchor: function () {
7759                 return this.options.icon.options.popupAnchor;
7760         },
7761
7762         _getTooltipAnchor: function () {
7763                 return this.options.icon.options.tooltipAnchor;
7764         }
7765 });
7766
7767
7768 // factory L.marker(latlng: LatLng, options? : Marker options)
7769
7770 // @factory L.marker(latlng: LatLng, options? : Marker options)
7771 // Instantiates a Marker object given a geographical point and optionally an options object.
7772 function marker(latlng, options) {
7773         return new Marker(latlng, options);
7774 }
7775
7776 /*
7777  * @class Path
7778  * @aka L.Path
7779  * @inherits Interactive layer
7780  *
7781  * An abstract class that contains options and constants shared between vector
7782  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7783  */
7784
7785 var Path = Layer.extend({
7786
7787         // @section
7788         // @aka Path options
7789         options: {
7790                 // @option stroke: Boolean = true
7791                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7792                 stroke: true,
7793
7794                 // @option color: String = '#3388ff'
7795                 // Stroke color
7796                 color: '#3388ff',
7797
7798                 // @option weight: Number = 3
7799                 // Stroke width in pixels
7800                 weight: 3,
7801
7802                 // @option opacity: Number = 1.0
7803                 // Stroke opacity
7804                 opacity: 1,
7805
7806                 // @option lineCap: String= 'round'
7807                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7808                 lineCap: 'round',
7809
7810                 // @option lineJoin: String = 'round'
7811                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7812                 lineJoin: 'round',
7813
7814                 // @option dashArray: String = null
7815                 // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
7816                 dashArray: null,
7817
7818                 // @option dashOffset: String = null
7819                 // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
7820                 dashOffset: null,
7821
7822                 // @option fill: Boolean = depends
7823                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7824                 fill: false,
7825
7826                 // @option fillColor: String = *
7827                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7828                 fillColor: null,
7829
7830                 // @option fillOpacity: Number = 0.2
7831                 // Fill opacity.
7832                 fillOpacity: 0.2,
7833
7834                 // @option fillRule: String = 'evenodd'
7835                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7836                 fillRule: 'evenodd',
7837
7838                 // className: '',
7839
7840                 // Option inherited from "Interactive layer" abstract class
7841                 interactive: true,
7842
7843                 // @option bubblingMouseEvents: Boolean = true
7844                 // When `true`, a mouse event on this path will trigger the same event on the map
7845                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7846                 bubblingMouseEvents: true
7847         },
7848
7849         beforeAdd: function (map) {
7850                 // Renderer is set here because we need to call renderer.getEvents
7851                 // before this.getEvents.
7852                 this._renderer = map.getRenderer(this);
7853         },
7854
7855         onAdd: function () {
7856                 this._renderer._initPath(this);
7857                 this._reset();
7858                 this._renderer._addPath(this);
7859         },
7860
7861         onRemove: function () {
7862                 this._renderer._removePath(this);
7863         },
7864
7865         // @method redraw(): this
7866         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7867         redraw: function () {
7868                 if (this._map) {
7869                         this._renderer._updatePath(this);
7870                 }
7871                 return this;
7872         },
7873
7874         // @method setStyle(style: Path options): this
7875         // Changes the appearance of a Path based on the options in the `Path options` object.
7876         setStyle: function (style) {
7877                 setOptions(this, style);
7878                 if (this._renderer) {
7879                         this._renderer._updateStyle(this);
7880                         if (this.options.stroke && style && style.hasOwnProperty('weight')) {
7881                                 this._updateBounds();
7882                         }
7883                 }
7884                 return this;
7885         },
7886
7887         // @method bringToFront(): this
7888         // Brings the layer to the top of all path layers.
7889         bringToFront: function () {
7890                 if (this._renderer) {
7891                         this._renderer._bringToFront(this);
7892                 }
7893                 return this;
7894         },
7895
7896         // @method bringToBack(): this
7897         // Brings the layer to the bottom of all path layers.
7898         bringToBack: function () {
7899                 if (this._renderer) {
7900                         this._renderer._bringToBack(this);
7901                 }
7902                 return this;
7903         },
7904
7905         getElement: function () {
7906                 return this._path;
7907         },
7908
7909         _reset: function () {
7910                 // defined in child classes
7911                 this._project();
7912                 this._update();
7913         },
7914
7915         _clickTolerance: function () {
7916                 // used when doing hit detection for Canvas layers
7917                 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7918         }
7919 });
7920
7921 /*
7922  * @class CircleMarker
7923  * @aka L.CircleMarker
7924  * @inherits Path
7925  *
7926  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7927  */
7928
7929 var CircleMarker = Path.extend({
7930
7931         // @section
7932         // @aka CircleMarker options
7933         options: {
7934                 fill: true,
7935
7936                 // @option radius: Number = 10
7937                 // Radius of the circle marker, in pixels
7938                 radius: 10
7939         },
7940
7941         initialize: function (latlng, options) {
7942                 setOptions(this, options);
7943                 this._latlng = toLatLng(latlng);
7944                 this._radius = this.options.radius;
7945         },
7946
7947         // @method setLatLng(latLng: LatLng): this
7948         // Sets the position of a circle marker to a new location.
7949         setLatLng: function (latlng) {
7950                 var oldLatLng = this._latlng;
7951                 this._latlng = toLatLng(latlng);
7952                 this.redraw();
7953
7954                 // @event move: Event
7955                 // Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
7956                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7957         },
7958
7959         // @method getLatLng(): LatLng
7960         // Returns the current geographical position of the circle marker
7961         getLatLng: function () {
7962                 return this._latlng;
7963         },
7964
7965         // @method setRadius(radius: Number): this
7966         // Sets the radius of a circle marker. Units are in pixels.
7967         setRadius: function (radius) {
7968                 this.options.radius = this._radius = radius;
7969                 return this.redraw();
7970         },
7971
7972         // @method getRadius(): Number
7973         // Returns the current radius of the circle
7974         getRadius: function () {
7975                 return this._radius;
7976         },
7977
7978         setStyle : function (options) {
7979                 var radius = options && options.radius || this._radius;
7980                 Path.prototype.setStyle.call(this, options);
7981                 this.setRadius(radius);
7982                 return this;
7983         },
7984
7985         _project: function () {
7986                 this._point = this._map.latLngToLayerPoint(this._latlng);
7987                 this._updateBounds();
7988         },
7989
7990         _updateBounds: function () {
7991                 var r = this._radius,
7992                     r2 = this._radiusY || r,
7993                     w = this._clickTolerance(),
7994                     p = [r + w, r2 + w];
7995                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7996         },
7997
7998         _update: function () {
7999                 if (this._map) {
8000                         this._updatePath();
8001                 }
8002         },
8003
8004         _updatePath: function () {
8005                 this._renderer._updateCircle(this);
8006         },
8007
8008         _empty: function () {
8009                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8010         },
8011
8012         // Needed by the `Canvas` renderer for interactivity
8013         _containsPoint: function (p) {
8014                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
8015         }
8016 });
8017
8018
8019 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8020 // Instantiates a circle marker object given a geographical point, and an optional options object.
8021 function circleMarker(latlng, options) {
8022         return new CircleMarker(latlng, options);
8023 }
8024
8025 /*
8026  * @class Circle
8027  * @aka L.Circle
8028  * @inherits CircleMarker
8029  *
8030  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8031  *
8032  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8033  *
8034  * @example
8035  *
8036  * ```js
8037  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8038  * ```
8039  */
8040
8041 var Circle = CircleMarker.extend({
8042
8043         initialize: function (latlng, options, legacyOptions) {
8044                 if (typeof options === 'number') {
8045                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8046                         options = extend({}, legacyOptions, {radius: options});
8047                 }
8048                 setOptions(this, options);
8049                 this._latlng = toLatLng(latlng);
8050
8051                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8052
8053                 // @section
8054                 // @aka Circle options
8055                 // @option radius: Number; Radius of the circle, in meters.
8056                 this._mRadius = this.options.radius;
8057         },
8058
8059         // @method setRadius(radius: Number): this
8060         // Sets the radius of a circle. Units are in meters.
8061         setRadius: function (radius) {
8062                 this._mRadius = radius;
8063                 return this.redraw();
8064         },
8065
8066         // @method getRadius(): Number
8067         // Returns the current radius of a circle. Units are in meters.
8068         getRadius: function () {
8069                 return this._mRadius;
8070         },
8071
8072         // @method getBounds(): LatLngBounds
8073         // Returns the `LatLngBounds` of the path.
8074         getBounds: function () {
8075                 var half = [this._radius, this._radiusY || this._radius];
8076
8077                 return new LatLngBounds(
8078                         this._map.layerPointToLatLng(this._point.subtract(half)),
8079                         this._map.layerPointToLatLng(this._point.add(half)));
8080         },
8081
8082         setStyle: Path.prototype.setStyle,
8083
8084         _project: function () {
8085
8086                 var lng = this._latlng.lng,
8087                     lat = this._latlng.lat,
8088                     map = this._map,
8089                     crs = map.options.crs;
8090
8091                 if (crs.distance === Earth.distance) {
8092                         var d = Math.PI / 180,
8093                             latR = (this._mRadius / Earth.R) / d,
8094                             top = map.project([lat + latR, lng]),
8095                             bottom = map.project([lat - latR, lng]),
8096                             p = top.add(bottom).divideBy(2),
8097                             lat2 = map.unproject(p).lat,
8098                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8099                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8100
8101                         if (isNaN(lngR) || lngR === 0) {
8102                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8103                         }
8104
8105                         this._point = p.subtract(map.getPixelOrigin());
8106                         this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8107                         this._radiusY = p.y - top.y;
8108
8109                 } else {
8110                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8111
8112                         this._point = map.latLngToLayerPoint(this._latlng);
8113                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8114                 }
8115
8116                 this._updateBounds();
8117         }
8118 });
8119
8120 // @factory L.circle(latlng: LatLng, options?: Circle options)
8121 // Instantiates a circle object given a geographical point, and an options object
8122 // which contains the circle radius.
8123 // @alternative
8124 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8125 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8126 // Do not use in new applications or plugins.
8127 function circle(latlng, options, legacyOptions) {
8128         return new Circle(latlng, options, legacyOptions);
8129 }
8130
8131 /*
8132  * @class Polyline
8133  * @aka L.Polyline
8134  * @inherits Path
8135  *
8136  * A class for drawing polyline overlays on a map. Extends `Path`.
8137  *
8138  * @example
8139  *
8140  * ```js
8141  * // create a red polyline from an array of LatLng points
8142  * var latlngs = [
8143  *      [45.51, -122.68],
8144  *      [37.77, -122.43],
8145  *      [34.04, -118.2]
8146  * ];
8147  *
8148  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8149  *
8150  * // zoom the map to the polyline
8151  * map.fitBounds(polyline.getBounds());
8152  * ```
8153  *
8154  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8155  *
8156  * ```js
8157  * // create a red polyline from an array of arrays of LatLng points
8158  * var latlngs = [
8159  *      [[45.51, -122.68],
8160  *       [37.77, -122.43],
8161  *       [34.04, -118.2]],
8162  *      [[40.78, -73.91],
8163  *       [41.83, -87.62],
8164  *       [32.76, -96.72]]
8165  * ];
8166  * ```
8167  */
8168
8169
8170 var Polyline = Path.extend({
8171
8172         // @section
8173         // @aka Polyline options
8174         options: {
8175                 // @option smoothFactor: Number = 1.0
8176                 // How much to simplify the polyline on each zoom level. More means
8177                 // better performance and smoother look, and less means more accurate representation.
8178                 smoothFactor: 1.0,
8179
8180                 // @option noClip: Boolean = false
8181                 // Disable polyline clipping.
8182                 noClip: false
8183         },
8184
8185         initialize: function (latlngs, options) {
8186                 setOptions(this, options);
8187                 this._setLatLngs(latlngs);
8188         },
8189
8190         // @method getLatLngs(): LatLng[]
8191         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8192         getLatLngs: function () {
8193                 return this._latlngs;
8194         },
8195
8196         // @method setLatLngs(latlngs: LatLng[]): this
8197         // Replaces all the points in the polyline with the given array of geographical points.
8198         setLatLngs: function (latlngs) {
8199                 this._setLatLngs(latlngs);
8200                 return this.redraw();
8201         },
8202
8203         // @method isEmpty(): Boolean
8204         // Returns `true` if the Polyline has no LatLngs.
8205         isEmpty: function () {
8206                 return !this._latlngs.length;
8207         },
8208
8209         // @method closestLayerPoint(p: Point): Point
8210         // Returns the point closest to `p` on the Polyline.
8211         closestLayerPoint: function (p) {
8212                 var minDistance = Infinity,
8213                     minPoint = null,
8214                     closest = _sqClosestPointOnSegment,
8215                     p1, p2;
8216
8217                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8218                         var points = this._parts[j];
8219
8220                         for (var i = 1, len = points.length; i < len; i++) {
8221                                 p1 = points[i - 1];
8222                                 p2 = points[i];
8223
8224                                 var sqDist = closest(p, p1, p2, true);
8225
8226                                 if (sqDist < minDistance) {
8227                                         minDistance = sqDist;
8228                                         minPoint = closest(p, p1, p2);
8229                                 }
8230                         }
8231                 }
8232                 if (minPoint) {
8233                         minPoint.distance = Math.sqrt(minDistance);
8234                 }
8235                 return minPoint;
8236         },
8237
8238         // @method getCenter(): LatLng
8239         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8240         getCenter: function () {
8241                 // throws error when not yet added to map as this center calculation requires projected coordinates
8242                 if (!this._map) {
8243                         throw new Error('Must add layer to map before using getCenter()');
8244                 }
8245
8246                 var i, halfDist, segDist, dist, p1, p2, ratio,
8247                     points = this._rings[0],
8248                     len = points.length;
8249
8250                 if (!len) { return null; }
8251
8252                 // polyline centroid algorithm; only uses the first ring if there are multiple
8253
8254                 for (i = 0, halfDist = 0; i < len - 1; i++) {
8255                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
8256                 }
8257
8258                 // The line is so small in the current view that all points are on the same pixel.
8259                 if (halfDist === 0) {
8260                         return this._map.layerPointToLatLng(points[0]);
8261                 }
8262
8263                 for (i = 0, dist = 0; i < len - 1; i++) {
8264                         p1 = points[i];
8265                         p2 = points[i + 1];
8266                         segDist = p1.distanceTo(p2);
8267                         dist += segDist;
8268
8269                         if (dist > halfDist) {
8270                                 ratio = (dist - halfDist) / segDist;
8271                                 return this._map.layerPointToLatLng([
8272                                         p2.x - ratio * (p2.x - p1.x),
8273                                         p2.y - ratio * (p2.y - p1.y)
8274                                 ]);
8275                         }
8276                 }
8277         },
8278
8279         // @method getBounds(): LatLngBounds
8280         // Returns the `LatLngBounds` of the path.
8281         getBounds: function () {
8282                 return this._bounds;
8283         },
8284
8285         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8286         // Adds a given point to the polyline. By default, adds to the first ring of
8287         // the polyline in case of a multi-polyline, but can be overridden by passing
8288         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8289         addLatLng: function (latlng, latlngs) {
8290                 latlngs = latlngs || this._defaultShape();
8291                 latlng = toLatLng(latlng);
8292                 latlngs.push(latlng);
8293                 this._bounds.extend(latlng);
8294                 return this.redraw();
8295         },
8296
8297         _setLatLngs: function (latlngs) {
8298                 this._bounds = new LatLngBounds();
8299                 this._latlngs = this._convertLatLngs(latlngs);
8300         },
8301
8302         _defaultShape: function () {
8303                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8304         },
8305
8306         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8307         _convertLatLngs: function (latlngs) {
8308                 var result = [],
8309                     flat = isFlat(latlngs);
8310
8311                 for (var i = 0, len = latlngs.length; i < len; i++) {
8312                         if (flat) {
8313                                 result[i] = toLatLng(latlngs[i]);
8314                                 this._bounds.extend(result[i]);
8315                         } else {
8316                                 result[i] = this._convertLatLngs(latlngs[i]);
8317                         }
8318                 }
8319
8320                 return result;
8321         },
8322
8323         _project: function () {
8324                 var pxBounds = new Bounds();
8325                 this._rings = [];
8326                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8327
8328                 if (this._bounds.isValid() && pxBounds.isValid()) {
8329                         this._rawPxBounds = pxBounds;
8330                         this._updateBounds();
8331                 }
8332         },
8333
8334         _updateBounds: function () {
8335                 var w = this._clickTolerance(),
8336                     p = new Point(w, w);
8337                 this._pxBounds = new Bounds([
8338                         this._rawPxBounds.min.subtract(p),
8339                         this._rawPxBounds.max.add(p)
8340                 ]);
8341         },
8342
8343         // recursively turns latlngs into a set of rings with projected coordinates
8344         _projectLatlngs: function (latlngs, result, projectedBounds) {
8345                 var flat = latlngs[0] instanceof LatLng,
8346                     len = latlngs.length,
8347                     i, ring;
8348
8349                 if (flat) {
8350                         ring = [];
8351                         for (i = 0; i < len; i++) {
8352                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8353                                 projectedBounds.extend(ring[i]);
8354                         }
8355                         result.push(ring);
8356                 } else {
8357                         for (i = 0; i < len; i++) {
8358                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8359                         }
8360                 }
8361         },
8362
8363         // clip polyline by renderer bounds so that we have less to render for performance
8364         _clipPoints: function () {
8365                 var bounds = this._renderer._bounds;
8366
8367                 this._parts = [];
8368                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8369                         return;
8370                 }
8371
8372                 if (this.options.noClip) {
8373                         this._parts = this._rings;
8374                         return;
8375                 }
8376
8377                 var parts = this._parts,
8378                     i, j, k, len, len2, segment, points;
8379
8380                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8381                         points = this._rings[i];
8382
8383                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8384                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8385
8386                                 if (!segment) { continue; }
8387
8388                                 parts[k] = parts[k] || [];
8389                                 parts[k].push(segment[0]);
8390
8391                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8392                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8393                                         parts[k].push(segment[1]);
8394                                         k++;
8395                                 }
8396                         }
8397                 }
8398         },
8399
8400         // simplify each clipped part of the polyline for performance
8401         _simplifyPoints: function () {
8402                 var parts = this._parts,
8403                     tolerance = this.options.smoothFactor;
8404
8405                 for (var i = 0, len = parts.length; i < len; i++) {
8406                         parts[i] = simplify(parts[i], tolerance);
8407                 }
8408         },
8409
8410         _update: function () {
8411                 if (!this._map) { return; }
8412
8413                 this._clipPoints();
8414                 this._simplifyPoints();
8415                 this._updatePath();
8416         },
8417
8418         _updatePath: function () {
8419                 this._renderer._updatePoly(this);
8420         },
8421
8422         // Needed by the `Canvas` renderer for interactivity
8423         _containsPoint: function (p, closed) {
8424                 var i, j, k, len, len2, part,
8425                     w = this._clickTolerance();
8426
8427                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8428
8429                 // hit detection for polylines
8430                 for (i = 0, len = this._parts.length; i < len; i++) {
8431                         part = this._parts[i];
8432
8433                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8434                                 if (!closed && (j === 0)) { continue; }
8435
8436                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8437                                         return true;
8438                                 }
8439                         }
8440                 }
8441                 return false;
8442         }
8443 });
8444
8445 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8446 // Instantiates a polyline object given an array of geographical points and
8447 // optionally an options object. You can create a `Polyline` object with
8448 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8449 // of geographic points.
8450 function polyline(latlngs, options) {
8451         return new Polyline(latlngs, options);
8452 }
8453
8454 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8455 Polyline._flat = _flat;
8456
8457 /*
8458  * @class Polygon
8459  * @aka L.Polygon
8460  * @inherits Polyline
8461  *
8462  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8463  *
8464  * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
8465  *
8466  *
8467  * @example
8468  *
8469  * ```js
8470  * // create a red polygon from an array of LatLng points
8471  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8472  *
8473  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8474  *
8475  * // zoom the map to the polygon
8476  * map.fitBounds(polygon.getBounds());
8477  * ```
8478  *
8479  * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
8480  *
8481  * ```js
8482  * var latlngs = [
8483  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8484  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8485  * ];
8486  * ```
8487  *
8488  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8489  *
8490  * ```js
8491  * var latlngs = [
8492  *   [ // first polygon
8493  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8494  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8495  *   ],
8496  *   [ // second polygon
8497  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8498  *   ]
8499  * ];
8500  * ```
8501  */
8502
8503 var Polygon = Polyline.extend({
8504
8505         options: {
8506                 fill: true
8507         },
8508
8509         isEmpty: function () {
8510                 return !this._latlngs.length || !this._latlngs[0].length;
8511         },
8512
8513         getCenter: function () {
8514                 // throws error when not yet added to map as this center calculation requires projected coordinates
8515                 if (!this._map) {
8516                         throw new Error('Must add layer to map before using getCenter()');
8517                 }
8518
8519                 var i, j, p1, p2, f, area, x, y, center,
8520                     points = this._rings[0],
8521                     len = points.length;
8522
8523                 if (!len) { return null; }
8524
8525                 // polygon centroid algorithm; only uses the first ring if there are multiple
8526
8527                 area = x = y = 0;
8528
8529                 for (i = 0, j = len - 1; i < len; j = i++) {
8530                         p1 = points[i];
8531                         p2 = points[j];
8532
8533                         f = p1.y * p2.x - p2.y * p1.x;
8534                         x += (p1.x + p2.x) * f;
8535                         y += (p1.y + p2.y) * f;
8536                         area += f * 3;
8537                 }
8538
8539                 if (area === 0) {
8540                         // Polygon is so small that all points are on same pixel.
8541                         center = points[0];
8542                 } else {
8543                         center = [x / area, y / area];
8544                 }
8545                 return this._map.layerPointToLatLng(center);
8546         },
8547
8548         _convertLatLngs: function (latlngs) {
8549                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8550                     len = result.length;
8551
8552                 // remove last point if it equals first one
8553                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8554                         result.pop();
8555                 }
8556                 return result;
8557         },
8558
8559         _setLatLngs: function (latlngs) {
8560                 Polyline.prototype._setLatLngs.call(this, latlngs);
8561                 if (isFlat(this._latlngs)) {
8562                         this._latlngs = [this._latlngs];
8563                 }
8564         },
8565
8566         _defaultShape: function () {
8567                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8568         },
8569
8570         _clipPoints: function () {
8571                 // polygons need a different clipping algorithm so we redefine that
8572
8573                 var bounds = this._renderer._bounds,
8574                     w = this.options.weight,
8575                     p = new Point(w, w);
8576
8577                 // increase clip padding by stroke width to avoid stroke on clip edges
8578                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8579
8580                 this._parts = [];
8581                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8582                         return;
8583                 }
8584
8585                 if (this.options.noClip) {
8586                         this._parts = this._rings;
8587                         return;
8588                 }
8589
8590                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8591                         clipped = clipPolygon(this._rings[i], bounds, true);
8592                         if (clipped.length) {
8593                                 this._parts.push(clipped);
8594                         }
8595                 }
8596         },
8597
8598         _updatePath: function () {
8599                 this._renderer._updatePoly(this, true);
8600         },
8601
8602         // Needed by the `Canvas` renderer for interactivity
8603         _containsPoint: function (p) {
8604                 var inside = false,
8605                     part, p1, p2, i, j, k, len, len2;
8606
8607                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8608
8609                 // ray casting algorithm for detecting if point is in polygon
8610                 for (i = 0, len = this._parts.length; i < len; i++) {
8611                         part = this._parts[i];
8612
8613                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8614                                 p1 = part[j];
8615                                 p2 = part[k];
8616
8617                                 if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
8618                                         inside = !inside;
8619                                 }
8620                         }
8621                 }
8622
8623                 // also check if it's on polygon stroke
8624                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8625         }
8626
8627 });
8628
8629
8630 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8631 function polygon(latlngs, options) {
8632         return new Polygon(latlngs, options);
8633 }
8634
8635 /*
8636  * @class GeoJSON
8637  * @aka L.GeoJSON
8638  * @inherits FeatureGroup
8639  *
8640  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8641  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8642  *
8643  * @example
8644  *
8645  * ```js
8646  * L.geoJSON(data, {
8647  *      style: function (feature) {
8648  *              return {color: feature.properties.color};
8649  *      }
8650  * }).bindPopup(function (layer) {
8651  *      return layer.feature.properties.description;
8652  * }).addTo(map);
8653  * ```
8654  */
8655
8656 var GeoJSON = FeatureGroup.extend({
8657
8658         /* @section
8659          * @aka GeoJSON options
8660          *
8661          * @option pointToLayer: Function = *
8662          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8663          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8664          * The default is to spawn a default `Marker`:
8665          * ```js
8666          * function(geoJsonPoint, latlng) {
8667          *      return L.marker(latlng);
8668          * }
8669          * ```
8670          *
8671          * @option style: Function = *
8672          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8673          * called internally when data is added.
8674          * The default value is to not override any defaults:
8675          * ```js
8676          * function (geoJsonFeature) {
8677          *      return {}
8678          * }
8679          * ```
8680          *
8681          * @option onEachFeature: Function = *
8682          * A `Function` that will be called once for each created `Feature`, after it has
8683          * been created and styled. Useful for attaching events and popups to features.
8684          * The default is to do nothing with the newly created layers:
8685          * ```js
8686          * function (feature, layer) {}
8687          * ```
8688          *
8689          * @option filter: Function = *
8690          * A `Function` that will be used to decide whether to include a feature or not.
8691          * The default is to include all features:
8692          * ```js
8693          * function (geoJsonFeature) {
8694          *      return true;
8695          * }
8696          * ```
8697          * Note: dynamically changing the `filter` option will have effect only on newly
8698          * added data. It will _not_ re-evaluate already included features.
8699          *
8700          * @option coordsToLatLng: Function = *
8701          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8702          * The default is the `coordsToLatLng` static method.
8703          *
8704          * @option markersInheritOptions: Boolean = false
8705          * Whether default Markers for "Point" type Features inherit from group options.
8706          */
8707
8708         initialize: function (geojson, options) {
8709                 setOptions(this, options);
8710
8711                 this._layers = {};
8712
8713                 if (geojson) {
8714                         this.addData(geojson);
8715                 }
8716         },
8717
8718         // @method addData( <GeoJSON> data ): this
8719         // Adds a GeoJSON object to the layer.
8720         addData: function (geojson) {
8721                 var features = isArray(geojson) ? geojson : geojson.features,
8722                     i, len, feature;
8723
8724                 if (features) {
8725                         for (i = 0, len = features.length; i < len; i++) {
8726                                 // only add this if geometry or geometries are set and not null
8727                                 feature = features[i];
8728                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8729                                         this.addData(feature);
8730                                 }
8731                         }
8732                         return this;
8733                 }
8734
8735                 var options = this.options;
8736
8737                 if (options.filter && !options.filter(geojson)) { return this; }
8738
8739                 var layer = geometryToLayer(geojson, options);
8740                 if (!layer) {
8741                         return this;
8742                 }
8743                 layer.feature = asFeature(geojson);
8744
8745                 layer.defaultOptions = layer.options;
8746                 this.resetStyle(layer);
8747
8748                 if (options.onEachFeature) {
8749                         options.onEachFeature(geojson, layer);
8750                 }
8751
8752                 return this.addLayer(layer);
8753         },
8754
8755         // @method resetStyle( <Path> layer? ): this
8756         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8757         // If `layer` is omitted, the style of all features in the current layer is reset.
8758         resetStyle: function (layer) {
8759                 if (layer === undefined) {
8760                         return this.eachLayer(this.resetStyle, this);
8761                 }
8762                 // reset any custom styles
8763                 layer.options = extend({}, layer.defaultOptions);
8764                 this._setLayerStyle(layer, this.options.style);
8765                 return this;
8766         },
8767
8768         // @method setStyle( <Function> style ): this
8769         // Changes styles of GeoJSON vector layers with the given style function.
8770         setStyle: function (style) {
8771                 return this.eachLayer(function (layer) {
8772                         this._setLayerStyle(layer, style);
8773                 }, this);
8774         },
8775
8776         _setLayerStyle: function (layer, style) {
8777                 if (layer.setStyle) {
8778                         if (typeof style === 'function') {
8779                                 style = style(layer.feature);
8780                         }
8781                         layer.setStyle(style);
8782                 }
8783         }
8784 });
8785
8786 // @section
8787 // There are several static functions which can be called without instantiating L.GeoJSON:
8788
8789 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8790 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8791 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8792 // functions if provided as options.
8793 function geometryToLayer(geojson, options) {
8794
8795         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8796             coords = geometry ? geometry.coordinates : null,
8797             layers = [],
8798             pointToLayer = options && options.pointToLayer,
8799             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8800             latlng, latlngs, i, len;
8801
8802         if (!coords && !geometry) {
8803                 return null;
8804         }
8805
8806         switch (geometry.type) {
8807         case 'Point':
8808                 latlng = _coordsToLatLng(coords);
8809                 return _pointToLayer(pointToLayer, geojson, latlng, options);
8810
8811         case 'MultiPoint':
8812                 for (i = 0, len = coords.length; i < len; i++) {
8813                         latlng = _coordsToLatLng(coords[i]);
8814                         layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
8815                 }
8816                 return new FeatureGroup(layers);
8817
8818         case 'LineString':
8819         case 'MultiLineString':
8820                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8821                 return new Polyline(latlngs, options);
8822
8823         case 'Polygon':
8824         case 'MultiPolygon':
8825                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8826                 return new Polygon(latlngs, options);
8827
8828         case 'GeometryCollection':
8829                 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8830                         var layer = geometryToLayer({
8831                                 geometry: geometry.geometries[i],
8832                                 type: 'Feature',
8833                                 properties: geojson.properties
8834                         }, options);
8835
8836                         if (layer) {
8837                                 layers.push(layer);
8838                         }
8839                 }
8840                 return new FeatureGroup(layers);
8841
8842         default:
8843                 throw new Error('Invalid GeoJSON object.');
8844         }
8845 }
8846
8847 function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
8848         return pointToLayerFn ?
8849                 pointToLayerFn(geojson, latlng) :
8850                 new Marker(latlng, options && options.markersInheritOptions && options);
8851 }
8852
8853 // @function coordsToLatLng(coords: Array): LatLng
8854 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8855 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8856 function coordsToLatLng(coords) {
8857         return new LatLng(coords[1], coords[0], coords[2]);
8858 }
8859
8860 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8861 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8862 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8863 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8864 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8865         var latlngs = [];
8866
8867         for (var i = 0, len = coords.length, latlng; i < len; i++) {
8868                 latlng = levelsDeep ?
8869                         coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8870                         (_coordsToLatLng || coordsToLatLng)(coords[i]);
8871
8872                 latlngs.push(latlng);
8873         }
8874
8875         return latlngs;
8876 }
8877
8878 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8879 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8880 function latLngToCoords(latlng, precision) {
8881         precision = typeof precision === 'number' ? precision : 6;
8882         return latlng.alt !== undefined ?
8883                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8884                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8885 }
8886
8887 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8888 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8889 // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
8890 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8891         var coords = [];
8892
8893         for (var i = 0, len = latlngs.length; i < len; i++) {
8894                 coords.push(levelsDeep ?
8895                         latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8896                         latLngToCoords(latlngs[i], precision));
8897         }
8898
8899         if (!levelsDeep && closed) {
8900                 coords.push(coords[0]);
8901         }
8902
8903         return coords;
8904 }
8905
8906 function getFeature(layer, newGeometry) {
8907         return layer.feature ?
8908                 extend({}, layer.feature, {geometry: newGeometry}) :
8909                 asFeature(newGeometry);
8910 }
8911
8912 // @function asFeature(geojson: Object): Object
8913 // Normalize GeoJSON geometries/features into GeoJSON features.
8914 function asFeature(geojson) {
8915         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8916                 return geojson;
8917         }
8918
8919         return {
8920                 type: 'Feature',
8921                 properties: {},
8922                 geometry: geojson
8923         };
8924 }
8925
8926 var PointToGeoJSON = {
8927         toGeoJSON: function (precision) {
8928                 return getFeature(this, {
8929                         type: 'Point',
8930                         coordinates: latLngToCoords(this.getLatLng(), precision)
8931                 });
8932         }
8933 };
8934
8935 // @namespace Marker
8936 // @section Other methods
8937 // @method toGeoJSON(precision?: Number): Object
8938 // `precision` is the number of decimal places for coordinates.
8939 // The default value is 6 places.
8940 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8941 Marker.include(PointToGeoJSON);
8942
8943 // @namespace CircleMarker
8944 // @method toGeoJSON(precision?: Number): Object
8945 // `precision` is the number of decimal places for coordinates.
8946 // The default value is 6 places.
8947 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8948 Circle.include(PointToGeoJSON);
8949 CircleMarker.include(PointToGeoJSON);
8950
8951
8952 // @namespace Polyline
8953 // @method toGeoJSON(precision?: Number): Object
8954 // `precision` is the number of decimal places for coordinates.
8955 // The default value is 6 places.
8956 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8957 Polyline.include({
8958         toGeoJSON: function (precision) {
8959                 var multi = !isFlat(this._latlngs);
8960
8961                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8962
8963                 return getFeature(this, {
8964                         type: (multi ? 'Multi' : '') + 'LineString',
8965                         coordinates: coords
8966                 });
8967         }
8968 });
8969
8970 // @namespace Polygon
8971 // @method toGeoJSON(precision?: Number): Object
8972 // `precision` is the number of decimal places for coordinates.
8973 // The default value is 6 places.
8974 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8975 Polygon.include({
8976         toGeoJSON: function (precision) {
8977                 var holes = !isFlat(this._latlngs),
8978                     multi = holes && !isFlat(this._latlngs[0]);
8979
8980                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8981
8982                 if (!holes) {
8983                         coords = [coords];
8984                 }
8985
8986                 return getFeature(this, {
8987                         type: (multi ? 'Multi' : '') + 'Polygon',
8988                         coordinates: coords
8989                 });
8990         }
8991 });
8992
8993
8994 // @namespace LayerGroup
8995 LayerGroup.include({
8996         toMultiPoint: function (precision) {
8997                 var coords = [];
8998
8999                 this.eachLayer(function (layer) {
9000                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);
9001                 });
9002
9003                 return getFeature(this, {
9004                         type: 'MultiPoint',
9005                         coordinates: coords
9006                 });
9007         },
9008
9009         // @method toGeoJSON(precision?: Number): Object
9010         // `precision` is the number of decimal places for coordinates.
9011         // The default value is 6 places.
9012         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
9013         toGeoJSON: function (precision) {
9014
9015                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9016
9017                 if (type === 'MultiPoint') {
9018                         return this.toMultiPoint(precision);
9019                 }
9020
9021                 var isGeometryCollection = type === 'GeometryCollection',
9022                     jsons = [];
9023
9024                 this.eachLayer(function (layer) {
9025                         if (layer.toGeoJSON) {
9026                                 var json = layer.toGeoJSON(precision);
9027                                 if (isGeometryCollection) {
9028                                         jsons.push(json.geometry);
9029                                 } else {
9030                                         var feature = asFeature(json);
9031                                         // Squash nested feature collections
9032                                         if (feature.type === 'FeatureCollection') {
9033                                                 jsons.push.apply(jsons, feature.features);
9034                                         } else {
9035                                                 jsons.push(feature);
9036                                         }
9037                                 }
9038                         }
9039                 });
9040
9041                 if (isGeometryCollection) {
9042                         return getFeature(this, {
9043                                 geometries: jsons,
9044                                 type: 'GeometryCollection'
9045                         });
9046                 }
9047
9048                 return {
9049                         type: 'FeatureCollection',
9050                         features: jsons
9051                 };
9052         }
9053 });
9054
9055 // @namespace GeoJSON
9056 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9057 // Creates a GeoJSON layer. Optionally accepts an object in
9058 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9059 // (you can alternatively add it later with `addData` method) and an `options` object.
9060 function geoJSON(geojson, options) {
9061         return new GeoJSON(geojson, options);
9062 }
9063
9064 // Backward compatibility.
9065 var geoJson = geoJSON;
9066
9067 /*
9068  * @class ImageOverlay
9069  * @aka L.ImageOverlay
9070  * @inherits Interactive layer
9071  *
9072  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9073  *
9074  * @example
9075  *
9076  * ```js
9077  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9078  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9079  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9080  * ```
9081  */
9082
9083 var ImageOverlay = Layer.extend({
9084
9085         // @section
9086         // @aka ImageOverlay options
9087         options: {
9088                 // @option opacity: Number = 1.0
9089                 // The opacity of the image overlay.
9090                 opacity: 1,
9091
9092                 // @option alt: String = ''
9093                 // Text for the `alt` attribute of the image (useful for accessibility).
9094                 alt: '',
9095
9096                 // @option interactive: Boolean = false
9097                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9098                 interactive: false,
9099
9100                 // @option crossOrigin: Boolean|String = false
9101                 // Whether the crossOrigin attribute will be added to the image.
9102                 // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
9103                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9104                 crossOrigin: false,
9105
9106                 // @option errorOverlayUrl: String = ''
9107                 // URL to the overlay image to show in place of the overlay that failed to load.
9108                 errorOverlayUrl: '',
9109
9110                 // @option zIndex: Number = 1
9111                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9112                 zIndex: 1,
9113
9114                 // @option className: String = ''
9115                 // A custom class name to assign to the image. Empty by default.
9116                 className: ''
9117         },
9118
9119         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9120                 this._url = url;
9121                 this._bounds = toLatLngBounds(bounds);
9122
9123                 setOptions(this, options);
9124         },
9125
9126         onAdd: function () {
9127                 if (!this._image) {
9128                         this._initImage();
9129
9130                         if (this.options.opacity < 1) {
9131                                 this._updateOpacity();
9132                         }
9133                 }
9134
9135                 if (this.options.interactive) {
9136                         addClass(this._image, 'leaflet-interactive');
9137                         this.addInteractiveTarget(this._image);
9138                 }
9139
9140                 this.getPane().appendChild(this._image);
9141                 this._reset();
9142         },
9143
9144         onRemove: function () {
9145                 remove(this._image);
9146                 if (this.options.interactive) {
9147                         this.removeInteractiveTarget(this._image);
9148                 }
9149         },
9150
9151         // @method setOpacity(opacity: Number): this
9152         // Sets the opacity of the overlay.
9153         setOpacity: function (opacity) {
9154                 this.options.opacity = opacity;
9155
9156                 if (this._image) {
9157                         this._updateOpacity();
9158                 }
9159                 return this;
9160         },
9161
9162         setStyle: function (styleOpts) {
9163                 if (styleOpts.opacity) {
9164                         this.setOpacity(styleOpts.opacity);
9165                 }
9166                 return this;
9167         },
9168
9169         // @method bringToFront(): this
9170         // Brings the layer to the top of all overlays.
9171         bringToFront: function () {
9172                 if (this._map) {
9173                         toFront(this._image);
9174                 }
9175                 return this;
9176         },
9177
9178         // @method bringToBack(): this
9179         // Brings the layer to the bottom of all overlays.
9180         bringToBack: function () {
9181                 if (this._map) {
9182                         toBack(this._image);
9183                 }
9184                 return this;
9185         },
9186
9187         // @method setUrl(url: String): this
9188         // Changes the URL of the image.
9189         setUrl: function (url) {
9190                 this._url = url;
9191
9192                 if (this._image) {
9193                         this._image.src = url;
9194                 }
9195                 return this;
9196         },
9197
9198         // @method setBounds(bounds: LatLngBounds): this
9199         // Update the bounds that this ImageOverlay covers
9200         setBounds: function (bounds) {
9201                 this._bounds = toLatLngBounds(bounds);
9202
9203                 if (this._map) {
9204                         this._reset();
9205                 }
9206                 return this;
9207         },
9208
9209         getEvents: function () {
9210                 var events = {
9211                         zoom: this._reset,
9212                         viewreset: this._reset
9213                 };
9214
9215                 if (this._zoomAnimated) {
9216                         events.zoomanim = this._animateZoom;
9217                 }
9218
9219                 return events;
9220         },
9221
9222         // @method setZIndex(value: Number): this
9223         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9224         setZIndex: function (value) {
9225                 this.options.zIndex = value;
9226                 this._updateZIndex();
9227                 return this;
9228         },
9229
9230         // @method getBounds(): LatLngBounds
9231         // Get the bounds that this ImageOverlay covers
9232         getBounds: function () {
9233                 return this._bounds;
9234         },
9235
9236         // @method getElement(): HTMLElement
9237         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9238         // used by this overlay.
9239         getElement: function () {
9240                 return this._image;
9241         },
9242
9243         _initImage: function () {
9244                 var wasElementSupplied = this._url.tagName === 'IMG';
9245                 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9246
9247                 addClass(img, 'leaflet-image-layer');
9248                 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9249                 if (this.options.className) { addClass(img, this.options.className); }
9250
9251                 img.onselectstart = falseFn;
9252                 img.onmousemove = falseFn;
9253
9254                 // @event load: Event
9255                 // Fired when the ImageOverlay layer has loaded its image
9256                 img.onload = bind(this.fire, this, 'load');
9257                 img.onerror = bind(this._overlayOnError, this, 'error');
9258
9259                 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9260                         img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9261                 }
9262
9263                 if (this.options.zIndex) {
9264                         this._updateZIndex();
9265                 }
9266
9267                 if (wasElementSupplied) {
9268                         this._url = img.src;
9269                         return;
9270                 }
9271
9272                 img.src = this._url;
9273                 img.alt = this.options.alt;
9274         },
9275
9276         _animateZoom: function (e) {
9277                 var scale = this._map.getZoomScale(e.zoom),
9278                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9279
9280                 setTransform(this._image, offset, scale);
9281         },
9282
9283         _reset: function () {
9284                 var image = this._image,
9285                     bounds = new Bounds(
9286                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9287                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9288                     size = bounds.getSize();
9289
9290                 setPosition(image, bounds.min);
9291
9292                 image.style.width  = size.x + 'px';
9293                 image.style.height = size.y + 'px';
9294         },
9295
9296         _updateOpacity: function () {
9297                 setOpacity(this._image, this.options.opacity);
9298         },
9299
9300         _updateZIndex: function () {
9301                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9302                         this._image.style.zIndex = this.options.zIndex;
9303                 }
9304         },
9305
9306         _overlayOnError: function () {
9307                 // @event error: Event
9308                 // Fired when the ImageOverlay layer fails to load its image
9309                 this.fire('error');
9310
9311                 var errorUrl = this.options.errorOverlayUrl;
9312                 if (errorUrl && this._url !== errorUrl) {
9313                         this._url = errorUrl;
9314                         this._image.src = errorUrl;
9315                 }
9316         }
9317 });
9318
9319 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9320 // Instantiates an image overlay object given the URL of the image and the
9321 // geographical bounds it is tied to.
9322 var imageOverlay = function (url, bounds, options) {
9323         return new ImageOverlay(url, bounds, options);
9324 };
9325
9326 /*
9327  * @class VideoOverlay
9328  * @aka L.VideoOverlay
9329  * @inherits ImageOverlay
9330  *
9331  * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9332  *
9333  * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9334  * HTML5 element.
9335  *
9336  * @example
9337  *
9338  * ```js
9339  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9340  *      videoBounds = [[ 32, -130], [ 13, -100]];
9341  * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9342  * ```
9343  */
9344
9345 var VideoOverlay = ImageOverlay.extend({
9346
9347         // @section
9348         // @aka VideoOverlay options
9349         options: {
9350                 // @option autoplay: Boolean = true
9351                 // Whether the video starts playing automatically when loaded.
9352                 autoplay: true,
9353
9354                 // @option loop: Boolean = true
9355                 // Whether the video will loop back to the beginning when played.
9356                 loop: true,
9357
9358                 // @option keepAspectRatio: Boolean = true
9359                 // Whether the video will save aspect ratio after the projection.
9360                 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9361                 keepAspectRatio: true
9362         },
9363
9364         _initImage: function () {
9365                 var wasElementSupplied = this._url.tagName === 'VIDEO';
9366                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9367
9368                 addClass(vid, 'leaflet-image-layer');
9369                 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9370                 if (this.options.className) { addClass(vid, this.options.className); }
9371
9372                 vid.onselectstart = falseFn;
9373                 vid.onmousemove = falseFn;
9374
9375                 // @event load: Event
9376                 // Fired when the video has finished loading the first frame
9377                 vid.onloadeddata = bind(this.fire, this, 'load');
9378
9379                 if (wasElementSupplied) {
9380                         var sourceElements = vid.getElementsByTagName('source');
9381                         var sources = [];
9382                         for (var j = 0; j < sourceElements.length; j++) {
9383                                 sources.push(sourceElements[j].src);
9384                         }
9385
9386                         this._url = (sourceElements.length > 0) ? sources : [vid.src];
9387                         return;
9388                 }
9389
9390                 if (!isArray(this._url)) { this._url = [this._url]; }
9391
9392                 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
9393                 vid.autoplay = !!this.options.autoplay;
9394                 vid.loop = !!this.options.loop;
9395                 for (var i = 0; i < this._url.length; i++) {
9396                         var source = create$1('source');
9397                         source.src = this._url[i];
9398                         vid.appendChild(source);
9399                 }
9400         }
9401
9402         // @method getElement(): HTMLVideoElement
9403         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9404         // used by this overlay.
9405 });
9406
9407
9408 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9409 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9410 // geographical bounds it is tied to.
9411
9412 function videoOverlay(video, bounds, options) {
9413         return new VideoOverlay(video, bounds, options);
9414 }
9415
9416 /*
9417  * @class SVGOverlay
9418  * @aka L.SVGOverlay
9419  * @inherits ImageOverlay
9420  *
9421  * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9422  *
9423  * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9424  *
9425  * @example
9426  *
9427  * ```js
9428  * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
9429  * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
9430  * svgElement.setAttribute('viewBox', "0 0 200 200");
9431  * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
9432  * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9433  * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
9434  * ```
9435  */
9436
9437 var SVGOverlay = ImageOverlay.extend({
9438         _initImage: function () {
9439                 var el = this._image = this._url;
9440
9441                 addClass(el, 'leaflet-image-layer');
9442                 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9443                 if (this.options.className) { addClass(el, this.options.className); }
9444
9445                 el.onselectstart = falseFn;
9446                 el.onmousemove = falseFn;
9447         }
9448
9449         // @method getElement(): SVGElement
9450         // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9451         // used by this overlay.
9452 });
9453
9454
9455 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9456 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9457 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9458
9459 function svgOverlay(el, bounds, options) {
9460         return new SVGOverlay(el, bounds, options);
9461 }
9462
9463 /*
9464  * @class DivOverlay
9465  * @inherits Layer
9466  * @aka L.DivOverlay
9467  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9468  */
9469
9470 // @namespace DivOverlay
9471 var DivOverlay = Layer.extend({
9472
9473         // @section
9474         // @aka DivOverlay options
9475         options: {
9476                 // @option offset: Point = Point(0, 7)
9477                 // The offset of the popup position. Useful to control the anchor
9478                 // of the popup when opening it on some overlays.
9479                 offset: [0, 7],
9480
9481                 // @option className: String = ''
9482                 // A custom CSS class name to assign to the popup.
9483                 className: '',
9484
9485                 // @option pane: String = 'popupPane'
9486                 // `Map pane` where the popup will be added.
9487                 pane: 'popupPane'
9488         },
9489
9490         initialize: function (options, source) {
9491                 setOptions(this, options);
9492
9493                 this._source = source;
9494         },
9495
9496         onAdd: function (map) {
9497                 this._zoomAnimated = map._zoomAnimated;
9498
9499                 if (!this._container) {
9500                         this._initLayout();
9501                 }
9502
9503                 if (map._fadeAnimated) {
9504                         setOpacity(this._container, 0);
9505                 }
9506
9507                 clearTimeout(this._removeTimeout);
9508                 this.getPane().appendChild(this._container);
9509                 this.update();
9510
9511                 if (map._fadeAnimated) {
9512                         setOpacity(this._container, 1);
9513                 }
9514
9515                 this.bringToFront();
9516         },
9517
9518         onRemove: function (map) {
9519                 if (map._fadeAnimated) {
9520                         setOpacity(this._container, 0);
9521                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9522                 } else {
9523                         remove(this._container);
9524                 }
9525         },
9526
9527         // @namespace Popup
9528         // @method getLatLng: LatLng
9529         // Returns the geographical point of popup.
9530         getLatLng: function () {
9531                 return this._latlng;
9532         },
9533
9534         // @method setLatLng(latlng: LatLng): this
9535         // Sets the geographical point where the popup will open.
9536         setLatLng: function (latlng) {
9537                 this._latlng = toLatLng(latlng);
9538                 if (this._map) {
9539                         this._updatePosition();
9540                         this._adjustPan();
9541                 }
9542                 return this;
9543         },
9544
9545         // @method getContent: String|HTMLElement
9546         // Returns the content of the popup.
9547         getContent: function () {
9548                 return this._content;
9549         },
9550
9551         // @method setContent(htmlContent: String|HTMLElement|Function): this
9552         // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
9553         setContent: function (content) {
9554                 this._content = content;
9555                 this.update();
9556                 return this;
9557         },
9558
9559         // @method getElement: String|HTMLElement
9560         // Alias for [getContent()](#popup-getcontent)
9561         getElement: function () {
9562                 return this._container;
9563         },
9564
9565         // @method update: null
9566         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9567         update: function () {
9568                 if (!this._map) { return; }
9569
9570                 this._container.style.visibility = 'hidden';
9571
9572                 this._updateContent();
9573                 this._updateLayout();
9574                 this._updatePosition();
9575
9576                 this._container.style.visibility = '';
9577
9578                 this._adjustPan();
9579         },
9580
9581         getEvents: function () {
9582                 var events = {
9583                         zoom: this._updatePosition,
9584                         viewreset: this._updatePosition
9585                 };
9586
9587                 if (this._zoomAnimated) {
9588                         events.zoomanim = this._animateZoom;
9589                 }
9590                 return events;
9591         },
9592
9593         // @method isOpen: Boolean
9594         // Returns `true` when the popup is visible on the map.
9595         isOpen: function () {
9596                 return !!this._map && this._map.hasLayer(this);
9597         },
9598
9599         // @method bringToFront: this
9600         // Brings this popup in front of other popups (in the same map pane).
9601         bringToFront: function () {
9602                 if (this._map) {
9603                         toFront(this._container);
9604                 }
9605                 return this;
9606         },
9607
9608         // @method bringToBack: this
9609         // Brings this popup to the back of other popups (in the same map pane).
9610         bringToBack: function () {
9611                 if (this._map) {
9612                         toBack(this._container);
9613                 }
9614                 return this;
9615         },
9616
9617         _prepareOpen: function (parent, layer, latlng) {
9618                 if (!(layer instanceof Layer)) {
9619                         latlng = layer;
9620                         layer = parent;
9621                 }
9622
9623                 if (layer instanceof FeatureGroup) {
9624                         for (var id in parent._layers) {
9625                                 layer = parent._layers[id];
9626                                 break;
9627                         }
9628                 }
9629
9630                 if (!latlng) {
9631                         if (layer.getCenter) {
9632                                 latlng = layer.getCenter();
9633                         } else if (layer.getLatLng) {
9634                                 latlng = layer.getLatLng();
9635                         } else {
9636                                 throw new Error('Unable to get source layer LatLng.');
9637                         }
9638                 }
9639
9640                 // set overlay source to this layer
9641                 this._source = layer;
9642
9643                 // update the overlay (content, layout, ect...)
9644                 this.update();
9645
9646                 return latlng;
9647         },
9648
9649         _updateContent: function () {
9650                 if (!this._content) { return; }
9651
9652                 var node = this._contentNode;
9653                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9654
9655                 if (typeof content === 'string') {
9656                         node.innerHTML = content;
9657                 } else {
9658                         while (node.hasChildNodes()) {
9659                                 node.removeChild(node.firstChild);
9660                         }
9661                         node.appendChild(content);
9662                 }
9663                 this.fire('contentupdate');
9664         },
9665
9666         _updatePosition: function () {
9667                 if (!this._map) { return; }
9668
9669                 var pos = this._map.latLngToLayerPoint(this._latlng),
9670                     offset = toPoint(this.options.offset),
9671                     anchor = this._getAnchor();
9672
9673                 if (this._zoomAnimated) {
9674                         setPosition(this._container, pos.add(anchor));
9675                 } else {
9676                         offset = offset.add(pos).add(anchor);
9677                 }
9678
9679                 var bottom = this._containerBottom = -offset.y,
9680                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9681
9682                 // bottom position the popup in case the height of the popup changes (images loading etc)
9683                 this._container.style.bottom = bottom + 'px';
9684                 this._container.style.left = left + 'px';
9685         },
9686
9687         _getAnchor: function () {
9688                 return [0, 0];
9689         }
9690
9691 });
9692
9693 /*
9694  * @class Popup
9695  * @inherits DivOverlay
9696  * @aka L.Popup
9697  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9698  * open popups while making sure that only one popup is open at one time
9699  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9700  *
9701  * @example
9702  *
9703  * If you want to just bind a popup to marker click and then open it, it's really easy:
9704  *
9705  * ```js
9706  * marker.bindPopup(popupContent).openPopup();
9707  * ```
9708  * Path overlays like polylines also have a `bindPopup` method.
9709  * Here's a more complicated way to open a popup on a map:
9710  *
9711  * ```js
9712  * var popup = L.popup()
9713  *      .setLatLng(latlng)
9714  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9715  *      .openOn(map);
9716  * ```
9717  */
9718
9719
9720 // @namespace Popup
9721 var Popup = DivOverlay.extend({
9722
9723         // @section
9724         // @aka Popup options
9725         options: {
9726                 // @option maxWidth: Number = 300
9727                 // Max width of the popup, in pixels.
9728                 maxWidth: 300,
9729
9730                 // @option minWidth: Number = 50
9731                 // Min width of the popup, in pixels.
9732                 minWidth: 50,
9733
9734                 // @option maxHeight: Number = null
9735                 // If set, creates a scrollable container of the given height
9736                 // inside a popup if its content exceeds it.
9737                 maxHeight: null,
9738
9739                 // @option autoPan: Boolean = true
9740                 // Set it to `false` if you don't want the map to do panning animation
9741                 // to fit the opened popup.
9742                 autoPan: true,
9743
9744                 // @option autoPanPaddingTopLeft: Point = null
9745                 // The margin between the popup and the top left corner of the map
9746                 // view after autopanning was performed.
9747                 autoPanPaddingTopLeft: null,
9748
9749                 // @option autoPanPaddingBottomRight: Point = null
9750                 // The margin between the popup and the bottom right corner of the map
9751                 // view after autopanning was performed.
9752                 autoPanPaddingBottomRight: null,
9753
9754                 // @option autoPanPadding: Point = Point(5, 5)
9755                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9756                 autoPanPadding: [5, 5],
9757
9758                 // @option keepInView: Boolean = false
9759                 // Set it to `true` if you want to prevent users from panning the popup
9760                 // off of the screen while it is open.
9761                 keepInView: false,
9762
9763                 // @option closeButton: Boolean = true
9764                 // Controls the presence of a close button in the popup.
9765                 closeButton: true,
9766
9767                 // @option autoClose: Boolean = true
9768                 // Set it to `false` if you want to override the default behavior of
9769                 // the popup closing when another popup is opened.
9770                 autoClose: true,
9771
9772                 // @option closeOnEscapeKey: Boolean = true
9773                 // Set it to `false` if you want to override the default behavior of
9774                 // the ESC key for closing of the popup.
9775                 closeOnEscapeKey: true,
9776
9777                 // @option closeOnClick: Boolean = *
9778                 // Set it if you want to override the default behavior of the popup closing when user clicks
9779                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9780
9781                 // @option className: String = ''
9782                 // A custom CSS class name to assign to the popup.
9783                 className: ''
9784         },
9785
9786         // @namespace Popup
9787         // @method openOn(map: Map): this
9788         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9789         openOn: function (map) {
9790                 map.openPopup(this);
9791                 return this;
9792         },
9793
9794         onAdd: function (map) {
9795                 DivOverlay.prototype.onAdd.call(this, map);
9796
9797                 // @namespace Map
9798                 // @section Popup events
9799                 // @event popupopen: PopupEvent
9800                 // Fired when a popup is opened in the map
9801                 map.fire('popupopen', {popup: this});
9802
9803                 if (this._source) {
9804                         // @namespace Layer
9805                         // @section Popup events
9806                         // @event popupopen: PopupEvent
9807                         // Fired when a popup bound to this layer is opened
9808                         this._source.fire('popupopen', {popup: this}, true);
9809                         // For non-path layers, we toggle the popup when clicking
9810                         // again the layer, so prevent the map to reopen it.
9811                         if (!(this._source instanceof Path)) {
9812                                 this._source.on('preclick', stopPropagation);
9813                         }
9814                 }
9815         },
9816
9817         onRemove: function (map) {
9818                 DivOverlay.prototype.onRemove.call(this, map);
9819
9820                 // @namespace Map
9821                 // @section Popup events
9822                 // @event popupclose: PopupEvent
9823                 // Fired when a popup in the map is closed
9824                 map.fire('popupclose', {popup: this});
9825
9826                 if (this._source) {
9827                         // @namespace Layer
9828                         // @section Popup events
9829                         // @event popupclose: PopupEvent
9830                         // Fired when a popup bound to this layer is closed
9831                         this._source.fire('popupclose', {popup: this}, true);
9832                         if (!(this._source instanceof Path)) {
9833                                 this._source.off('preclick', stopPropagation);
9834                         }
9835                 }
9836         },
9837
9838         getEvents: function () {
9839                 var events = DivOverlay.prototype.getEvents.call(this);
9840
9841                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9842                         events.preclick = this._close;
9843                 }
9844
9845                 if (this.options.keepInView) {
9846                         events.moveend = this._adjustPan;
9847                 }
9848
9849                 return events;
9850         },
9851
9852         _close: function () {
9853                 if (this._map) {
9854                         this._map.closePopup(this);
9855                 }
9856         },
9857
9858         _initLayout: function () {
9859                 var prefix = 'leaflet-popup',
9860                     container = this._container = create$1('div',
9861                         prefix + ' ' + (this.options.className || '') +
9862                         ' leaflet-zoom-animated');
9863
9864                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9865                 this._contentNode = create$1('div', prefix + '-content', wrapper);
9866
9867                 disableClickPropagation(wrapper);
9868                 disableScrollPropagation(this._contentNode);
9869                 on(wrapper, 'contextmenu', stopPropagation);
9870
9871                 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9872                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9873
9874                 if (this.options.closeButton) {
9875                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9876                         closeButton.href = '#close';
9877                         closeButton.innerHTML = '&#215;';
9878
9879                         on(closeButton, 'click', this._onCloseButtonClick, this);
9880                 }
9881         },
9882
9883         _updateLayout: function () {
9884                 var container = this._contentNode,
9885                     style = container.style;
9886
9887                 style.width = '';
9888                 style.whiteSpace = 'nowrap';
9889
9890                 var width = container.offsetWidth;
9891                 width = Math.min(width, this.options.maxWidth);
9892                 width = Math.max(width, this.options.minWidth);
9893
9894                 style.width = (width + 1) + 'px';
9895                 style.whiteSpace = '';
9896
9897                 style.height = '';
9898
9899                 var height = container.offsetHeight,
9900                     maxHeight = this.options.maxHeight,
9901                     scrolledClass = 'leaflet-popup-scrolled';
9902
9903                 if (maxHeight && height > maxHeight) {
9904                         style.height = maxHeight + 'px';
9905                         addClass(container, scrolledClass);
9906                 } else {
9907                         removeClass(container, scrolledClass);
9908                 }
9909
9910                 this._containerWidth = this._container.offsetWidth;
9911         },
9912
9913         _animateZoom: function (e) {
9914                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9915                     anchor = this._getAnchor();
9916                 setPosition(this._container, pos.add(anchor));
9917         },
9918
9919         _adjustPan: function () {
9920                 if (!this.options.autoPan) { return; }
9921                 if (this._map._panAnim) { this._map._panAnim.stop(); }
9922
9923                 var map = this._map,
9924                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9925                     containerHeight = this._container.offsetHeight + marginBottom,
9926                     containerWidth = this._containerWidth,
9927                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9928
9929                 layerPos._add(getPosition(this._container));
9930
9931                 var containerPos = map.layerPointToContainerPoint(layerPos),
9932                     padding = toPoint(this.options.autoPanPadding),
9933                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9934                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9935                     size = map.getSize(),
9936                     dx = 0,
9937                     dy = 0;
9938
9939                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9940                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9941                 }
9942                 if (containerPos.x - dx - paddingTL.x < 0) { // left
9943                         dx = containerPos.x - paddingTL.x;
9944                 }
9945                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9946                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9947                 }
9948                 if (containerPos.y - dy - paddingTL.y < 0) { // top
9949                         dy = containerPos.y - paddingTL.y;
9950                 }
9951
9952                 // @namespace Map
9953                 // @section Popup events
9954                 // @event autopanstart: Event
9955                 // Fired when the map starts autopanning when opening a popup.
9956                 if (dx || dy) {
9957                         map
9958                             .fire('autopanstart')
9959                             .panBy([dx, dy]);
9960                 }
9961         },
9962
9963         _onCloseButtonClick: function (e) {
9964                 this._close();
9965                 stop(e);
9966         },
9967
9968         _getAnchor: function () {
9969                 // Where should we anchor the popup on the source layer?
9970                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9971         }
9972
9973 });
9974
9975 // @namespace Popup
9976 // @factory L.popup(options?: Popup options, source?: Layer)
9977 // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
9978 var popup = function (options, source) {
9979         return new Popup(options, source);
9980 };
9981
9982
9983 /* @namespace Map
9984  * @section Interaction Options
9985  * @option closePopupOnClick: Boolean = true
9986  * Set it to `false` if you don't want popups to close when user clicks the map.
9987  */
9988 Map.mergeOptions({
9989         closePopupOnClick: true
9990 });
9991
9992
9993 // @namespace Map
9994 // @section Methods for Layers and Controls
9995 Map.include({
9996         // @method openPopup(popup: Popup): this
9997         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9998         // @alternative
9999         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
10000         // Creates a popup with the specified content and options and opens it in the given point on a map.
10001         openPopup: function (popup, latlng, options) {
10002                 if (!(popup instanceof Popup)) {
10003                         popup = new Popup(options).setContent(popup);
10004                 }
10005
10006                 if (latlng) {
10007                         popup.setLatLng(latlng);
10008                 }
10009
10010                 if (this.hasLayer(popup)) {
10011                         return this;
10012                 }
10013
10014                 if (this._popup && this._popup.options.autoClose) {
10015                         this.closePopup();
10016                 }
10017
10018                 this._popup = popup;
10019                 return this.addLayer(popup);
10020         },
10021
10022         // @method closePopup(popup?: Popup): this
10023         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
10024         closePopup: function (popup) {
10025                 if (!popup || popup === this._popup) {
10026                         popup = this._popup;
10027                         this._popup = null;
10028                 }
10029                 if (popup) {
10030                         this.removeLayer(popup);
10031                 }
10032                 return this;
10033         }
10034 });
10035
10036 /*
10037  * @namespace Layer
10038  * @section Popup methods example
10039  *
10040  * All layers share a set of methods convenient for binding popups to it.
10041  *
10042  * ```js
10043  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
10044  * layer.openPopup();
10045  * layer.closePopup();
10046  * ```
10047  *
10048  * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
10049  */
10050
10051 // @section Popup methods
10052 Layer.include({
10053
10054         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10055         // Binds a popup to the layer with the passed `content` and sets up the
10056         // necessary event listeners. If a `Function` is passed it will receive
10057         // the layer as the first argument and should return a `String` or `HTMLElement`.
10058         bindPopup: function (content, options) {
10059
10060                 if (content instanceof Popup) {
10061                         setOptions(content, options);
10062                         this._popup = content;
10063                         content._source = this;
10064                 } else {
10065                         if (!this._popup || options) {
10066                                 this._popup = new Popup(options, this);
10067                         }
10068                         this._popup.setContent(content);
10069                 }
10070
10071                 if (!this._popupHandlersAdded) {
10072                         this.on({
10073                                 click: this._openPopup,
10074                                 keypress: this._onKeyPress,
10075                                 remove: this.closePopup,
10076                                 move: this._movePopup
10077                         });
10078                         this._popupHandlersAdded = true;
10079                 }
10080
10081                 return this;
10082         },
10083
10084         // @method unbindPopup(): this
10085         // Removes the popup previously bound with `bindPopup`.
10086         unbindPopup: function () {
10087                 if (this._popup) {
10088                         this.off({
10089                                 click: this._openPopup,
10090                                 keypress: this._onKeyPress,
10091                                 remove: this.closePopup,
10092                                 move: this._movePopup
10093                         });
10094                         this._popupHandlersAdded = false;
10095                         this._popup = null;
10096                 }
10097                 return this;
10098         },
10099
10100         // @method openPopup(latlng?: LatLng): this
10101         // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10102         openPopup: function (layer, latlng) {
10103                 if (this._popup && this._map) {
10104                         latlng = this._popup._prepareOpen(this, layer, latlng);
10105
10106                         // open the popup on the map
10107                         this._map.openPopup(this._popup, latlng);
10108                 }
10109
10110                 return this;
10111         },
10112
10113         // @method closePopup(): this
10114         // Closes the popup bound to this layer if it is open.
10115         closePopup: function () {
10116                 if (this._popup) {
10117                         this._popup._close();
10118                 }
10119                 return this;
10120         },
10121
10122         // @method togglePopup(): this
10123         // Opens or closes the popup bound to this layer depending on its current state.
10124         togglePopup: function (target) {
10125                 if (this._popup) {
10126                         if (this._popup._map) {
10127                                 this.closePopup();
10128                         } else {
10129                                 this.openPopup(target);
10130                         }
10131                 }
10132                 return this;
10133         },
10134
10135         // @method isPopupOpen(): boolean
10136         // Returns `true` if the popup bound to this layer is currently open.
10137         isPopupOpen: function () {
10138                 return (this._popup ? this._popup.isOpen() : false);
10139         },
10140
10141         // @method setPopupContent(content: String|HTMLElement|Popup): this
10142         // Sets the content of the popup bound to this layer.
10143         setPopupContent: function (content) {
10144                 if (this._popup) {
10145                         this._popup.setContent(content);
10146                 }
10147                 return this;
10148         },
10149
10150         // @method getPopup(): Popup
10151         // Returns the popup bound to this layer.
10152         getPopup: function () {
10153                 return this._popup;
10154         },
10155
10156         _openPopup: function (e) {
10157                 var layer = e.layer || e.target;
10158
10159                 if (!this._popup) {
10160                         return;
10161                 }
10162
10163                 if (!this._map) {
10164                         return;
10165                 }
10166
10167                 // prevent map click
10168                 stop(e);
10169
10170                 // if this inherits from Path its a vector and we can just
10171                 // open the popup at the new location
10172                 if (layer instanceof Path) {
10173                         this.openPopup(e.layer || e.target, e.latlng);
10174                         return;
10175                 }
10176
10177                 // otherwise treat it like a marker and figure out
10178                 // if we should toggle it open/closed
10179                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10180                         this.closePopup();
10181                 } else {
10182                         this.openPopup(layer, e.latlng);
10183                 }
10184         },
10185
10186         _movePopup: function (e) {
10187                 this._popup.setLatLng(e.latlng);
10188         },
10189
10190         _onKeyPress: function (e) {
10191                 if (e.originalEvent.keyCode === 13) {
10192                         this._openPopup(e);
10193                 }
10194         }
10195 });
10196
10197 /*
10198  * @class Tooltip
10199  * @inherits DivOverlay
10200  * @aka L.Tooltip
10201  * Used to display small texts on top of map layers.
10202  *
10203  * @example
10204  *
10205  * ```js
10206  * marker.bindTooltip("my tooltip text").openTooltip();
10207  * ```
10208  * Note about tooltip offset. Leaflet takes two options in consideration
10209  * for computing tooltip offsetting:
10210  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10211  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
10212  *   move it to the bottom. Negatives will move to the left and top.
10213  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10214  *   should adapt this value if you use a custom icon.
10215  */
10216
10217
10218 // @namespace Tooltip
10219 var Tooltip = DivOverlay.extend({
10220
10221         // @section
10222         // @aka Tooltip options
10223         options: {
10224                 // @option pane: String = 'tooltipPane'
10225                 // `Map pane` where the tooltip will be added.
10226                 pane: 'tooltipPane',
10227
10228                 // @option offset: Point = Point(0, 0)
10229                 // Optional offset of the tooltip position.
10230                 offset: [0, 0],
10231
10232                 // @option direction: String = 'auto'
10233                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10234                 // `top`, `bottom`, `center`, `auto`.
10235                 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10236                 // position on the map.
10237                 direction: 'auto',
10238
10239                 // @option permanent: Boolean = false
10240                 // Whether to open the tooltip permanently or only on mouseover.
10241                 permanent: false,
10242
10243                 // @option sticky: Boolean = false
10244                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10245                 sticky: false,
10246
10247                 // @option interactive: Boolean = false
10248                 // If true, the tooltip will listen to the feature events.
10249                 interactive: false,
10250
10251                 // @option opacity: Number = 0.9
10252                 // Tooltip container opacity.
10253                 opacity: 0.9
10254         },
10255
10256         onAdd: function (map) {
10257                 DivOverlay.prototype.onAdd.call(this, map);
10258                 this.setOpacity(this.options.opacity);
10259
10260                 // @namespace Map
10261                 // @section Tooltip events
10262                 // @event tooltipopen: TooltipEvent
10263                 // Fired when a tooltip is opened in the map.
10264                 map.fire('tooltipopen', {tooltip: this});
10265
10266                 if (this._source) {
10267                         // @namespace Layer
10268                         // @section Tooltip events
10269                         // @event tooltipopen: TooltipEvent
10270                         // Fired when a tooltip bound to this layer is opened.
10271                         this._source.fire('tooltipopen', {tooltip: this}, true);
10272                 }
10273         },
10274
10275         onRemove: function (map) {
10276                 DivOverlay.prototype.onRemove.call(this, map);
10277
10278                 // @namespace Map
10279                 // @section Tooltip events
10280                 // @event tooltipclose: TooltipEvent
10281                 // Fired when a tooltip in the map is closed.
10282                 map.fire('tooltipclose', {tooltip: this});
10283
10284                 if (this._source) {
10285                         // @namespace Layer
10286                         // @section Tooltip events
10287                         // @event tooltipclose: TooltipEvent
10288                         // Fired when a tooltip bound to this layer is closed.
10289                         this._source.fire('tooltipclose', {tooltip: this}, true);
10290                 }
10291         },
10292
10293         getEvents: function () {
10294                 var events = DivOverlay.prototype.getEvents.call(this);
10295
10296                 if (touch && !this.options.permanent) {
10297                         events.preclick = this._close;
10298                 }
10299
10300                 return events;
10301         },
10302
10303         _close: function () {
10304                 if (this._map) {
10305                         this._map.closeTooltip(this);
10306                 }
10307         },
10308
10309         _initLayout: function () {
10310                 var prefix = 'leaflet-tooltip',
10311                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10312
10313                 this._contentNode = this._container = create$1('div', className);
10314         },
10315
10316         _updateLayout: function () {},
10317
10318         _adjustPan: function () {},
10319
10320         _setPosition: function (pos) {
10321                 var map = this._map,
10322                     container = this._container,
10323                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
10324                     tooltipPoint = map.layerPointToContainerPoint(pos),
10325                     direction = this.options.direction,
10326                     tooltipWidth = container.offsetWidth,
10327                     tooltipHeight = container.offsetHeight,
10328                     offset = toPoint(this.options.offset),
10329                     anchor = this._getAnchor();
10330
10331                 if (direction === 'top') {
10332                         pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10333                 } else if (direction === 'bottom') {
10334                         pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10335                 } else if (direction === 'center') {
10336                         pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10337                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10338                         direction = 'right';
10339                         pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10340                 } else {
10341                         direction = 'left';
10342                         pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10343                 }
10344
10345                 removeClass(container, 'leaflet-tooltip-right');
10346                 removeClass(container, 'leaflet-tooltip-left');
10347                 removeClass(container, 'leaflet-tooltip-top');
10348                 removeClass(container, 'leaflet-tooltip-bottom');
10349                 addClass(container, 'leaflet-tooltip-' + direction);
10350                 setPosition(container, pos);
10351         },
10352
10353         _updatePosition: function () {
10354                 var pos = this._map.latLngToLayerPoint(this._latlng);
10355                 this._setPosition(pos);
10356         },
10357
10358         setOpacity: function (opacity) {
10359                 this.options.opacity = opacity;
10360
10361                 if (this._container) {
10362                         setOpacity(this._container, opacity);
10363                 }
10364         },
10365
10366         _animateZoom: function (e) {
10367                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10368                 this._setPosition(pos);
10369         },
10370
10371         _getAnchor: function () {
10372                 // Where should we anchor the tooltip on the source layer?
10373                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10374         }
10375
10376 });
10377
10378 // @namespace Tooltip
10379 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10380 // Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
10381 var tooltip = function (options, source) {
10382         return new Tooltip(options, source);
10383 };
10384
10385 // @namespace Map
10386 // @section Methods for Layers and Controls
10387 Map.include({
10388
10389         // @method openTooltip(tooltip: Tooltip): this
10390         // Opens the specified tooltip.
10391         // @alternative
10392         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10393         // Creates a tooltip with the specified content and options and open it.
10394         openTooltip: function (tooltip, latlng, options) {
10395                 if (!(tooltip instanceof Tooltip)) {
10396                         tooltip = new Tooltip(options).setContent(tooltip);
10397                 }
10398
10399                 if (latlng) {
10400                         tooltip.setLatLng(latlng);
10401                 }
10402
10403                 if (this.hasLayer(tooltip)) {
10404                         return this;
10405                 }
10406
10407                 return this.addLayer(tooltip);
10408         },
10409
10410         // @method closeTooltip(tooltip?: Tooltip): this
10411         // Closes the tooltip given as parameter.
10412         closeTooltip: function (tooltip) {
10413                 if (tooltip) {
10414                         this.removeLayer(tooltip);
10415                 }
10416                 return this;
10417         }
10418
10419 });
10420
10421 /*
10422  * @namespace Layer
10423  * @section Tooltip methods example
10424  *
10425  * All layers share a set of methods convenient for binding tooltips to it.
10426  *
10427  * ```js
10428  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10429  * layer.openTooltip();
10430  * layer.closeTooltip();
10431  * ```
10432  */
10433
10434 // @section Tooltip methods
10435 Layer.include({
10436
10437         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10438         // Binds a tooltip to the layer with the passed `content` and sets up the
10439         // necessary event listeners. If a `Function` is passed it will receive
10440         // the layer as the first argument and should return a `String` or `HTMLElement`.
10441         bindTooltip: function (content, options) {
10442
10443                 if (content instanceof Tooltip) {
10444                         setOptions(content, options);
10445                         this._tooltip = content;
10446                         content._source = this;
10447                 } else {
10448                         if (!this._tooltip || options) {
10449                                 this._tooltip = new Tooltip(options, this);
10450                         }
10451                         this._tooltip.setContent(content);
10452
10453                 }
10454
10455                 this._initTooltipInteractions();
10456
10457                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10458                         this.openTooltip();
10459                 }
10460
10461                 return this;
10462         },
10463
10464         // @method unbindTooltip(): this
10465         // Removes the tooltip previously bound with `bindTooltip`.
10466         unbindTooltip: function () {
10467                 if (this._tooltip) {
10468                         this._initTooltipInteractions(true);
10469                         this.closeTooltip();
10470                         this._tooltip = null;
10471                 }
10472                 return this;
10473         },
10474
10475         _initTooltipInteractions: function (remove$$1) {
10476                 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10477                 var onOff = remove$$1 ? 'off' : 'on',
10478                     events = {
10479                         remove: this.closeTooltip,
10480                         move: this._moveTooltip
10481                     };
10482                 if (!this._tooltip.options.permanent) {
10483                         events.mouseover = this._openTooltip;
10484                         events.mouseout = this.closeTooltip;
10485                         if (this._tooltip.options.sticky) {
10486                                 events.mousemove = this._moveTooltip;
10487                         }
10488                         if (touch) {
10489                                 events.click = this._openTooltip;
10490                         }
10491                 } else {
10492                         events.add = this._openTooltip;
10493                 }
10494                 this[onOff](events);
10495                 this._tooltipHandlersAdded = !remove$$1;
10496         },
10497
10498         // @method openTooltip(latlng?: LatLng): this
10499         // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10500         openTooltip: function (layer, latlng) {
10501                 if (this._tooltip && this._map) {
10502                         latlng = this._tooltip._prepareOpen(this, layer, latlng);
10503
10504                         // open the tooltip on the map
10505                         this._map.openTooltip(this._tooltip, latlng);
10506
10507                         // Tooltip container may not be defined if not permanent and never
10508                         // opened.
10509                         if (this._tooltip.options.interactive && this._tooltip._container) {
10510                                 addClass(this._tooltip._container, 'leaflet-clickable');
10511                                 this.addInteractiveTarget(this._tooltip._container);
10512                         }
10513                 }
10514
10515                 return this;
10516         },
10517
10518         // @method closeTooltip(): this
10519         // Closes the tooltip bound to this layer if it is open.
10520         closeTooltip: function () {
10521                 if (this._tooltip) {
10522                         this._tooltip._close();
10523                         if (this._tooltip.options.interactive && this._tooltip._container) {
10524                                 removeClass(this._tooltip._container, 'leaflet-clickable');
10525                                 this.removeInteractiveTarget(this._tooltip._container);
10526                         }
10527                 }
10528                 return this;
10529         },
10530
10531         // @method toggleTooltip(): this
10532         // Opens or closes the tooltip bound to this layer depending on its current state.
10533         toggleTooltip: function (target) {
10534                 if (this._tooltip) {
10535                         if (this._tooltip._map) {
10536                                 this.closeTooltip();
10537                         } else {
10538                                 this.openTooltip(target);
10539                         }
10540                 }
10541                 return this;
10542         },
10543
10544         // @method isTooltipOpen(): boolean
10545         // Returns `true` if the tooltip bound to this layer is currently open.
10546         isTooltipOpen: function () {
10547                 return this._tooltip.isOpen();
10548         },
10549
10550         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10551         // Sets the content of the tooltip bound to this layer.
10552         setTooltipContent: function (content) {
10553                 if (this._tooltip) {
10554                         this._tooltip.setContent(content);
10555                 }
10556                 return this;
10557         },
10558
10559         // @method getTooltip(): Tooltip
10560         // Returns the tooltip bound to this layer.
10561         getTooltip: function () {
10562                 return this._tooltip;
10563         },
10564
10565         _openTooltip: function (e) {
10566                 var layer = e.layer || e.target;
10567
10568                 if (!this._tooltip || !this._map) {
10569                         return;
10570                 }
10571                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10572         },
10573
10574         _moveTooltip: function (e) {
10575                 var latlng = e.latlng, containerPoint, layerPoint;
10576                 if (this._tooltip.options.sticky && e.originalEvent) {
10577                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10578                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10579                         latlng = this._map.layerPointToLatLng(layerPoint);
10580                 }
10581                 this._tooltip.setLatLng(latlng);
10582         }
10583 });
10584
10585 /*
10586  * @class DivIcon
10587  * @aka L.DivIcon
10588  * @inherits Icon
10589  *
10590  * Represents a lightweight icon for markers that uses a simple `<div>`
10591  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10592  *
10593  * @example
10594  * ```js
10595  * var myIcon = L.divIcon({className: 'my-div-icon'});
10596  * // you can set .my-div-icon styles in CSS
10597  *
10598  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10599  * ```
10600  *
10601  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10602  */
10603
10604 var DivIcon = Icon.extend({
10605         options: {
10606                 // @section
10607                 // @aka DivIcon options
10608                 iconSize: [12, 12], // also can be set through CSS
10609
10610                 // iconAnchor: (Point),
10611                 // popupAnchor: (Point),
10612
10613                 // @option html: String|HTMLElement = ''
10614                 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10615                 // an instance of `HTMLElement`.
10616                 html: false,
10617
10618                 // @option bgPos: Point = [0, 0]
10619                 // Optional relative position of the background, in pixels
10620                 bgPos: null,
10621
10622                 className: 'leaflet-div-icon'
10623         },
10624
10625         createIcon: function (oldIcon) {
10626                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10627                     options = this.options;
10628
10629                 if (options.html instanceof Element) {
10630                         empty(div);
10631                         div.appendChild(options.html);
10632                 } else {
10633                         div.innerHTML = options.html !== false ? options.html : '';
10634                 }
10635
10636                 if (options.bgPos) {
10637                         var bgPos = toPoint(options.bgPos);
10638                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10639                 }
10640                 this._setIconStyles(div, 'icon');
10641
10642                 return div;
10643         },
10644
10645         createShadow: function () {
10646                 return null;
10647         }
10648 });
10649
10650 // @factory L.divIcon(options: DivIcon options)
10651 // Creates a `DivIcon` instance with the given options.
10652 function divIcon(options) {
10653         return new DivIcon(options);
10654 }
10655
10656 Icon.Default = IconDefault;
10657
10658 /*
10659  * @class GridLayer
10660  * @inherits Layer
10661  * @aka L.GridLayer
10662  *
10663  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10664  * 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.
10665  *
10666  *
10667  * @section Synchronous usage
10668  * @example
10669  *
10670  * 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.
10671  *
10672  * ```js
10673  * var CanvasLayer = L.GridLayer.extend({
10674  *     createTile: function(coords){
10675  *         // create a <canvas> element for drawing
10676  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10677  *
10678  *         // setup tile width and height according to the options
10679  *         var size = this.getTileSize();
10680  *         tile.width = size.x;
10681  *         tile.height = size.y;
10682  *
10683  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10684  *         var ctx = tile.getContext('2d');
10685  *
10686  *         // return the tile so it can be rendered on screen
10687  *         return tile;
10688  *     }
10689  * });
10690  * ```
10691  *
10692  * @section Asynchronous usage
10693  * @example
10694  *
10695  * 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.
10696  *
10697  * ```js
10698  * var CanvasLayer = L.GridLayer.extend({
10699  *     createTile: function(coords, done){
10700  *         var error;
10701  *
10702  *         // create a <canvas> element for drawing
10703  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10704  *
10705  *         // setup tile width and height according to the options
10706  *         var size = this.getTileSize();
10707  *         tile.width = size.x;
10708  *         tile.height = size.y;
10709  *
10710  *         // draw something asynchronously and pass the tile to the done() callback
10711  *         setTimeout(function() {
10712  *             done(error, tile);
10713  *         }, 1000);
10714  *
10715  *         return tile;
10716  *     }
10717  * });
10718  * ```
10719  *
10720  * @section
10721  */
10722
10723
10724 var GridLayer = Layer.extend({
10725
10726         // @section
10727         // @aka GridLayer options
10728         options: {
10729                 // @option tileSize: Number|Point = 256
10730                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10731                 tileSize: 256,
10732
10733                 // @option opacity: Number = 1.0
10734                 // Opacity of the tiles. Can be used in the `createTile()` function.
10735                 opacity: 1,
10736
10737                 // @option updateWhenIdle: Boolean = (depends)
10738                 // Load new tiles only when panning ends.
10739                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10740                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10741                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10742                 updateWhenIdle: mobile,
10743
10744                 // @option updateWhenZooming: Boolean = true
10745                 // 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.
10746                 updateWhenZooming: true,
10747
10748                 // @option updateInterval: Number = 200
10749                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10750                 updateInterval: 200,
10751
10752                 // @option zIndex: Number = 1
10753                 // The explicit zIndex of the tile layer.
10754                 zIndex: 1,
10755
10756                 // @option bounds: LatLngBounds = undefined
10757                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10758                 bounds: null,
10759
10760                 // @option minZoom: Number = 0
10761                 // The minimum zoom level down to which this layer will be displayed (inclusive).
10762                 minZoom: 0,
10763
10764                 // @option maxZoom: Number = undefined
10765                 // The maximum zoom level up to which this layer will be displayed (inclusive).
10766                 maxZoom: undefined,
10767
10768                 // @option maxNativeZoom: Number = undefined
10769                 // Maximum zoom number the tile source has available. If it is specified,
10770                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10771                 // from `maxNativeZoom` level and auto-scaled.
10772                 maxNativeZoom: undefined,
10773
10774                 // @option minNativeZoom: Number = undefined
10775                 // Minimum zoom number the tile source has available. If it is specified,
10776                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10777                 // from `minNativeZoom` level and auto-scaled.
10778                 minNativeZoom: undefined,
10779
10780                 // @option noWrap: Boolean = false
10781                 // Whether the layer is wrapped around the antimeridian. If `true`, the
10782                 // GridLayer will only be displayed once at low zoom levels. Has no
10783                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10784                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10785                 // tiles outside the CRS limits.
10786                 noWrap: false,
10787
10788                 // @option pane: String = 'tilePane'
10789                 // `Map pane` where the grid layer will be added.
10790                 pane: 'tilePane',
10791
10792                 // @option className: String = ''
10793                 // A custom class name to assign to the tile layer. Empty by default.
10794                 className: '',
10795
10796                 // @option keepBuffer: Number = 2
10797                 // When panning the map, keep this many rows and columns of tiles before unloading them.
10798                 keepBuffer: 2
10799         },
10800
10801         initialize: function (options) {
10802                 setOptions(this, options);
10803         },
10804
10805         onAdd: function () {
10806                 this._initContainer();
10807
10808                 this._levels = {};
10809                 this._tiles = {};
10810
10811                 this._resetView();
10812                 this._update();
10813         },
10814
10815         beforeAdd: function (map) {
10816                 map._addZoomLimit(this);
10817         },
10818
10819         onRemove: function (map) {
10820                 this._removeAllTiles();
10821                 remove(this._container);
10822                 map._removeZoomLimit(this);
10823                 this._container = null;
10824                 this._tileZoom = undefined;
10825         },
10826
10827         // @method bringToFront: this
10828         // Brings the tile layer to the top of all tile layers.
10829         bringToFront: function () {
10830                 if (this._map) {
10831                         toFront(this._container);
10832                         this._setAutoZIndex(Math.max);
10833                 }
10834                 return this;
10835         },
10836
10837         // @method bringToBack: this
10838         // Brings the tile layer to the bottom of all tile layers.
10839         bringToBack: function () {
10840                 if (this._map) {
10841                         toBack(this._container);
10842                         this._setAutoZIndex(Math.min);
10843                 }
10844                 return this;
10845         },
10846
10847         // @method getContainer: HTMLElement
10848         // Returns the HTML element that contains the tiles for this layer.
10849         getContainer: function () {
10850                 return this._container;
10851         },
10852
10853         // @method setOpacity(opacity: Number): this
10854         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10855         setOpacity: function (opacity) {
10856                 this.options.opacity = opacity;
10857                 this._updateOpacity();
10858                 return this;
10859         },
10860
10861         // @method setZIndex(zIndex: Number): this
10862         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10863         setZIndex: function (zIndex) {
10864                 this.options.zIndex = zIndex;
10865                 this._updateZIndex();
10866
10867                 return this;
10868         },
10869
10870         // @method isLoading: Boolean
10871         // Returns `true` if any tile in the grid layer has not finished loading.
10872         isLoading: function () {
10873                 return this._loading;
10874         },
10875
10876         // @method redraw: this
10877         // Causes the layer to clear all the tiles and request them again.
10878         redraw: function () {
10879                 if (this._map) {
10880                         this._removeAllTiles();
10881                         this._update();
10882                 }
10883                 return this;
10884         },
10885
10886         getEvents: function () {
10887                 var events = {
10888                         viewprereset: this._invalidateAll,
10889                         viewreset: this._resetView,
10890                         zoom: this._resetView,
10891                         moveend: this._onMoveEnd
10892                 };
10893
10894                 if (!this.options.updateWhenIdle) {
10895                         // update tiles on move, but not more often than once per given interval
10896                         if (!this._onMove) {
10897                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10898                         }
10899
10900                         events.move = this._onMove;
10901                 }
10902
10903                 if (this._zoomAnimated) {
10904                         events.zoomanim = this._animateZoom;
10905                 }
10906
10907                 return events;
10908         },
10909
10910         // @section Extension methods
10911         // Layers extending `GridLayer` shall reimplement the following method.
10912         // @method createTile(coords: Object, done?: Function): HTMLElement
10913         // Called only internally, must be overridden by classes extending `GridLayer`.
10914         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10915         // is specified, it must be called when the tile has finished loading and drawing.
10916         createTile: function () {
10917                 return document.createElement('div');
10918         },
10919
10920         // @section
10921         // @method getTileSize: Point
10922         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10923         getTileSize: function () {
10924                 var s = this.options.tileSize;
10925                 return s instanceof Point ? s : new Point(s, s);
10926         },
10927
10928         _updateZIndex: function () {
10929                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10930                         this._container.style.zIndex = this.options.zIndex;
10931                 }
10932         },
10933
10934         _setAutoZIndex: function (compare) {
10935                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10936
10937                 var layers = this.getPane().children,
10938                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10939
10940                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10941
10942                         zIndex = layers[i].style.zIndex;
10943
10944                         if (layers[i] !== this._container && zIndex) {
10945                                 edgeZIndex = compare(edgeZIndex, +zIndex);
10946                         }
10947                 }
10948
10949                 if (isFinite(edgeZIndex)) {
10950                         this.options.zIndex = edgeZIndex + compare(-1, 1);
10951                         this._updateZIndex();
10952                 }
10953         },
10954
10955         _updateOpacity: function () {
10956                 if (!this._map) { return; }
10957
10958                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10959                 if (ielt9) { return; }
10960
10961                 setOpacity(this._container, this.options.opacity);
10962
10963                 var now = +new Date(),
10964                     nextFrame = false,
10965                     willPrune = false;
10966
10967                 for (var key in this._tiles) {
10968                         var tile = this._tiles[key];
10969                         if (!tile.current || !tile.loaded) { continue; }
10970
10971                         var fade = Math.min(1, (now - tile.loaded) / 200);
10972
10973                         setOpacity(tile.el, fade);
10974                         if (fade < 1) {
10975                                 nextFrame = true;
10976                         } else {
10977                                 if (tile.active) {
10978                                         willPrune = true;
10979                                 } else {
10980                                         this._onOpaqueTile(tile);
10981                                 }
10982                                 tile.active = true;
10983                         }
10984                 }
10985
10986                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10987
10988                 if (nextFrame) {
10989                         cancelAnimFrame(this._fadeFrame);
10990                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10991                 }
10992         },
10993
10994         _onOpaqueTile: falseFn,
10995
10996         _initContainer: function () {
10997                 if (this._container) { return; }
10998
10999                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
11000                 this._updateZIndex();
11001
11002                 if (this.options.opacity < 1) {
11003                         this._updateOpacity();
11004                 }
11005
11006                 this.getPane().appendChild(this._container);
11007         },
11008
11009         _updateLevels: function () {
11010
11011                 var zoom = this._tileZoom,
11012                     maxZoom = this.options.maxZoom;
11013
11014                 if (zoom === undefined) { return undefined; }
11015
11016                 for (var z in this._levels) {
11017                         if (this._levels[z].el.children.length || z === zoom) {
11018                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
11019                                 this._onUpdateLevel(z);
11020                         } else {
11021                                 remove(this._levels[z].el);
11022                                 this._removeTilesAtZoom(z);
11023                                 this._onRemoveLevel(z);
11024                                 delete this._levels[z];
11025                         }
11026                 }
11027
11028                 var level = this._levels[zoom],
11029                     map = this._map;
11030
11031                 if (!level) {
11032                         level = this._levels[zoom] = {};
11033
11034                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
11035                         level.el.style.zIndex = maxZoom;
11036
11037                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
11038                         level.zoom = zoom;
11039
11040                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
11041
11042                         // force the browser to consider the newly added element for transition
11043                         falseFn(level.el.offsetWidth);
11044
11045                         this._onCreateLevel(level);
11046                 }
11047
11048                 this._level = level;
11049
11050                 return level;
11051         },
11052
11053         _onUpdateLevel: falseFn,
11054
11055         _onRemoveLevel: falseFn,
11056
11057         _onCreateLevel: falseFn,
11058
11059         _pruneTiles: function () {
11060                 if (!this._map) {
11061                         return;
11062                 }
11063
11064                 var key, tile;
11065
11066                 var zoom = this._map.getZoom();
11067                 if (zoom > this.options.maxZoom ||
11068                         zoom < this.options.minZoom) {
11069                         this._removeAllTiles();
11070                         return;
11071                 }
11072
11073                 for (key in this._tiles) {
11074                         tile = this._tiles[key];
11075                         tile.retain = tile.current;
11076                 }
11077
11078                 for (key in this._tiles) {
11079                         tile = this._tiles[key];
11080                         if (tile.current && !tile.active) {
11081                                 var coords = tile.coords;
11082                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11083                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11084                                 }
11085                         }
11086                 }
11087
11088                 for (key in this._tiles) {
11089                         if (!this._tiles[key].retain) {
11090                                 this._removeTile(key);
11091                         }
11092                 }
11093         },
11094
11095         _removeTilesAtZoom: function (zoom) {
11096                 for (var key in this._tiles) {
11097                         if (this._tiles[key].coords.z !== zoom) {
11098                                 continue;
11099                         }
11100                         this._removeTile(key);
11101                 }
11102         },
11103
11104         _removeAllTiles: function () {
11105                 for (var key in this._tiles) {
11106                         this._removeTile(key);
11107                 }
11108         },
11109
11110         _invalidateAll: function () {
11111                 for (var z in this._levels) {
11112                         remove(this._levels[z].el);
11113                         this._onRemoveLevel(z);
11114                         delete this._levels[z];
11115                 }
11116                 this._removeAllTiles();
11117
11118                 this._tileZoom = undefined;
11119         },
11120
11121         _retainParent: function (x, y, z, minZoom) {
11122                 var x2 = Math.floor(x / 2),
11123                     y2 = Math.floor(y / 2),
11124                     z2 = z - 1,
11125                     coords2 = new Point(+x2, +y2);
11126                 coords2.z = +z2;
11127
11128                 var key = this._tileCoordsToKey(coords2),
11129                     tile = this._tiles[key];
11130
11131                 if (tile && tile.active) {
11132                         tile.retain = true;
11133                         return true;
11134
11135                 } else if (tile && tile.loaded) {
11136                         tile.retain = true;
11137                 }
11138
11139                 if (z2 > minZoom) {
11140                         return this._retainParent(x2, y2, z2, minZoom);
11141                 }
11142
11143                 return false;
11144         },
11145
11146         _retainChildren: function (x, y, z, maxZoom) {
11147
11148                 for (var i = 2 * x; i < 2 * x + 2; i++) {
11149                         for (var j = 2 * y; j < 2 * y + 2; j++) {
11150
11151                                 var coords = new Point(i, j);
11152                                 coords.z = z + 1;
11153
11154                                 var key = this._tileCoordsToKey(coords),
11155                                     tile = this._tiles[key];
11156
11157                                 if (tile && tile.active) {
11158                                         tile.retain = true;
11159                                         continue;
11160
11161                                 } else if (tile && tile.loaded) {
11162                                         tile.retain = true;
11163                                 }
11164
11165                                 if (z + 1 < maxZoom) {
11166                                         this._retainChildren(i, j, z + 1, maxZoom);
11167                                 }
11168                         }
11169                 }
11170         },
11171
11172         _resetView: function (e) {
11173                 var animating = e && (e.pinch || e.flyTo);
11174                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11175         },
11176
11177         _animateZoom: function (e) {
11178                 this._setView(e.center, e.zoom, true, e.noUpdate);
11179         },
11180
11181         _clampZoom: function (zoom) {
11182                 var options = this.options;
11183
11184                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11185                         return options.minNativeZoom;
11186                 }
11187
11188                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11189                         return options.maxNativeZoom;
11190                 }
11191
11192                 return zoom;
11193         },
11194
11195         _setView: function (center, zoom, noPrune, noUpdate) {
11196                 var tileZoom = this._clampZoom(Math.round(zoom));
11197                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11198                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11199                         tileZoom = undefined;
11200                 }
11201
11202                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11203
11204                 if (!noUpdate || tileZoomChanged) {
11205
11206                         this._tileZoom = tileZoom;
11207
11208                         if (this._abortLoading) {
11209                                 this._abortLoading();
11210                         }
11211
11212                         this._updateLevels();
11213                         this._resetGrid();
11214
11215                         if (tileZoom !== undefined) {
11216                                 this._update(center);
11217                         }
11218
11219                         if (!noPrune) {
11220                                 this._pruneTiles();
11221                         }
11222
11223                         // Flag to prevent _updateOpacity from pruning tiles during
11224                         // a zoom anim or a pinch gesture
11225                         this._noPrune = !!noPrune;
11226                 }
11227
11228                 this._setZoomTransforms(center, zoom);
11229         },
11230
11231         _setZoomTransforms: function (center, zoom) {
11232                 for (var i in this._levels) {
11233                         this._setZoomTransform(this._levels[i], center, zoom);
11234                 }
11235         },
11236
11237         _setZoomTransform: function (level, center, zoom) {
11238                 var scale = this._map.getZoomScale(zoom, level.zoom),
11239                     translate = level.origin.multiplyBy(scale)
11240                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11241
11242                 if (any3d) {
11243                         setTransform(level.el, translate, scale);
11244                 } else {
11245                         setPosition(level.el, translate);
11246                 }
11247         },
11248
11249         _resetGrid: function () {
11250                 var map = this._map,
11251                     crs = map.options.crs,
11252                     tileSize = this._tileSize = this.getTileSize(),
11253                     tileZoom = this._tileZoom;
11254
11255                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11256                 if (bounds) {
11257                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
11258                 }
11259
11260                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11261                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11262                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11263                 ];
11264                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11265                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11266                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11267                 ];
11268         },
11269
11270         _onMoveEnd: function () {
11271                 if (!this._map || this._map._animatingZoom) { return; }
11272
11273                 this._update();
11274         },
11275
11276         _getTiledPixelBounds: function (center) {
11277                 var map = this._map,
11278                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11279                     scale = map.getZoomScale(mapZoom, this._tileZoom),
11280                     pixelCenter = map.project(center, this._tileZoom).floor(),
11281                     halfSize = map.getSize().divideBy(scale * 2);
11282
11283                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11284         },
11285
11286         // Private method to load tiles in the grid's active zoom level according to map bounds
11287         _update: function (center) {
11288                 var map = this._map;
11289                 if (!map) { return; }
11290                 var zoom = this._clampZoom(map.getZoom());
11291
11292                 if (center === undefined) { center = map.getCenter(); }
11293                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
11294
11295                 var pixelBounds = this._getTiledPixelBounds(center),
11296                     tileRange = this._pxBoundsToTileRange(pixelBounds),
11297                     tileCenter = tileRange.getCenter(),
11298                     queue = [],
11299                     margin = this.options.keepBuffer,
11300                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11301                                               tileRange.getTopRight().add([margin, -margin]));
11302
11303                 // Sanity check: panic if the tile range contains Infinity somewhere.
11304                 if (!(isFinite(tileRange.min.x) &&
11305                       isFinite(tileRange.min.y) &&
11306                       isFinite(tileRange.max.x) &&
11307                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11308
11309                 for (var key in this._tiles) {
11310                         var c = this._tiles[key].coords;
11311                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11312                                 this._tiles[key].current = false;
11313                         }
11314                 }
11315
11316                 // _update just loads more tiles. If the tile zoom level differs too much
11317                 // from the map's, let _setView reset levels and prune old tiles.
11318                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11319
11320                 // create a queue of coordinates to load tiles from
11321                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11322                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11323                                 var coords = new Point(i, j);
11324                                 coords.z = this._tileZoom;
11325
11326                                 if (!this._isValidTile(coords)) { continue; }
11327
11328                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
11329                                 if (tile) {
11330                                         tile.current = true;
11331                                 } else {
11332                                         queue.push(coords);
11333                                 }
11334                         }
11335                 }
11336
11337                 // sort tile queue to load tiles in order of their distance to center
11338                 queue.sort(function (a, b) {
11339                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11340                 });
11341
11342                 if (queue.length !== 0) {
11343                         // if it's the first batch of tiles to load
11344                         if (!this._loading) {
11345                                 this._loading = true;
11346                                 // @event loading: Event
11347                                 // Fired when the grid layer starts loading tiles.
11348                                 this.fire('loading');
11349                         }
11350
11351                         // create DOM fragment to append tiles in one batch
11352                         var fragment = document.createDocumentFragment();
11353
11354                         for (i = 0; i < queue.length; i++) {
11355                                 this._addTile(queue[i], fragment);
11356                         }
11357
11358                         this._level.el.appendChild(fragment);
11359                 }
11360         },
11361
11362         _isValidTile: function (coords) {
11363                 var crs = this._map.options.crs;
11364
11365                 if (!crs.infinite) {
11366                         // don't load tile if it's out of bounds and not wrapped
11367                         var bounds = this._globalTileRange;
11368                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11369                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11370                 }
11371
11372                 if (!this.options.bounds) { return true; }
11373
11374                 // don't load tile if it doesn't intersect the bounds in options
11375                 var tileBounds = this._tileCoordsToBounds(coords);
11376                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11377         },
11378
11379         _keyToBounds: function (key) {
11380                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11381         },
11382
11383         _tileCoordsToNwSe: function (coords) {
11384                 var map = this._map,
11385                     tileSize = this.getTileSize(),
11386                     nwPoint = coords.scaleBy(tileSize),
11387                     sePoint = nwPoint.add(tileSize),
11388                     nw = map.unproject(nwPoint, coords.z),
11389                     se = map.unproject(sePoint, coords.z);
11390                 return [nw, se];
11391         },
11392
11393         // converts tile coordinates to its geographical bounds
11394         _tileCoordsToBounds: function (coords) {
11395                 var bp = this._tileCoordsToNwSe(coords),
11396                     bounds = new LatLngBounds(bp[0], bp[1]);
11397
11398                 if (!this.options.noWrap) {
11399                         bounds = this._map.wrapLatLngBounds(bounds);
11400                 }
11401                 return bounds;
11402         },
11403         // converts tile coordinates to key for the tile cache
11404         _tileCoordsToKey: function (coords) {
11405                 return coords.x + ':' + coords.y + ':' + coords.z;
11406         },
11407
11408         // converts tile cache key to coordinates
11409         _keyToTileCoords: function (key) {
11410                 var k = key.split(':'),
11411                     coords = new Point(+k[0], +k[1]);
11412                 coords.z = +k[2];
11413                 return coords;
11414         },
11415
11416         _removeTile: function (key) {
11417                 var tile = this._tiles[key];
11418                 if (!tile) { return; }
11419
11420                 remove(tile.el);
11421
11422                 delete this._tiles[key];
11423
11424                 // @event tileunload: TileEvent
11425                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11426                 this.fire('tileunload', {
11427                         tile: tile.el,
11428                         coords: this._keyToTileCoords(key)
11429                 });
11430         },
11431
11432         _initTile: function (tile) {
11433                 addClass(tile, 'leaflet-tile');
11434
11435                 var tileSize = this.getTileSize();
11436                 tile.style.width = tileSize.x + 'px';
11437                 tile.style.height = tileSize.y + 'px';
11438
11439                 tile.onselectstart = falseFn;
11440                 tile.onmousemove = falseFn;
11441
11442                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11443                 if (ielt9 && this.options.opacity < 1) {
11444                         setOpacity(tile, this.options.opacity);
11445                 }
11446
11447                 // without this hack, tiles disappear after zoom on Chrome for Android
11448                 // https://github.com/Leaflet/Leaflet/issues/2078
11449                 if (android && !android23) {
11450                         tile.style.WebkitBackfaceVisibility = 'hidden';
11451                 }
11452         },
11453
11454         _addTile: function (coords, container) {
11455                 var tilePos = this._getTilePos(coords),
11456                     key = this._tileCoordsToKey(coords);
11457
11458                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11459
11460                 this._initTile(tile);
11461
11462                 // if createTile is defined with a second argument ("done" callback),
11463                 // we know that tile is async and will be ready later; otherwise
11464                 if (this.createTile.length < 2) {
11465                         // mark tile as ready, but delay one frame for opacity animation to happen
11466                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11467                 }
11468
11469                 setPosition(tile, tilePos);
11470
11471                 // save tile in cache
11472                 this._tiles[key] = {
11473                         el: tile,
11474                         coords: coords,
11475                         current: true
11476                 };
11477
11478                 container.appendChild(tile);
11479                 // @event tileloadstart: TileEvent
11480                 // Fired when a tile is requested and starts loading.
11481                 this.fire('tileloadstart', {
11482                         tile: tile,
11483                         coords: coords
11484                 });
11485         },
11486
11487         _tileReady: function (coords, err, tile) {
11488                 if (err) {
11489                         // @event tileerror: TileErrorEvent
11490                         // Fired when there is an error loading a tile.
11491                         this.fire('tileerror', {
11492                                 error: err,
11493                                 tile: tile,
11494                                 coords: coords
11495                         });
11496                 }
11497
11498                 var key = this._tileCoordsToKey(coords);
11499
11500                 tile = this._tiles[key];
11501                 if (!tile) { return; }
11502
11503                 tile.loaded = +new Date();
11504                 if (this._map._fadeAnimated) {
11505                         setOpacity(tile.el, 0);
11506                         cancelAnimFrame(this._fadeFrame);
11507                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11508                 } else {
11509                         tile.active = true;
11510                         this._pruneTiles();
11511                 }
11512
11513                 if (!err) {
11514                         addClass(tile.el, 'leaflet-tile-loaded');
11515
11516                         // @event tileload: TileEvent
11517                         // Fired when a tile loads.
11518                         this.fire('tileload', {
11519                                 tile: tile.el,
11520                                 coords: coords
11521                         });
11522                 }
11523
11524                 if (this._noTilesToLoad()) {
11525                         this._loading = false;
11526                         // @event load: Event
11527                         // Fired when the grid layer loaded all visible tiles.
11528                         this.fire('load');
11529
11530                         if (ielt9 || !this._map._fadeAnimated) {
11531                                 requestAnimFrame(this._pruneTiles, this);
11532                         } else {
11533                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11534                                 // to trigger a pruning.
11535                                 setTimeout(bind(this._pruneTiles, this), 250);
11536                         }
11537                 }
11538         },
11539
11540         _getTilePos: function (coords) {
11541                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11542         },
11543
11544         _wrapCoords: function (coords) {
11545                 var newCoords = new Point(
11546                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11547                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11548                 newCoords.z = coords.z;
11549                 return newCoords;
11550         },
11551
11552         _pxBoundsToTileRange: function (bounds) {
11553                 var tileSize = this.getTileSize();
11554                 return new Bounds(
11555                         bounds.min.unscaleBy(tileSize).floor(),
11556                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11557         },
11558
11559         _noTilesToLoad: function () {
11560                 for (var key in this._tiles) {
11561                         if (!this._tiles[key].loaded) { return false; }
11562                 }
11563                 return true;
11564         }
11565 });
11566
11567 // @factory L.gridLayer(options?: GridLayer options)
11568 // Creates a new instance of GridLayer with the supplied options.
11569 function gridLayer(options) {
11570         return new GridLayer(options);
11571 }
11572
11573 /*
11574  * @class TileLayer
11575  * @inherits GridLayer
11576  * @aka L.TileLayer
11577  * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
11578  *
11579  * @example
11580  *
11581  * ```js
11582  * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>'}).addTo(map);
11583  * ```
11584  *
11585  * @section URL template
11586  * @example
11587  *
11588  * A string of the following form:
11589  *
11590  * ```
11591  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11592  * ```
11593  *
11594  * `{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 "&commat;2x" to the URL to load retina tiles.
11595  *
11596  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11597  *
11598  * ```
11599  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11600  * ```
11601  */
11602
11603
11604 var TileLayer = GridLayer.extend({
11605
11606         // @section
11607         // @aka TileLayer options
11608         options: {
11609                 // @option minZoom: Number = 0
11610                 // The minimum zoom level down to which this layer will be displayed (inclusive).
11611                 minZoom: 0,
11612
11613                 // @option maxZoom: Number = 18
11614                 // The maximum zoom level up to which this layer will be displayed (inclusive).
11615                 maxZoom: 18,
11616
11617                 // @option subdomains: String|String[] = 'abc'
11618                 // 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.
11619                 subdomains: 'abc',
11620
11621                 // @option errorTileUrl: String = ''
11622                 // URL to the tile image to show in place of the tile that failed to load.
11623                 errorTileUrl: '',
11624
11625                 // @option zoomOffset: Number = 0
11626                 // The zoom number used in tile URLs will be offset with this value.
11627                 zoomOffset: 0,
11628
11629                 // @option tms: Boolean = false
11630                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11631                 tms: false,
11632
11633                 // @option zoomReverse: Boolean = false
11634                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11635                 zoomReverse: false,
11636
11637                 // @option detectRetina: Boolean = false
11638                 // 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.
11639                 detectRetina: false,
11640
11641                 // @option crossOrigin: Boolean|String = false
11642                 // Whether the crossOrigin attribute will be added to the tiles.
11643                 // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
11644                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11645                 crossOrigin: false
11646         },
11647
11648         initialize: function (url, options) {
11649
11650                 this._url = url;
11651
11652                 options = setOptions(this, options);
11653
11654                 // detecting retina displays, adjusting tileSize and zoom levels
11655                 if (options.detectRetina && retina && options.maxZoom > 0) {
11656
11657                         options.tileSize = Math.floor(options.tileSize / 2);
11658
11659                         if (!options.zoomReverse) {
11660                                 options.zoomOffset++;
11661                                 options.maxZoom--;
11662                         } else {
11663                                 options.zoomOffset--;
11664                                 options.minZoom++;
11665                         }
11666
11667                         options.minZoom = Math.max(0, options.minZoom);
11668                 }
11669
11670                 if (typeof options.subdomains === 'string') {
11671                         options.subdomains = options.subdomains.split('');
11672                 }
11673
11674                 // for https://github.com/Leaflet/Leaflet/issues/137
11675                 if (!android) {
11676                         this.on('tileunload', this._onTileRemove);
11677                 }
11678         },
11679
11680         // @method setUrl(url: String, noRedraw?: Boolean): this
11681         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11682         // If the URL does not change, the layer will not be redrawn unless
11683         // the noRedraw parameter is set to false.
11684         setUrl: function (url, noRedraw) {
11685                 if (this._url === url && noRedraw === undefined) {
11686                         noRedraw = true;
11687                 }
11688
11689                 this._url = url;
11690
11691                 if (!noRedraw) {
11692                         this.redraw();
11693                 }
11694                 return this;
11695         },
11696
11697         // @method createTile(coords: Object, done?: Function): HTMLElement
11698         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11699         // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11700         // callback is called when the tile has been loaded.
11701         createTile: function (coords, done) {
11702                 var tile = document.createElement('img');
11703
11704                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11705                 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11706
11707                 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11708                         tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11709                 }
11710
11711                 /*
11712                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11713                  http://www.w3.org/TR/WCAG20-TECHS/H67
11714                 */
11715                 tile.alt = '';
11716
11717                 /*
11718                  Set role="presentation" to force screen readers to ignore this
11719                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11720                 */
11721                 tile.setAttribute('role', 'presentation');
11722
11723                 tile.src = this.getTileUrl(coords);
11724
11725                 return tile;
11726         },
11727
11728         // @section Extension methods
11729         // @uninheritable
11730         // Layers extending `TileLayer` might reimplement the following method.
11731         // @method getTileUrl(coords: Object): String
11732         // Called only internally, returns the URL for a tile given its coordinates.
11733         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11734         getTileUrl: function (coords) {
11735                 var data = {
11736                         r: retina ? '@2x' : '',
11737                         s: this._getSubdomain(coords),
11738                         x: coords.x,
11739                         y: coords.y,
11740                         z: this._getZoomForUrl()
11741                 };
11742                 if (this._map && !this._map.options.crs.infinite) {
11743                         var invertedY = this._globalTileRange.max.y - coords.y;
11744                         if (this.options.tms) {
11745                                 data['y'] = invertedY;
11746                         }
11747                         data['-y'] = invertedY;
11748                 }
11749
11750                 return template(this._url, extend(data, this.options));
11751         },
11752
11753         _tileOnLoad: function (done, tile) {
11754                 // For https://github.com/Leaflet/Leaflet/issues/3332
11755                 if (ielt9) {
11756                         setTimeout(bind(done, this, null, tile), 0);
11757                 } else {
11758                         done(null, tile);
11759                 }
11760         },
11761
11762         _tileOnError: function (done, tile, e) {
11763                 var errorUrl = this.options.errorTileUrl;
11764                 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11765                         tile.src = errorUrl;
11766                 }
11767                 done(e, tile);
11768         },
11769
11770         _onTileRemove: function (e) {
11771                 e.tile.onload = null;
11772         },
11773
11774         _getZoomForUrl: function () {
11775                 var zoom = this._tileZoom,
11776                 maxZoom = this.options.maxZoom,
11777                 zoomReverse = this.options.zoomReverse,
11778                 zoomOffset = this.options.zoomOffset;
11779
11780                 if (zoomReverse) {
11781                         zoom = maxZoom - zoom;
11782                 }
11783
11784                 return zoom + zoomOffset;
11785         },
11786
11787         _getSubdomain: function (tilePoint) {
11788                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11789                 return this.options.subdomains[index];
11790         },
11791
11792         // stops loading all tiles in the background layer
11793         _abortLoading: function () {
11794                 var i, tile;
11795                 for (i in this._tiles) {
11796                         if (this._tiles[i].coords.z !== this._tileZoom) {
11797                                 tile = this._tiles[i].el;
11798
11799                                 tile.onload = falseFn;
11800                                 tile.onerror = falseFn;
11801
11802                                 if (!tile.complete) {
11803                                         tile.src = emptyImageUrl;
11804                                         remove(tile);
11805                                         delete this._tiles[i];
11806                                 }
11807                         }
11808                 }
11809         },
11810
11811         _removeTile: function (key) {
11812                 var tile = this._tiles[key];
11813                 if (!tile) { return; }
11814
11815                 // Cancels any pending http requests associated with the tile
11816                 // unless we're on Android's stock browser,
11817                 // see https://github.com/Leaflet/Leaflet/issues/137
11818                 if (!androidStock) {
11819                         tile.el.setAttribute('src', emptyImageUrl);
11820                 }
11821
11822                 return GridLayer.prototype._removeTile.call(this, key);
11823         },
11824
11825         _tileReady: function (coords, err, tile) {
11826                 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11827                         return;
11828                 }
11829
11830                 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11831         }
11832 });
11833
11834
11835 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11836 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11837
11838 function tileLayer(url, options) {
11839         return new TileLayer(url, options);
11840 }
11841
11842 /*
11843  * @class TileLayer.WMS
11844  * @inherits TileLayer
11845  * @aka L.TileLayer.WMS
11846  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11847  *
11848  * @example
11849  *
11850  * ```js
11851  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11852  *      layers: 'nexrad-n0r-900913',
11853  *      format: 'image/png',
11854  *      transparent: true,
11855  *      attribution: "Weather data © 2012 IEM Nexrad"
11856  * });
11857  * ```
11858  */
11859
11860 var TileLayerWMS = TileLayer.extend({
11861
11862         // @section
11863         // @aka TileLayer.WMS options
11864         // If any custom options not documented here are used, they will be sent to the
11865         // WMS server as extra parameters in each request URL. This can be useful for
11866         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11867         defaultWmsParams: {
11868                 service: 'WMS',
11869                 request: 'GetMap',
11870
11871                 // @option layers: String = ''
11872                 // **(required)** Comma-separated list of WMS layers to show.
11873                 layers: '',
11874
11875                 // @option styles: String = ''
11876                 // Comma-separated list of WMS styles.
11877                 styles: '',
11878
11879                 // @option format: String = 'image/jpeg'
11880                 // WMS image format (use `'image/png'` for layers with transparency).
11881                 format: 'image/jpeg',
11882
11883                 // @option transparent: Boolean = false
11884                 // If `true`, the WMS service will return images with transparency.
11885                 transparent: false,
11886
11887                 // @option version: String = '1.1.1'
11888                 // Version of the WMS service to use
11889                 version: '1.1.1'
11890         },
11891
11892         options: {
11893                 // @option crs: CRS = null
11894                 // Coordinate Reference System to use for the WMS requests, defaults to
11895                 // map CRS. Don't change this if you're not sure what it means.
11896                 crs: null,
11897
11898                 // @option uppercase: Boolean = false
11899                 // If `true`, WMS request parameter keys will be uppercase.
11900                 uppercase: false
11901         },
11902
11903         initialize: function (url, options) {
11904
11905                 this._url = url;
11906
11907                 var wmsParams = extend({}, this.defaultWmsParams);
11908
11909                 // all keys that are not TileLayer options go to WMS params
11910                 for (var i in options) {
11911                         if (!(i in this.options)) {
11912                                 wmsParams[i] = options[i];
11913                         }
11914                 }
11915
11916                 options = setOptions(this, options);
11917
11918                 var realRetina = options.detectRetina && retina ? 2 : 1;
11919                 var tileSize = this.getTileSize();
11920                 wmsParams.width = tileSize.x * realRetina;
11921                 wmsParams.height = tileSize.y * realRetina;
11922
11923                 this.wmsParams = wmsParams;
11924         },
11925
11926         onAdd: function (map) {
11927
11928                 this._crs = this.options.crs || map.options.crs;
11929                 this._wmsVersion = parseFloat(this.wmsParams.version);
11930
11931                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11932                 this.wmsParams[projectionKey] = this._crs.code;
11933
11934                 TileLayer.prototype.onAdd.call(this, map);
11935         },
11936
11937         getTileUrl: function (coords) {
11938
11939                 var tileBounds = this._tileCoordsToNwSe(coords),
11940                     crs = this._crs,
11941                     bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11942                     min = bounds.min,
11943                     max = bounds.max,
11944                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11945                     [min.y, min.x, max.y, max.x] :
11946                     [min.x, min.y, max.x, max.y]).join(','),
11947                     url = TileLayer.prototype.getTileUrl.call(this, coords);
11948                 return url +
11949                         getParamString(this.wmsParams, url, this.options.uppercase) +
11950                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11951         },
11952
11953         // @method setParams(params: Object, noRedraw?: Boolean): this
11954         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11955         setParams: function (params, noRedraw) {
11956
11957                 extend(this.wmsParams, params);
11958
11959                 if (!noRedraw) {
11960                         this.redraw();
11961                 }
11962
11963                 return this;
11964         }
11965 });
11966
11967
11968 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11969 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11970 function tileLayerWMS(url, options) {
11971         return new TileLayerWMS(url, options);
11972 }
11973
11974 TileLayer.WMS = TileLayerWMS;
11975 tileLayer.wms = tileLayerWMS;
11976
11977 /*
11978  * @class Renderer
11979  * @inherits Layer
11980  * @aka L.Renderer
11981  *
11982  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11983  * DOM container of the renderer, its bounds, and its zoom animation.
11984  *
11985  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11986  * itself can be added or removed to the map. All paths use a renderer, which can
11987  * be implicit (the map will decide the type of renderer and use it automatically)
11988  * or explicit (using the [`renderer`](#path-renderer) option of the path).
11989  *
11990  * Do not use this class directly, use `SVG` and `Canvas` instead.
11991  *
11992  * @event update: Event
11993  * Fired when the renderer updates its bounds, center and zoom, for example when
11994  * its map has moved
11995  */
11996
11997 var Renderer = Layer.extend({
11998
11999         // @section
12000         // @aka Renderer options
12001         options: {
12002                 // @option padding: Number = 0.1
12003                 // How much to extend the clip area around the map view (relative to its size)
12004                 // e.g. 0.1 would be 10% of map view in each direction
12005                 padding: 0.1,
12006
12007                 // @option tolerance: Number = 0
12008                 // How much to extend click tolerance round a path/object on the map
12009                 tolerance : 0
12010         },
12011
12012         initialize: function (options) {
12013                 setOptions(this, options);
12014                 stamp(this);
12015                 this._layers = this._layers || {};
12016         },
12017
12018         onAdd: function () {
12019                 if (!this._container) {
12020                         this._initContainer(); // defined by renderer implementations
12021
12022                         if (this._zoomAnimated) {
12023                                 addClass(this._container, 'leaflet-zoom-animated');
12024                         }
12025                 }
12026
12027                 this.getPane().appendChild(this._container);
12028                 this._update();
12029                 this.on('update', this._updatePaths, this);
12030         },
12031
12032         onRemove: function () {
12033                 this.off('update', this._updatePaths, this);
12034                 this._destroyContainer();
12035         },
12036
12037         getEvents: function () {
12038                 var events = {
12039                         viewreset: this._reset,
12040                         zoom: this._onZoom,
12041                         moveend: this._update,
12042                         zoomend: this._onZoomEnd
12043                 };
12044                 if (this._zoomAnimated) {
12045                         events.zoomanim = this._onAnimZoom;
12046                 }
12047                 return events;
12048         },
12049
12050         _onAnimZoom: function (ev) {
12051                 this._updateTransform(ev.center, ev.zoom);
12052         },
12053
12054         _onZoom: function () {
12055                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12056         },
12057
12058         _updateTransform: function (center, zoom) {
12059                 var scale = this._map.getZoomScale(zoom, this._zoom),
12060                     position = getPosition(this._container),
12061                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12062                     currentCenterPoint = this._map.project(this._center, zoom),
12063                     destCenterPoint = this._map.project(center, zoom),
12064                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
12065
12066                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12067
12068                 if (any3d) {
12069                         setTransform(this._container, topLeftOffset, scale);
12070                 } else {
12071                         setPosition(this._container, topLeftOffset);
12072                 }
12073         },
12074
12075         _reset: function () {
12076                 this._update();
12077                 this._updateTransform(this._center, this._zoom);
12078
12079                 for (var id in this._layers) {
12080                         this._layers[id]._reset();
12081                 }
12082         },
12083
12084         _onZoomEnd: function () {
12085                 for (var id in this._layers) {
12086                         this._layers[id]._project();
12087                 }
12088         },
12089
12090         _updatePaths: function () {
12091                 for (var id in this._layers) {
12092                         this._layers[id]._update();
12093                 }
12094         },
12095
12096         _update: function () {
12097                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12098                 // Subclasses are responsible of firing the 'update' event.
12099                 var p = this.options.padding,
12100                     size = this._map.getSize(),
12101                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12102
12103                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12104
12105                 this._center = this._map.getCenter();
12106                 this._zoom = this._map.getZoom();
12107         }
12108 });
12109
12110 /*
12111  * @class Canvas
12112  * @inherits Renderer
12113  * @aka L.Canvas
12114  *
12115  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12116  * Inherits `Renderer`.
12117  *
12118  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12119  * available in all web browsers, notably IE8, and overlapping geometries might
12120  * not display properly in some edge cases.
12121  *
12122  * @example
12123  *
12124  * Use Canvas by default for all paths in the map:
12125  *
12126  * ```js
12127  * var map = L.map('map', {
12128  *      renderer: L.canvas()
12129  * });
12130  * ```
12131  *
12132  * Use a Canvas renderer with extra padding for specific vector geometries:
12133  *
12134  * ```js
12135  * var map = L.map('map');
12136  * var myRenderer = L.canvas({ padding: 0.5 });
12137  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12138  * var circle = L.circle( center, { renderer: myRenderer } );
12139  * ```
12140  */
12141
12142 var Canvas = Renderer.extend({
12143         getEvents: function () {
12144                 var events = Renderer.prototype.getEvents.call(this);
12145                 events.viewprereset = this._onViewPreReset;
12146                 return events;
12147         },
12148
12149         _onViewPreReset: function () {
12150                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12151                 this._postponeUpdatePaths = true;
12152         },
12153
12154         onAdd: function () {
12155                 Renderer.prototype.onAdd.call(this);
12156
12157                 // Redraw vectors since canvas is cleared upon removal,
12158                 // in case of removing the renderer itself from the map.
12159                 this._draw();
12160         },
12161
12162         _initContainer: function () {
12163                 var container = this._container = document.createElement('canvas');
12164
12165                 on(container, 'mousemove', this._onMouseMove, this);
12166                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12167                 on(container, 'mouseout', this._handleMouseOut, this);
12168
12169                 this._ctx = container.getContext('2d');
12170         },
12171
12172         _destroyContainer: function () {
12173                 cancelAnimFrame(this._redrawRequest);
12174                 delete this._ctx;
12175                 remove(this._container);
12176                 off(this._container);
12177                 delete this._container;
12178         },
12179
12180         _updatePaths: function () {
12181                 if (this._postponeUpdatePaths) { return; }
12182
12183                 var layer;
12184                 this._redrawBounds = null;
12185                 for (var id in this._layers) {
12186                         layer = this._layers[id];
12187                         layer._update();
12188                 }
12189                 this._redraw();
12190         },
12191
12192         _update: function () {
12193                 if (this._map._animatingZoom && this._bounds) { return; }
12194
12195                 Renderer.prototype._update.call(this);
12196
12197                 var b = this._bounds,
12198                     container = this._container,
12199                     size = b.getSize(),
12200                     m = retina ? 2 : 1;
12201
12202                 setPosition(container, b.min);
12203
12204                 // set canvas size (also clearing it); use double size on retina
12205                 container.width = m * size.x;
12206                 container.height = m * size.y;
12207                 container.style.width = size.x + 'px';
12208                 container.style.height = size.y + 'px';
12209
12210                 if (retina) {
12211                         this._ctx.scale(2, 2);
12212                 }
12213
12214                 // translate so we use the same path coordinates after canvas element moves
12215                 this._ctx.translate(-b.min.x, -b.min.y);
12216
12217                 // Tell paths to redraw themselves
12218                 this.fire('update');
12219         },
12220
12221         _reset: function () {
12222                 Renderer.prototype._reset.call(this);
12223
12224                 if (this._postponeUpdatePaths) {
12225                         this._postponeUpdatePaths = false;
12226                         this._updatePaths();
12227                 }
12228         },
12229
12230         _initPath: function (layer) {
12231                 this._updateDashArray(layer);
12232                 this._layers[stamp(layer)] = layer;
12233
12234                 var order = layer._order = {
12235                         layer: layer,
12236                         prev: this._drawLast,
12237                         next: null
12238                 };
12239                 if (this._drawLast) { this._drawLast.next = order; }
12240                 this._drawLast = order;
12241                 this._drawFirst = this._drawFirst || this._drawLast;
12242         },
12243
12244         _addPath: function (layer) {
12245                 this._requestRedraw(layer);
12246         },
12247
12248         _removePath: function (layer) {
12249                 var order = layer._order;
12250                 var next = order.next;
12251                 var prev = order.prev;
12252
12253                 if (next) {
12254                         next.prev = prev;
12255                 } else {
12256                         this._drawLast = prev;
12257                 }
12258                 if (prev) {
12259                         prev.next = next;
12260                 } else {
12261                         this._drawFirst = next;
12262                 }
12263
12264                 delete layer._order;
12265
12266                 delete this._layers[stamp(layer)];
12267
12268                 this._requestRedraw(layer);
12269         },
12270
12271         _updatePath: function (layer) {
12272                 // Redraw the union of the layer's old pixel
12273                 // bounds and the new pixel bounds.
12274                 this._extendRedrawBounds(layer);
12275                 layer._project();
12276                 layer._update();
12277                 // The redraw will extend the redraw bounds
12278                 // with the new pixel bounds.
12279                 this._requestRedraw(layer);
12280         },
12281
12282         _updateStyle: function (layer) {
12283                 this._updateDashArray(layer);
12284                 this._requestRedraw(layer);
12285         },
12286
12287         _updateDashArray: function (layer) {
12288                 if (typeof layer.options.dashArray === 'string') {
12289                         var parts = layer.options.dashArray.split(/[, ]+/),
12290                             dashArray = [],
12291                             dashValue,
12292                             i;
12293                         for (i = 0; i < parts.length; i++) {
12294                                 dashValue = Number(parts[i]);
12295                                 // Ignore dash array containing invalid lengths
12296                                 if (isNaN(dashValue)) { return; }
12297                                 dashArray.push(dashValue);
12298                         }
12299                         layer.options._dashArray = dashArray;
12300                 } else {
12301                         layer.options._dashArray = layer.options.dashArray;
12302                 }
12303         },
12304
12305         _requestRedraw: function (layer) {
12306                 if (!this._map) { return; }
12307
12308                 this._extendRedrawBounds(layer);
12309                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12310         },
12311
12312         _extendRedrawBounds: function (layer) {
12313                 if (layer._pxBounds) {
12314                         var padding = (layer.options.weight || 0) + 1;
12315                         this._redrawBounds = this._redrawBounds || new Bounds();
12316                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12317                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12318                 }
12319         },
12320
12321         _redraw: function () {
12322                 this._redrawRequest = null;
12323
12324                 if (this._redrawBounds) {
12325                         this._redrawBounds.min._floor();
12326                         this._redrawBounds.max._ceil();
12327                 }
12328
12329                 this._clear(); // clear layers in redraw bounds
12330                 this._draw(); // draw layers
12331
12332                 this._redrawBounds = null;
12333         },
12334
12335         _clear: function () {
12336                 var bounds = this._redrawBounds;
12337                 if (bounds) {
12338                         var size = bounds.getSize();
12339                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12340                 } else {
12341                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12342                 }
12343         },
12344
12345         _draw: function () {
12346                 var layer, bounds = this._redrawBounds;
12347                 this._ctx.save();
12348                 if (bounds) {
12349                         var size = bounds.getSize();
12350                         this._ctx.beginPath();
12351                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12352                         this._ctx.clip();
12353                 }
12354
12355                 this._drawing = true;
12356
12357                 for (var order = this._drawFirst; order; order = order.next) {
12358                         layer = order.layer;
12359                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12360                                 layer._updatePath();
12361                         }
12362                 }
12363
12364                 this._drawing = false;
12365
12366                 this._ctx.restore();  // Restore state before clipping.
12367         },
12368
12369         _updatePoly: function (layer, closed) {
12370                 if (!this._drawing) { return; }
12371
12372                 var i, j, len2, p,
12373                     parts = layer._parts,
12374                     len = parts.length,
12375                     ctx = this._ctx;
12376
12377                 if (!len) { return; }
12378
12379                 ctx.beginPath();
12380
12381                 for (i = 0; i < len; i++) {
12382                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
12383                                 p = parts[i][j];
12384                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12385                         }
12386                         if (closed) {
12387                                 ctx.closePath();
12388                         }
12389                 }
12390
12391                 this._fillStroke(ctx, layer);
12392
12393                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12394         },
12395
12396         _updateCircle: function (layer) {
12397
12398                 if (!this._drawing || layer._empty()) { return; }
12399
12400                 var p = layer._point,
12401                     ctx = this._ctx,
12402                     r = Math.max(Math.round(layer._radius), 1),
12403                     s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12404
12405                 if (s !== 1) {
12406                         ctx.save();
12407                         ctx.scale(1, s);
12408                 }
12409
12410                 ctx.beginPath();
12411                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12412
12413                 if (s !== 1) {
12414                         ctx.restore();
12415                 }
12416
12417                 this._fillStroke(ctx, layer);
12418         },
12419
12420         _fillStroke: function (ctx, layer) {
12421                 var options = layer.options;
12422
12423                 if (options.fill) {
12424                         ctx.globalAlpha = options.fillOpacity;
12425                         ctx.fillStyle = options.fillColor || options.color;
12426                         ctx.fill(options.fillRule || 'evenodd');
12427                 }
12428
12429                 if (options.stroke && options.weight !== 0) {
12430                         if (ctx.setLineDash) {
12431                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12432                         }
12433                         ctx.globalAlpha = options.opacity;
12434                         ctx.lineWidth = options.weight;
12435                         ctx.strokeStyle = options.color;
12436                         ctx.lineCap = options.lineCap;
12437                         ctx.lineJoin = options.lineJoin;
12438                         ctx.stroke();
12439                 }
12440         },
12441
12442         // Canvas obviously doesn't have mouse events for individual drawn objects,
12443         // so we emulate that by calculating what's under the mouse on mousemove/click manually
12444
12445         _onClick: function (e) {
12446                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12447
12448                 for (var order = this._drawFirst; order; order = order.next) {
12449                         layer = order.layer;
12450                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12451                                 clickedLayer = layer;
12452                         }
12453                 }
12454                 if (clickedLayer)  {
12455                         fakeStop(e);
12456                         this._fireEvent([clickedLayer], e);
12457                 }
12458         },
12459
12460         _onMouseMove: function (e) {
12461                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12462
12463                 var point = this._map.mouseEventToLayerPoint(e);
12464                 this._handleMouseHover(e, point);
12465         },
12466
12467
12468         _handleMouseOut: function (e) {
12469                 var layer = this._hoveredLayer;
12470                 if (layer) {
12471                         // if we're leaving the layer, fire mouseout
12472                         removeClass(this._container, 'leaflet-interactive');
12473                         this._fireEvent([layer], e, 'mouseout');
12474                         this._hoveredLayer = null;
12475                         this._mouseHoverThrottled = false;
12476                 }
12477         },
12478
12479         _handleMouseHover: function (e, point) {
12480                 if (this._mouseHoverThrottled) {
12481                         return;
12482                 }
12483
12484                 var layer, candidateHoveredLayer;
12485
12486                 for (var order = this._drawFirst; order; order = order.next) {
12487                         layer = order.layer;
12488                         if (layer.options.interactive && layer._containsPoint(point)) {
12489                                 candidateHoveredLayer = layer;
12490                         }
12491                 }
12492
12493                 if (candidateHoveredLayer !== this._hoveredLayer) {
12494                         this._handleMouseOut(e);
12495
12496                         if (candidateHoveredLayer) {
12497                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12498                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12499                                 this._hoveredLayer = candidateHoveredLayer;
12500                         }
12501                 }
12502
12503                 if (this._hoveredLayer) {
12504                         this._fireEvent([this._hoveredLayer], e);
12505                 }
12506
12507                 this._mouseHoverThrottled = true;
12508                 setTimeout(L.bind(function () {
12509                         this._mouseHoverThrottled = false;
12510                 }, this), 32);
12511         },
12512
12513         _fireEvent: function (layers, e, type) {
12514                 this._map._fireDOMEvent(e, type || e.type, layers);
12515         },
12516
12517         _bringToFront: function (layer) {
12518                 var order = layer._order;
12519
12520                 if (!order) { return; }
12521
12522                 var next = order.next;
12523                 var prev = order.prev;
12524
12525                 if (next) {
12526                         next.prev = prev;
12527                 } else {
12528                         // Already last
12529                         return;
12530                 }
12531                 if (prev) {
12532                         prev.next = next;
12533                 } else if (next) {
12534                         // Update first entry unless this is the
12535                         // single entry
12536                         this._drawFirst = next;
12537                 }
12538
12539                 order.prev = this._drawLast;
12540                 this._drawLast.next = order;
12541
12542                 order.next = null;
12543                 this._drawLast = order;
12544
12545                 this._requestRedraw(layer);
12546         },
12547
12548         _bringToBack: function (layer) {
12549                 var order = layer._order;
12550
12551                 if (!order) { return; }
12552
12553                 var next = order.next;
12554                 var prev = order.prev;
12555
12556                 if (prev) {
12557                         prev.next = next;
12558                 } else {
12559                         // Already first
12560                         return;
12561                 }
12562                 if (next) {
12563                         next.prev = prev;
12564                 } else if (prev) {
12565                         // Update last entry unless this is the
12566                         // single entry
12567                         this._drawLast = prev;
12568                 }
12569
12570                 order.prev = null;
12571
12572                 order.next = this._drawFirst;
12573                 this._drawFirst.prev = order;
12574                 this._drawFirst = order;
12575
12576                 this._requestRedraw(layer);
12577         }
12578 });
12579
12580 // @factory L.canvas(options?: Renderer options)
12581 // Creates a Canvas renderer with the given options.
12582 function canvas$1(options) {
12583         return canvas ? new Canvas(options) : null;
12584 }
12585
12586 /*
12587  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12588  */
12589
12590
12591 var vmlCreate = (function () {
12592         try {
12593                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12594                 return function (name) {
12595                         return document.createElement('<lvml:' + name + ' class="lvml">');
12596                 };
12597         } catch (e) {
12598                 return function (name) {
12599                         return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12600                 };
12601         }
12602 })();
12603
12604
12605 /*
12606  * @class SVG
12607  *
12608  *
12609  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12610  * with old versions of Internet Explorer.
12611  */
12612
12613 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12614 var vmlMixin = {
12615
12616         _initContainer: function () {
12617                 this._container = create$1('div', 'leaflet-vml-container');
12618         },
12619
12620         _update: function () {
12621                 if (this._map._animatingZoom) { return; }
12622                 Renderer.prototype._update.call(this);
12623                 this.fire('update');
12624         },
12625
12626         _initPath: function (layer) {
12627                 var container = layer._container = vmlCreate('shape');
12628
12629                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12630
12631                 container.coordsize = '1 1';
12632
12633                 layer._path = vmlCreate('path');
12634                 container.appendChild(layer._path);
12635
12636                 this._updateStyle(layer);
12637                 this._layers[stamp(layer)] = layer;
12638         },
12639
12640         _addPath: function (layer) {
12641                 var container = layer._container;
12642                 this._container.appendChild(container);
12643
12644                 if (layer.options.interactive) {
12645                         layer.addInteractiveTarget(container);
12646                 }
12647         },
12648
12649         _removePath: function (layer) {
12650                 var container = layer._container;
12651                 remove(container);
12652                 layer.removeInteractiveTarget(container);
12653                 delete this._layers[stamp(layer)];
12654         },
12655
12656         _updateStyle: function (layer) {
12657                 var stroke = layer._stroke,
12658                     fill = layer._fill,
12659                     options = layer.options,
12660                     container = layer._container;
12661
12662                 container.stroked = !!options.stroke;
12663                 container.filled = !!options.fill;
12664
12665                 if (options.stroke) {
12666                         if (!stroke) {
12667                                 stroke = layer._stroke = vmlCreate('stroke');
12668                         }
12669                         container.appendChild(stroke);
12670                         stroke.weight = options.weight + 'px';
12671                         stroke.color = options.color;
12672                         stroke.opacity = options.opacity;
12673
12674                         if (options.dashArray) {
12675                                 stroke.dashStyle = isArray(options.dashArray) ?
12676                                     options.dashArray.join(' ') :
12677                                     options.dashArray.replace(/( *, *)/g, ' ');
12678                         } else {
12679                                 stroke.dashStyle = '';
12680                         }
12681                         stroke.endcap = options.lineCap.replace('butt', 'flat');
12682                         stroke.joinstyle = options.lineJoin;
12683
12684                 } else if (stroke) {
12685                         container.removeChild(stroke);
12686                         layer._stroke = null;
12687                 }
12688
12689                 if (options.fill) {
12690                         if (!fill) {
12691                                 fill = layer._fill = vmlCreate('fill');
12692                         }
12693                         container.appendChild(fill);
12694                         fill.color = options.fillColor || options.color;
12695                         fill.opacity = options.fillOpacity;
12696
12697                 } else if (fill) {
12698                         container.removeChild(fill);
12699                         layer._fill = null;
12700                 }
12701         },
12702
12703         _updateCircle: function (layer) {
12704                 var p = layer._point.round(),
12705                     r = Math.round(layer._radius),
12706                     r2 = Math.round(layer._radiusY || r);
12707
12708                 this._setPath(layer, layer._empty() ? 'M0 0' :
12709                         'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12710         },
12711
12712         _setPath: function (layer, path) {
12713                 layer._path.v = path;
12714         },
12715
12716         _bringToFront: function (layer) {
12717                 toFront(layer._container);
12718         },
12719
12720         _bringToBack: function (layer) {
12721                 toBack(layer._container);
12722         }
12723 };
12724
12725 var create$2 = vml ? vmlCreate : svgCreate;
12726
12727 /*
12728  * @class SVG
12729  * @inherits Renderer
12730  * @aka L.SVG
12731  *
12732  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12733  * Inherits `Renderer`.
12734  *
12735  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12736  * available in all web browsers, notably Android 2.x and 3.x.
12737  *
12738  * Although SVG is not available on IE7 and IE8, these browsers support
12739  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12740  * (a now deprecated technology), and the SVG renderer will fall back to VML in
12741  * this case.
12742  *
12743  * @example
12744  *
12745  * Use SVG by default for all paths in the map:
12746  *
12747  * ```js
12748  * var map = L.map('map', {
12749  *      renderer: L.svg()
12750  * });
12751  * ```
12752  *
12753  * Use a SVG renderer with extra padding for specific vector geometries:
12754  *
12755  * ```js
12756  * var map = L.map('map');
12757  * var myRenderer = L.svg({ padding: 0.5 });
12758  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12759  * var circle = L.circle( center, { renderer: myRenderer } );
12760  * ```
12761  */
12762
12763 var SVG = Renderer.extend({
12764
12765         getEvents: function () {
12766                 var events = Renderer.prototype.getEvents.call(this);
12767                 events.zoomstart = this._onZoomStart;
12768                 return events;
12769         },
12770
12771         _initContainer: function () {
12772                 this._container = create$2('svg');
12773
12774                 // makes it possible to click through svg root; we'll reset it back in individual paths
12775                 this._container.setAttribute('pointer-events', 'none');
12776
12777                 this._rootGroup = create$2('g');
12778                 this._container.appendChild(this._rootGroup);
12779         },
12780
12781         _destroyContainer: function () {
12782                 remove(this._container);
12783                 off(this._container);
12784                 delete this._container;
12785                 delete this._rootGroup;
12786                 delete this._svgSize;
12787         },
12788
12789         _onZoomStart: function () {
12790                 // Drag-then-pinch interactions might mess up the center and zoom.
12791                 // In this case, the easiest way to prevent this is re-do the renderer
12792                 //   bounds and padding when the zooming starts.
12793                 this._update();
12794         },
12795
12796         _update: function () {
12797                 if (this._map._animatingZoom && this._bounds) { return; }
12798
12799                 Renderer.prototype._update.call(this);
12800
12801                 var b = this._bounds,
12802                     size = b.getSize(),
12803                     container = this._container;
12804
12805                 // set size of svg-container if changed
12806                 if (!this._svgSize || !this._svgSize.equals(size)) {
12807                         this._svgSize = size;
12808                         container.setAttribute('width', size.x);
12809                         container.setAttribute('height', size.y);
12810                 }
12811
12812                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12813                 setPosition(container, b.min);
12814                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12815
12816                 this.fire('update');
12817         },
12818
12819         // methods below are called by vector layers implementations
12820
12821         _initPath: function (layer) {
12822                 var path = layer._path = create$2('path');
12823
12824                 // @namespace Path
12825                 // @option className: String = null
12826                 // Custom class name set on an element. Only for SVG renderer.
12827                 if (layer.options.className) {
12828                         addClass(path, layer.options.className);
12829                 }
12830
12831                 if (layer.options.interactive) {
12832                         addClass(path, 'leaflet-interactive');
12833                 }
12834
12835                 this._updateStyle(layer);
12836                 this._layers[stamp(layer)] = layer;
12837         },
12838
12839         _addPath: function (layer) {
12840                 if (!this._rootGroup) { this._initContainer(); }
12841                 this._rootGroup.appendChild(layer._path);
12842                 layer.addInteractiveTarget(layer._path);
12843         },
12844
12845         _removePath: function (layer) {
12846                 remove(layer._path);
12847                 layer.removeInteractiveTarget(layer._path);
12848                 delete this._layers[stamp(layer)];
12849         },
12850
12851         _updatePath: function (layer) {
12852                 layer._project();
12853                 layer._update();
12854         },
12855
12856         _updateStyle: function (layer) {
12857                 var path = layer._path,
12858                     options = layer.options;
12859
12860                 if (!path) { return; }
12861
12862                 if (options.stroke) {
12863                         path.setAttribute('stroke', options.color);
12864                         path.setAttribute('stroke-opacity', options.opacity);
12865                         path.setAttribute('stroke-width', options.weight);
12866                         path.setAttribute('stroke-linecap', options.lineCap);
12867                         path.setAttribute('stroke-linejoin', options.lineJoin);
12868
12869                         if (options.dashArray) {
12870                                 path.setAttribute('stroke-dasharray', options.dashArray);
12871                         } else {
12872                                 path.removeAttribute('stroke-dasharray');
12873                         }
12874
12875                         if (options.dashOffset) {
12876                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
12877                         } else {
12878                                 path.removeAttribute('stroke-dashoffset');
12879                         }
12880                 } else {
12881                         path.setAttribute('stroke', 'none');
12882                 }
12883
12884                 if (options.fill) {
12885                         path.setAttribute('fill', options.fillColor || options.color);
12886                         path.setAttribute('fill-opacity', options.fillOpacity);
12887                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12888                 } else {
12889                         path.setAttribute('fill', 'none');
12890                 }
12891         },
12892
12893         _updatePoly: function (layer, closed) {
12894                 this._setPath(layer, pointsToPath(layer._parts, closed));
12895         },
12896
12897         _updateCircle: function (layer) {
12898                 var p = layer._point,
12899                     r = Math.max(Math.round(layer._radius), 1),
12900                     r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12901                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12902
12903                 // drawing a circle with two half-arcs
12904                 var d = layer._empty() ? 'M0 0' :
12905                         'M' + (p.x - r) + ',' + p.y +
12906                         arc + (r * 2) + ',0 ' +
12907                         arc + (-r * 2) + ',0 ';
12908
12909                 this._setPath(layer, d);
12910         },
12911
12912         _setPath: function (layer, path) {
12913                 layer._path.setAttribute('d', path);
12914         },
12915
12916         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12917         _bringToFront: function (layer) {
12918                 toFront(layer._path);
12919         },
12920
12921         _bringToBack: function (layer) {
12922                 toBack(layer._path);
12923         }
12924 });
12925
12926 if (vml) {
12927         SVG.include(vmlMixin);
12928 }
12929
12930 // @namespace SVG
12931 // @factory L.svg(options?: Renderer options)
12932 // Creates a SVG renderer with the given options.
12933 function svg$1(options) {
12934         return svg || vml ? new SVG(options) : null;
12935 }
12936
12937 Map.include({
12938         // @namespace Map; @method getRenderer(layer: Path): Renderer
12939         // Returns the instance of `Renderer` that should be used to render the given
12940         // `Path`. It will ensure that the `renderer` options of the map and paths
12941         // are respected, and that the renderers do exist on the map.
12942         getRenderer: function (layer) {
12943                 // @namespace Path; @option renderer: Renderer
12944                 // Use this specific instance of `Renderer` for this path. Takes
12945                 // precedence over the map's [default renderer](#map-renderer).
12946                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12947
12948                 if (!renderer) {
12949                         renderer = this._renderer = this._createRenderer();
12950                 }
12951
12952                 if (!this.hasLayer(renderer)) {
12953                         this.addLayer(renderer);
12954                 }
12955                 return renderer;
12956         },
12957
12958         _getPaneRenderer: function (name) {
12959                 if (name === 'overlayPane' || name === undefined) {
12960                         return false;
12961                 }
12962
12963                 var renderer = this._paneRenderers[name];
12964                 if (renderer === undefined) {
12965                         renderer = this._createRenderer({pane: name});
12966                         this._paneRenderers[name] = renderer;
12967                 }
12968                 return renderer;
12969         },
12970
12971         _createRenderer: function (options) {
12972                 // @namespace Map; @option preferCanvas: Boolean = false
12973                 // Whether `Path`s should be rendered on a `Canvas` renderer.
12974                 // By default, all `Path`s are rendered in a `SVG` renderer.
12975                 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12976         }
12977 });
12978
12979 /*
12980  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12981  */
12982
12983 /*
12984  * @class Rectangle
12985  * @aka L.Rectangle
12986  * @inherits Polygon
12987  *
12988  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12989  *
12990  * @example
12991  *
12992  * ```js
12993  * // define rectangle geographical bounds
12994  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12995  *
12996  * // create an orange rectangle
12997  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12998  *
12999  * // zoom the map to the rectangle bounds
13000  * map.fitBounds(bounds);
13001  * ```
13002  *
13003  */
13004
13005
13006 var Rectangle = Polygon.extend({
13007         initialize: function (latLngBounds, options) {
13008                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
13009         },
13010
13011         // @method setBounds(latLngBounds: LatLngBounds): this
13012         // Redraws the rectangle with the passed bounds.
13013         setBounds: function (latLngBounds) {
13014                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
13015         },
13016
13017         _boundsToLatLngs: function (latLngBounds) {
13018                 latLngBounds = toLatLngBounds(latLngBounds);
13019                 return [
13020                         latLngBounds.getSouthWest(),
13021                         latLngBounds.getNorthWest(),
13022                         latLngBounds.getNorthEast(),
13023                         latLngBounds.getSouthEast()
13024                 ];
13025         }
13026 });
13027
13028
13029 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
13030 function rectangle(latLngBounds, options) {
13031         return new Rectangle(latLngBounds, options);
13032 }
13033
13034 SVG.create = create$2;
13035 SVG.pointsToPath = pointsToPath;
13036
13037 GeoJSON.geometryToLayer = geometryToLayer;
13038 GeoJSON.coordsToLatLng = coordsToLatLng;
13039 GeoJSON.coordsToLatLngs = coordsToLatLngs;
13040 GeoJSON.latLngToCoords = latLngToCoords;
13041 GeoJSON.latLngsToCoords = latLngsToCoords;
13042 GeoJSON.getFeature = getFeature;
13043 GeoJSON.asFeature = asFeature;
13044
13045 /*
13046  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
13047  * (zoom to a selected bounding box), enabled by default.
13048  */
13049
13050 // @namespace Map
13051 // @section Interaction Options
13052 Map.mergeOptions({
13053         // @option boxZoom: Boolean = true
13054         // Whether the map can be zoomed to a rectangular area specified by
13055         // dragging the mouse while pressing the shift key.
13056         boxZoom: true
13057 });
13058
13059 var BoxZoom = Handler.extend({
13060         initialize: function (map) {
13061                 this._map = map;
13062                 this._container = map._container;
13063                 this._pane = map._panes.overlayPane;
13064                 this._resetStateTimeout = 0;
13065                 map.on('unload', this._destroy, this);
13066         },
13067
13068         addHooks: function () {
13069                 on(this._container, 'mousedown', this._onMouseDown, this);
13070         },
13071
13072         removeHooks: function () {
13073                 off(this._container, 'mousedown', this._onMouseDown, this);
13074         },
13075
13076         moved: function () {
13077                 return this._moved;
13078         },
13079
13080         _destroy: function () {
13081                 remove(this._pane);
13082                 delete this._pane;
13083         },
13084
13085         _resetState: function () {
13086                 this._resetStateTimeout = 0;
13087                 this._moved = false;
13088         },
13089
13090         _clearDeferredResetState: function () {
13091                 if (this._resetStateTimeout !== 0) {
13092                         clearTimeout(this._resetStateTimeout);
13093                         this._resetStateTimeout = 0;
13094                 }
13095         },
13096
13097         _onMouseDown: function (e) {
13098                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13099
13100                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13101                 // will interrupt the interaction and orphan a box element in the container.
13102                 this._clearDeferredResetState();
13103                 this._resetState();
13104
13105                 disableTextSelection();
13106                 disableImageDrag();
13107
13108                 this._startPoint = this._map.mouseEventToContainerPoint(e);
13109
13110                 on(document, {
13111                         contextmenu: stop,
13112                         mousemove: this._onMouseMove,
13113                         mouseup: this._onMouseUp,
13114                         keydown: this._onKeyDown
13115                 }, this);
13116         },
13117
13118         _onMouseMove: function (e) {
13119                 if (!this._moved) {
13120                         this._moved = true;
13121
13122                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
13123                         addClass(this._container, 'leaflet-crosshair');
13124
13125                         this._map.fire('boxzoomstart');
13126                 }
13127
13128                 this._point = this._map.mouseEventToContainerPoint(e);
13129
13130                 var bounds = new Bounds(this._point, this._startPoint),
13131                     size = bounds.getSize();
13132
13133                 setPosition(this._box, bounds.min);
13134
13135                 this._box.style.width  = size.x + 'px';
13136                 this._box.style.height = size.y + 'px';
13137         },
13138
13139         _finish: function () {
13140                 if (this._moved) {
13141                         remove(this._box);
13142                         removeClass(this._container, 'leaflet-crosshair');
13143                 }
13144
13145                 enableTextSelection();
13146                 enableImageDrag();
13147
13148                 off(document, {
13149                         contextmenu: stop,
13150                         mousemove: this._onMouseMove,
13151                         mouseup: this._onMouseUp,
13152                         keydown: this._onKeyDown
13153                 }, this);
13154         },
13155
13156         _onMouseUp: function (e) {
13157                 if ((e.which !== 1) && (e.button !== 1)) { return; }
13158
13159                 this._finish();
13160
13161                 if (!this._moved) { return; }
13162                 // Postpone to next JS tick so internal click event handling
13163                 // still see it as "moved".
13164                 this._clearDeferredResetState();
13165                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13166
13167                 var bounds = new LatLngBounds(
13168                         this._map.containerPointToLatLng(this._startPoint),
13169                         this._map.containerPointToLatLng(this._point));
13170
13171                 this._map
13172                         .fitBounds(bounds)
13173                         .fire('boxzoomend', {boxZoomBounds: bounds});
13174         },
13175
13176         _onKeyDown: function (e) {
13177                 if (e.keyCode === 27) {
13178                         this._finish();
13179                 }
13180         }
13181 });
13182
13183 // @section Handlers
13184 // @property boxZoom: Handler
13185 // Box (shift-drag with mouse) zoom handler.
13186 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13187
13188 /*
13189  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13190  */
13191
13192 // @namespace Map
13193 // @section Interaction Options
13194
13195 Map.mergeOptions({
13196         // @option doubleClickZoom: Boolean|String = true
13197         // Whether the map can be zoomed in by double clicking on it and
13198         // zoomed out by double clicking while holding shift. If passed
13199         // `'center'`, double-click zoom will zoom to the center of the
13200         //  view regardless of where the mouse was.
13201         doubleClickZoom: true
13202 });
13203
13204 var DoubleClickZoom = Handler.extend({
13205         addHooks: function () {
13206                 this._map.on('dblclick', this._onDoubleClick, this);
13207         },
13208
13209         removeHooks: function () {
13210                 this._map.off('dblclick', this._onDoubleClick, this);
13211         },
13212
13213         _onDoubleClick: function (e) {
13214                 var map = this._map,
13215                     oldZoom = map.getZoom(),
13216                     delta = map.options.zoomDelta,
13217                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13218
13219                 if (map.options.doubleClickZoom === 'center') {
13220                         map.setZoom(zoom);
13221                 } else {
13222                         map.setZoomAround(e.containerPoint, zoom);
13223                 }
13224         }
13225 });
13226
13227 // @section Handlers
13228 //
13229 // Map properties include interaction handlers that allow you to control
13230 // interaction behavior in runtime, enabling or disabling certain features such
13231 // as dragging or touch zoom (see `Handler` methods). For example:
13232 //
13233 // ```js
13234 // map.doubleClickZoom.disable();
13235 // ```
13236 //
13237 // @property doubleClickZoom: Handler
13238 // Double click zoom handler.
13239 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13240
13241 /*
13242  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13243  */
13244
13245 // @namespace Map
13246 // @section Interaction Options
13247 Map.mergeOptions({
13248         // @option dragging: Boolean = true
13249         // Whether the map be draggable with mouse/touch or not.
13250         dragging: true,
13251
13252         // @section Panning Inertia Options
13253         // @option inertia: Boolean = *
13254         // If enabled, panning of the map will have an inertia effect where
13255         // the map builds momentum while dragging and continues moving in
13256         // the same direction for some time. Feels especially nice on touch
13257         // devices. Enabled by default unless running on old Android devices.
13258         inertia: !android23,
13259
13260         // @option inertiaDeceleration: Number = 3000
13261         // The rate with which the inertial movement slows down, in pixels/second².
13262         inertiaDeceleration: 3400, // px/s^2
13263
13264         // @option inertiaMaxSpeed: Number = Infinity
13265         // Max speed of the inertial movement, in pixels/second.
13266         inertiaMaxSpeed: Infinity, // px/s
13267
13268         // @option easeLinearity: Number = 0.2
13269         easeLinearity: 0.2,
13270
13271         // TODO refactor, move to CRS
13272         // @option worldCopyJump: Boolean = false
13273         // With this option enabled, the map tracks when you pan to another "copy"
13274         // of the world and seamlessly jumps to the original one so that all overlays
13275         // like markers and vector layers are still visible.
13276         worldCopyJump: false,
13277
13278         // @option maxBoundsViscosity: Number = 0.0
13279         // If `maxBounds` is set, this option will control how solid the bounds
13280         // are when dragging the map around. The default value of `0.0` allows the
13281         // user to drag outside the bounds at normal speed, higher values will
13282         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13283         // solid, preventing the user from dragging outside the bounds.
13284         maxBoundsViscosity: 0.0
13285 });
13286
13287 var Drag = Handler.extend({
13288         addHooks: function () {
13289                 if (!this._draggable) {
13290                         var map = this._map;
13291
13292                         this._draggable = new Draggable(map._mapPane, map._container);
13293
13294                         this._draggable.on({
13295                                 dragstart: this._onDragStart,
13296                                 drag: this._onDrag,
13297                                 dragend: this._onDragEnd
13298                         }, this);
13299
13300                         this._draggable.on('predrag', this._onPreDragLimit, this);
13301                         if (map.options.worldCopyJump) {
13302                                 this._draggable.on('predrag', this._onPreDragWrap, this);
13303                                 map.on('zoomend', this._onZoomEnd, this);
13304
13305                                 map.whenReady(this._onZoomEnd, this);
13306                         }
13307                 }
13308                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13309                 this._draggable.enable();
13310                 this._positions = [];
13311                 this._times = [];
13312         },
13313
13314         removeHooks: function () {
13315                 removeClass(this._map._container, 'leaflet-grab');
13316                 removeClass(this._map._container, 'leaflet-touch-drag');
13317                 this._draggable.disable();
13318         },
13319
13320         moved: function () {
13321                 return this._draggable && this._draggable._moved;
13322         },
13323
13324         moving: function () {
13325                 return this._draggable && this._draggable._moving;
13326         },
13327
13328         _onDragStart: function () {
13329                 var map = this._map;
13330
13331                 map._stop();
13332                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13333                         var bounds = toLatLngBounds(this._map.options.maxBounds);
13334
13335                         this._offsetLimit = toBounds(
13336                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13337                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13338                                         .add(this._map.getSize()));
13339
13340                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13341                 } else {
13342                         this._offsetLimit = null;
13343                 }
13344
13345                 map
13346                     .fire('movestart')
13347                     .fire('dragstart');
13348
13349                 if (map.options.inertia) {
13350                         this._positions = [];
13351                         this._times = [];
13352                 }
13353         },
13354
13355         _onDrag: function (e) {
13356                 if (this._map.options.inertia) {
13357                         var time = this._lastTime = +new Date(),
13358                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13359
13360                         this._positions.push(pos);
13361                         this._times.push(time);
13362
13363                         this._prunePositions(time);
13364                 }
13365
13366                 this._map
13367                     .fire('move', e)
13368                     .fire('drag', e);
13369         },
13370
13371         _prunePositions: function (time) {
13372                 while (this._positions.length > 1 && time - this._times[0] > 50) {
13373                         this._positions.shift();
13374                         this._times.shift();
13375                 }
13376         },
13377
13378         _onZoomEnd: function () {
13379                 var pxCenter = this._map.getSize().divideBy(2),
13380                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13381
13382                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13383                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13384         },
13385
13386         _viscousLimit: function (value, threshold) {
13387                 return value - (value - threshold) * this._viscosity;
13388         },
13389
13390         _onPreDragLimit: function () {
13391                 if (!this._viscosity || !this._offsetLimit) { return; }
13392
13393                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13394
13395                 var limit = this._offsetLimit;
13396                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13397                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13398                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13399                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13400
13401                 this._draggable._newPos = this._draggable._startPos.add(offset);
13402         },
13403
13404         _onPreDragWrap: function () {
13405                 // TODO refactor to be able to adjust map pane position after zoom
13406                 var worldWidth = this._worldWidth,
13407                     halfWidth = Math.round(worldWidth / 2),
13408                     dx = this._initialWorldOffset,
13409                     x = this._draggable._newPos.x,
13410                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13411                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13412                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13413
13414                 this._draggable._absPos = this._draggable._newPos.clone();
13415                 this._draggable._newPos.x = newX;
13416         },
13417
13418         _onDragEnd: function (e) {
13419                 var map = this._map,
13420                     options = map.options,
13421
13422                     noInertia = !options.inertia || this._times.length < 2;
13423
13424                 map.fire('dragend', e);
13425
13426                 if (noInertia) {
13427                         map.fire('moveend');
13428
13429                 } else {
13430                         this._prunePositions(+new Date());
13431
13432                         var direction = this._lastPos.subtract(this._positions[0]),
13433                             duration = (this._lastTime - this._times[0]) / 1000,
13434                             ease = options.easeLinearity,
13435
13436                             speedVector = direction.multiplyBy(ease / duration),
13437                             speed = speedVector.distanceTo([0, 0]),
13438
13439                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13440                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13441
13442                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13443                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13444
13445                         if (!offset.x && !offset.y) {
13446                                 map.fire('moveend');
13447
13448                         } else {
13449                                 offset = map._limitOffset(offset, map.options.maxBounds);
13450
13451                                 requestAnimFrame(function () {
13452                                         map.panBy(offset, {
13453                                                 duration: decelerationDuration,
13454                                                 easeLinearity: ease,
13455                                                 noMoveStart: true,
13456                                                 animate: true
13457                                         });
13458                                 });
13459                         }
13460                 }
13461         }
13462 });
13463
13464 // @section Handlers
13465 // @property dragging: Handler
13466 // Map dragging handler (by both mouse and touch).
13467 Map.addInitHook('addHandler', 'dragging', Drag);
13468
13469 /*
13470  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13471  */
13472
13473 // @namespace Map
13474 // @section Keyboard Navigation Options
13475 Map.mergeOptions({
13476         // @option keyboard: Boolean = true
13477         // Makes the map focusable and allows users to navigate the map with keyboard
13478         // arrows and `+`/`-` keys.
13479         keyboard: true,
13480
13481         // @option keyboardPanDelta: Number = 80
13482         // Amount of pixels to pan when pressing an arrow key.
13483         keyboardPanDelta: 80
13484 });
13485
13486 var Keyboard = Handler.extend({
13487
13488         keyCodes: {
13489                 left:    [37],
13490                 right:   [39],
13491                 down:    [40],
13492                 up:      [38],
13493                 zoomIn:  [187, 107, 61, 171],
13494                 zoomOut: [189, 109, 54, 173]
13495         },
13496
13497         initialize: function (map) {
13498                 this._map = map;
13499
13500                 this._setPanDelta(map.options.keyboardPanDelta);
13501                 this._setZoomDelta(map.options.zoomDelta);
13502         },
13503
13504         addHooks: function () {
13505                 var container = this._map._container;
13506
13507                 // make the container focusable by tabbing
13508                 if (container.tabIndex <= 0) {
13509                         container.tabIndex = '0';
13510                 }
13511
13512                 on(container, {
13513                         focus: this._onFocus,
13514                         blur: this._onBlur,
13515                         mousedown: this._onMouseDown
13516                 }, this);
13517
13518                 this._map.on({
13519                         focus: this._addHooks,
13520                         blur: this._removeHooks
13521                 }, this);
13522         },
13523
13524         removeHooks: function () {
13525                 this._removeHooks();
13526
13527                 off(this._map._container, {
13528                         focus: this._onFocus,
13529                         blur: this._onBlur,
13530                         mousedown: this._onMouseDown
13531                 }, this);
13532
13533                 this._map.off({
13534                         focus: this._addHooks,
13535                         blur: this._removeHooks
13536                 }, this);
13537         },
13538
13539         _onMouseDown: function () {
13540                 if (this._focused) { return; }
13541
13542                 var body = document.body,
13543                     docEl = document.documentElement,
13544                     top = body.scrollTop || docEl.scrollTop,
13545                     left = body.scrollLeft || docEl.scrollLeft;
13546
13547                 this._map._container.focus();
13548
13549                 window.scrollTo(left, top);
13550         },
13551
13552         _onFocus: function () {
13553                 this._focused = true;
13554                 this._map.fire('focus');
13555         },
13556
13557         _onBlur: function () {
13558                 this._focused = false;
13559                 this._map.fire('blur');
13560         },
13561
13562         _setPanDelta: function (panDelta) {
13563                 var keys = this._panKeys = {},
13564                     codes = this.keyCodes,
13565                     i, len;
13566
13567                 for (i = 0, len = codes.left.length; i < len; i++) {
13568                         keys[codes.left[i]] = [-1 * panDelta, 0];
13569                 }
13570                 for (i = 0, len = codes.right.length; i < len; i++) {
13571                         keys[codes.right[i]] = [panDelta, 0];
13572                 }
13573                 for (i = 0, len = codes.down.length; i < len; i++) {
13574                         keys[codes.down[i]] = [0, panDelta];
13575                 }
13576                 for (i = 0, len = codes.up.length; i < len; i++) {
13577                         keys[codes.up[i]] = [0, -1 * panDelta];
13578                 }
13579         },
13580
13581         _setZoomDelta: function (zoomDelta) {
13582                 var keys = this._zoomKeys = {},
13583                     codes = this.keyCodes,
13584                     i, len;
13585
13586                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13587                         keys[codes.zoomIn[i]] = zoomDelta;
13588                 }
13589                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13590                         keys[codes.zoomOut[i]] = -zoomDelta;
13591                 }
13592         },
13593
13594         _addHooks: function () {
13595                 on(document, 'keydown', this._onKeyDown, this);
13596         },
13597
13598         _removeHooks: function () {
13599                 off(document, 'keydown', this._onKeyDown, this);
13600         },
13601
13602         _onKeyDown: function (e) {
13603                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13604
13605                 var key = e.keyCode,
13606                     map = this._map,
13607                     offset;
13608
13609                 if (key in this._panKeys) {
13610                         if (!map._panAnim || !map._panAnim._inProgress) {
13611                                 offset = this._panKeys[key];
13612                                 if (e.shiftKey) {
13613                                         offset = toPoint(offset).multiplyBy(3);
13614                                 }
13615
13616                                 map.panBy(offset);
13617
13618                                 if (map.options.maxBounds) {
13619                                         map.panInsideBounds(map.options.maxBounds);
13620                                 }
13621                         }
13622                 } else if (key in this._zoomKeys) {
13623                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13624
13625                 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13626                         map.closePopup();
13627
13628                 } else {
13629                         return;
13630                 }
13631
13632                 stop(e);
13633         }
13634 });
13635
13636 // @section Handlers
13637 // @section Handlers
13638 // @property keyboard: Handler
13639 // Keyboard navigation handler.
13640 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13641
13642 /*
13643  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13644  */
13645
13646 // @namespace Map
13647 // @section Interaction Options
13648 Map.mergeOptions({
13649         // @section Mousewheel options
13650         // @option scrollWheelZoom: Boolean|String = true
13651         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13652         // it will zoom to the center of the view regardless of where the mouse was.
13653         scrollWheelZoom: true,
13654
13655         // @option wheelDebounceTime: Number = 40
13656         // Limits the rate at which a wheel can fire (in milliseconds). By default
13657         // user can't zoom via wheel more often than once per 40 ms.
13658         wheelDebounceTime: 40,
13659
13660         // @option wheelPxPerZoomLevel: Number = 60
13661         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13662         // mean a change of one full zoom level. Smaller values will make wheel-zooming
13663         // faster (and vice versa).
13664         wheelPxPerZoomLevel: 60
13665 });
13666
13667 var ScrollWheelZoom = Handler.extend({
13668         addHooks: function () {
13669                 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13670
13671                 this._delta = 0;
13672         },
13673
13674         removeHooks: function () {
13675                 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13676         },
13677
13678         _onWheelScroll: function (e) {
13679                 var delta = getWheelDelta(e);
13680
13681                 var debounce = this._map.options.wheelDebounceTime;
13682
13683                 this._delta += delta;
13684                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13685
13686                 if (!this._startTime) {
13687                         this._startTime = +new Date();
13688                 }
13689
13690                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13691
13692                 clearTimeout(this._timer);
13693                 this._timer = setTimeout(bind(this._performZoom, this), left);
13694
13695                 stop(e);
13696         },
13697
13698         _performZoom: function () {
13699                 var map = this._map,
13700                     zoom = map.getZoom(),
13701                     snap = this._map.options.zoomSnap || 0;
13702
13703                 map._stop(); // stop panning and fly animations if any
13704
13705                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13706                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13707                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13708                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13709                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13710
13711                 this._delta = 0;
13712                 this._startTime = null;
13713
13714                 if (!delta) { return; }
13715
13716                 if (map.options.scrollWheelZoom === 'center') {
13717                         map.setZoom(zoom + delta);
13718                 } else {
13719                         map.setZoomAround(this._lastMousePos, zoom + delta);
13720                 }
13721         }
13722 });
13723
13724 // @section Handlers
13725 // @property scrollWheelZoom: Handler
13726 // Scroll wheel zoom handler.
13727 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13728
13729 /*
13730  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13731  */
13732
13733 // @namespace Map
13734 // @section Interaction Options
13735 Map.mergeOptions({
13736         // @section Touch interaction options
13737         // @option tap: Boolean = true
13738         // Enables mobile hacks for supporting instant taps (fixing 200ms click
13739         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13740         tap: true,
13741
13742         // @option tapTolerance: Number = 15
13743         // The max number of pixels a user can shift his finger during touch
13744         // for it to be considered a valid tap.
13745         tapTolerance: 15
13746 });
13747
13748 var Tap = Handler.extend({
13749         addHooks: function () {
13750                 on(this._map._container, 'touchstart', this._onDown, this);
13751         },
13752
13753         removeHooks: function () {
13754                 off(this._map._container, 'touchstart', this._onDown, this);
13755         },
13756
13757         _onDown: function (e) {
13758                 if (!e.touches) { return; }
13759
13760                 preventDefault(e);
13761
13762                 this._fireClick = true;
13763
13764                 // don't simulate click or track longpress if more than 1 touch
13765                 if (e.touches.length > 1) {
13766                         this._fireClick = false;
13767                         clearTimeout(this._holdTimeout);
13768                         return;
13769                 }
13770
13771                 var first = e.touches[0],
13772                     el = first.target;
13773
13774                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13775
13776                 // if touching a link, highlight it
13777                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13778                         addClass(el, 'leaflet-active');
13779                 }
13780
13781                 // simulate long hold but setting a timeout
13782                 this._holdTimeout = setTimeout(bind(function () {
13783                         if (this._isTapValid()) {
13784                                 this._fireClick = false;
13785                                 this._onUp();
13786                                 this._simulateEvent('contextmenu', first);
13787                         }
13788                 }, this), 1000);
13789
13790                 this._simulateEvent('mousedown', first);
13791
13792                 on(document, {
13793                         touchmove: this._onMove,
13794                         touchend: this._onUp
13795                 }, this);
13796         },
13797
13798         _onUp: function (e) {
13799                 clearTimeout(this._holdTimeout);
13800
13801                 off(document, {
13802                         touchmove: this._onMove,
13803                         touchend: this._onUp
13804                 }, this);
13805
13806                 if (this._fireClick && e && e.changedTouches) {
13807
13808                         var first = e.changedTouches[0],
13809                             el = first.target;
13810
13811                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13812                                 removeClass(el, 'leaflet-active');
13813                         }
13814
13815                         this._simulateEvent('mouseup', first);
13816
13817                         // simulate click if the touch didn't move too much
13818                         if (this._isTapValid()) {
13819                                 this._simulateEvent('click', first);
13820                         }
13821                 }
13822         },
13823
13824         _isTapValid: function () {
13825                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13826         },
13827
13828         _onMove: function (e) {
13829                 var first = e.touches[0];
13830                 this._newPos = new Point(first.clientX, first.clientY);
13831                 this._simulateEvent('mousemove', first);
13832         },
13833
13834         _simulateEvent: function (type, e) {
13835                 var simulatedEvent = document.createEvent('MouseEvents');
13836
13837                 simulatedEvent._simulated = true;
13838                 e.target._simulatedClick = true;
13839
13840                 simulatedEvent.initMouseEvent(
13841                         type, true, true, window, 1,
13842                         e.screenX, e.screenY,
13843                         e.clientX, e.clientY,
13844                         false, false, false, false, 0, null);
13845
13846                 e.target.dispatchEvent(simulatedEvent);
13847         }
13848 });
13849
13850 // @section Handlers
13851 // @property tap: Handler
13852 // Mobile touch hacks (quick tap and touch hold) handler.
13853 if (touch && !pointer) {
13854         Map.addInitHook('addHandler', 'tap', Tap);
13855 }
13856
13857 /*
13858  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13859  */
13860
13861 // @namespace Map
13862 // @section Interaction Options
13863 Map.mergeOptions({
13864         // @section Touch interaction options
13865         // @option touchZoom: Boolean|String = *
13866         // Whether the map can be zoomed by touch-dragging with two fingers. If
13867         // passed `'center'`, it will zoom to the center of the view regardless of
13868         // where the touch events (fingers) were. Enabled for touch-capable web
13869         // browsers except for old Androids.
13870         touchZoom: touch && !android23,
13871
13872         // @option bounceAtZoomLimits: Boolean = true
13873         // Set it to false if you don't want the map to zoom beyond min/max zoom
13874         // and then bounce back when pinch-zooming.
13875         bounceAtZoomLimits: true
13876 });
13877
13878 var TouchZoom = Handler.extend({
13879         addHooks: function () {
13880                 addClass(this._map._container, 'leaflet-touch-zoom');
13881                 on(this._map._container, 'touchstart', this._onTouchStart, this);
13882         },
13883
13884         removeHooks: function () {
13885                 removeClass(this._map._container, 'leaflet-touch-zoom');
13886                 off(this._map._container, 'touchstart', this._onTouchStart, this);
13887         },
13888
13889         _onTouchStart: function (e) {
13890                 var map = this._map;
13891                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13892
13893                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13894                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
13895
13896                 this._centerPoint = map.getSize()._divideBy(2);
13897                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13898                 if (map.options.touchZoom !== 'center') {
13899                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13900                 }
13901
13902                 this._startDist = p1.distanceTo(p2);
13903                 this._startZoom = map.getZoom();
13904
13905                 this._moved = false;
13906                 this._zooming = true;
13907
13908                 map._stop();
13909
13910                 on(document, 'touchmove', this._onTouchMove, this);
13911                 on(document, 'touchend', this._onTouchEnd, this);
13912
13913                 preventDefault(e);
13914         },
13915
13916         _onTouchMove: function (e) {
13917                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13918
13919                 var map = this._map,
13920                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
13921                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
13922                     scale = p1.distanceTo(p2) / this._startDist;
13923
13924                 this._zoom = map.getScaleZoom(scale, this._startZoom);
13925
13926                 if (!map.options.bounceAtZoomLimits && (
13927                         (this._zoom < map.getMinZoom() && scale < 1) ||
13928                         (this._zoom > map.getMaxZoom() && scale > 1))) {
13929                         this._zoom = map._limitZoom(this._zoom);
13930                 }
13931
13932                 if (map.options.touchZoom === 'center') {
13933                         this._center = this._startLatLng;
13934                         if (scale === 1) { return; }
13935                 } else {
13936                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13937                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13938                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13939                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13940                 }
13941
13942                 if (!this._moved) {
13943                         map._moveStart(true, false);
13944                         this._moved = true;
13945                 }
13946
13947                 cancelAnimFrame(this._animRequest);
13948
13949                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13950                 this._animRequest = requestAnimFrame(moveFn, this, true);
13951
13952                 preventDefault(e);
13953         },
13954
13955         _onTouchEnd: function () {
13956                 if (!this._moved || !this._zooming) {
13957                         this._zooming = false;
13958                         return;
13959                 }
13960
13961                 this._zooming = false;
13962                 cancelAnimFrame(this._animRequest);
13963
13964                 off(document, 'touchmove', this._onTouchMove);
13965                 off(document, 'touchend', this._onTouchEnd);
13966
13967                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13968                 if (this._map.options.zoomAnimation) {
13969                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13970                 } else {
13971                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13972                 }
13973         }
13974 });
13975
13976 // @section Handlers
13977 // @property touchZoom: Handler
13978 // Touch zoom handler.
13979 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13980
13981 Map.BoxZoom = BoxZoom;
13982 Map.DoubleClickZoom = DoubleClickZoom;
13983 Map.Drag = Drag;
13984 Map.Keyboard = Keyboard;
13985 Map.ScrollWheelZoom = ScrollWheelZoom;
13986 Map.Tap = Tap;
13987 Map.TouchZoom = TouchZoom;
13988
13989 Object.freeze = freeze;
13990
13991 exports.version = version;
13992 exports.Control = Control;
13993 exports.control = control;
13994 exports.Browser = Browser;
13995 exports.Evented = Evented;
13996 exports.Mixin = Mixin;
13997 exports.Util = Util;
13998 exports.Class = Class;
13999 exports.Handler = Handler;
14000 exports.extend = extend;
14001 exports.bind = bind;
14002 exports.stamp = stamp;
14003 exports.setOptions = setOptions;
14004 exports.DomEvent = DomEvent;
14005 exports.DomUtil = DomUtil;
14006 exports.PosAnimation = PosAnimation;
14007 exports.Draggable = Draggable;
14008 exports.LineUtil = LineUtil;
14009 exports.PolyUtil = PolyUtil;
14010 exports.Point = Point;
14011 exports.point = toPoint;
14012 exports.Bounds = Bounds;
14013 exports.bounds = toBounds;
14014 exports.Transformation = Transformation;
14015 exports.transformation = toTransformation;
14016 exports.Projection = index;
14017 exports.LatLng = LatLng;
14018 exports.latLng = toLatLng;
14019 exports.LatLngBounds = LatLngBounds;
14020 exports.latLngBounds = toLatLngBounds;
14021 exports.CRS = CRS;
14022 exports.GeoJSON = GeoJSON;
14023 exports.geoJSON = geoJSON;
14024 exports.geoJson = geoJson;
14025 exports.Layer = Layer;
14026 exports.LayerGroup = LayerGroup;
14027 exports.layerGroup = layerGroup;
14028 exports.FeatureGroup = FeatureGroup;
14029 exports.featureGroup = featureGroup;
14030 exports.ImageOverlay = ImageOverlay;
14031 exports.imageOverlay = imageOverlay;
14032 exports.VideoOverlay = VideoOverlay;
14033 exports.videoOverlay = videoOverlay;
14034 exports.SVGOverlay = SVGOverlay;
14035 exports.svgOverlay = svgOverlay;
14036 exports.DivOverlay = DivOverlay;
14037 exports.Popup = Popup;
14038 exports.popup = popup;
14039 exports.Tooltip = Tooltip;
14040 exports.tooltip = tooltip;
14041 exports.Icon = Icon;
14042 exports.icon = icon;
14043 exports.DivIcon = DivIcon;
14044 exports.divIcon = divIcon;
14045 exports.Marker = Marker;
14046 exports.marker = marker;
14047 exports.TileLayer = TileLayer;
14048 exports.tileLayer = tileLayer;
14049 exports.GridLayer = GridLayer;
14050 exports.gridLayer = gridLayer;
14051 exports.SVG = SVG;
14052 exports.svg = svg$1;
14053 exports.Renderer = Renderer;
14054 exports.Canvas = Canvas;
14055 exports.canvas = canvas$1;
14056 exports.Path = Path;
14057 exports.CircleMarker = CircleMarker;
14058 exports.circleMarker = circleMarker;
14059 exports.Circle = Circle;
14060 exports.circle = circle;
14061 exports.Polyline = Polyline;
14062 exports.polyline = polyline;
14063 exports.Polygon = Polygon;
14064 exports.polygon = polygon;
14065 exports.Rectangle = Rectangle;
14066 exports.rectangle = rectangle;
14067 exports.Map = Map;
14068 exports.map = createMap;
14069
14070 var oldL = window.L;
14071 exports.noConflict = function() {
14072         window.L = oldL;
14073         return this;
14074 }
14075
14076 // Always export us to window global (see #2364)
14077 window.L = exports;
14078
14079 })));
14080 //# sourceMappingURL=leaflet-src.js.map