]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Update leaflet to 1.5.1
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /* @preserve
2  * Leaflet 1.5.1+build.2e3e0ff, a JS library for interactive maps. http://leafletjs.com
3  * (c) 2010-2018 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.5.1+build.2e3e0ffb";
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         digits = (digits === undefined ? 6 : digits);
131         return +(Math.round(num + ('e+' + digits)) + ('e-' + digits));
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 = !!(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
1931 // @property canvas: Boolean
1932 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1933 var canvas = (function () {
1934         return !!document.createElement('canvas').getContext;
1935 }());
1936
1937 // @property svg: Boolean
1938 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1939 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1940
1941 // @property vml: Boolean
1942 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1943 var vml = !svg && (function () {
1944         try {
1945                 var div = document.createElement('div');
1946                 div.innerHTML = '<v:shape adj="1"/>';
1947
1948                 var shape = div.firstChild;
1949                 shape.style.behavior = 'url(#default#VML)';
1950
1951                 return shape && (typeof shape.adj === 'object');
1952
1953         } catch (e) {
1954                 return false;
1955         }
1956 }());
1957
1958
1959 function userAgentContains(str) {
1960         return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1961 }
1962
1963
1964 var Browser = (Object.freeze || Object)({
1965         ie: ie,
1966         ielt9: ielt9,
1967         edge: edge,
1968         webkit: webkit,
1969         android: android,
1970         android23: android23,
1971         androidStock: androidStock,
1972         opera: opera,
1973         chrome: chrome,
1974         gecko: gecko,
1975         safari: safari,
1976         phantom: phantom,
1977         opera12: opera12,
1978         win: win,
1979         ie3d: ie3d,
1980         webkit3d: webkit3d,
1981         gecko3d: gecko3d,
1982         any3d: any3d,
1983         mobile: mobile,
1984         mobileWebkit: mobileWebkit,
1985         mobileWebkit3d: mobileWebkit3d,
1986         msPointer: msPointer,
1987         pointer: pointer,
1988         touch: touch,
1989         mobileOpera: mobileOpera,
1990         mobileGecko: mobileGecko,
1991         retina: retina,
1992         canvas: canvas,
1993         svg: svg,
1994         vml: vml
1995 });
1996
1997 /*
1998  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1999  */
2000
2001
2002 var POINTER_DOWN =   msPointer ? 'MSPointerDown'   : 'pointerdown';
2003 var POINTER_MOVE =   msPointer ? 'MSPointerMove'   : 'pointermove';
2004 var POINTER_UP =     msPointer ? 'MSPointerUp'     : 'pointerup';
2005 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2006 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2007
2008 var _pointers = {};
2009 var _pointerDocListener = false;
2010
2011 // DomEvent.DoubleTap needs to know about this
2012 var _pointersCount = 0;
2013
2014 // Provides a touch events wrapper for (ms)pointer events.
2015 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2016
2017 function addPointerListener(obj, type, handler, id) {
2018         if (type === 'touchstart') {
2019                 _addPointerStart(obj, handler, id);
2020
2021         } else if (type === 'touchmove') {
2022                 _addPointerMove(obj, handler, id);
2023
2024         } else if (type === 'touchend') {
2025                 _addPointerEnd(obj, handler, id);
2026         }
2027
2028         return this;
2029 }
2030
2031 function removePointerListener(obj, type, id) {
2032         var handler = obj['_leaflet_' + type + id];
2033
2034         if (type === 'touchstart') {
2035                 obj.removeEventListener(POINTER_DOWN, handler, false);
2036
2037         } else if (type === 'touchmove') {
2038                 obj.removeEventListener(POINTER_MOVE, handler, false);
2039
2040         } else if (type === 'touchend') {
2041                 obj.removeEventListener(POINTER_UP, handler, false);
2042                 obj.removeEventListener(POINTER_CANCEL, handler, false);
2043         }
2044
2045         return this;
2046 }
2047
2048 function _addPointerStart(obj, handler, id) {
2049         var onDown = bind(function (e) {
2050                 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2051                         // In IE11, some touch events needs to fire for form controls, or
2052                         // the controls will stop working. We keep a whitelist of tag names that
2053                         // need these events. For other target tags, we prevent default on the event.
2054                         if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2055                                 preventDefault(e);
2056                         } else {
2057                                 return;
2058                         }
2059                 }
2060
2061                 _handlePointer(e, handler);
2062         });
2063
2064         obj['_leaflet_touchstart' + id] = onDown;
2065         obj.addEventListener(POINTER_DOWN, onDown, false);
2066
2067         // need to keep track of what pointers and how many are active to provide e.touches emulation
2068         if (!_pointerDocListener) {
2069                 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2070                 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2071                 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2072                 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2073                 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2074
2075                 _pointerDocListener = true;
2076         }
2077 }
2078
2079 function _globalPointerDown(e) {
2080         _pointers[e.pointerId] = e;
2081         _pointersCount++;
2082 }
2083
2084 function _globalPointerMove(e) {
2085         if (_pointers[e.pointerId]) {
2086                 _pointers[e.pointerId] = e;
2087         }
2088 }
2089
2090 function _globalPointerUp(e) {
2091         delete _pointers[e.pointerId];
2092         _pointersCount--;
2093 }
2094
2095 function _handlePointer(e, handler) {
2096         e.touches = [];
2097         for (var i in _pointers) {
2098                 e.touches.push(_pointers[i]);
2099         }
2100         e.changedTouches = [e];
2101
2102         handler(e);
2103 }
2104
2105 function _addPointerMove(obj, handler, id) {
2106         var onMove = function (e) {
2107                 // don't fire touch moves when mouse isn't down
2108                 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2109
2110                 _handlePointer(e, handler);
2111         };
2112
2113         obj['_leaflet_touchmove' + id] = onMove;
2114         obj.addEventListener(POINTER_MOVE, onMove, false);
2115 }
2116
2117 function _addPointerEnd(obj, handler, id) {
2118         var onUp = function (e) {
2119                 _handlePointer(e, handler);
2120         };
2121
2122         obj['_leaflet_touchend' + id] = onUp;
2123         obj.addEventListener(POINTER_UP, onUp, false);
2124         obj.addEventListener(POINTER_CANCEL, onUp, false);
2125 }
2126
2127 /*
2128  * Extends the event handling code with double tap support for mobile browsers.
2129  */
2130
2131 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2132 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2133 var _pre = '_leaflet_';
2134
2135 // inspired by Zepto touch code by Thomas Fuchs
2136 function addDoubleTapListener(obj, handler, id) {
2137         var last, touch$$1,
2138             doubleTap = false,
2139             delay = 250;
2140
2141         function onTouchStart(e) {
2142                 var count;
2143
2144                 if (pointer) {
2145                         if ((!edge) || e.pointerType === 'mouse') { return; }
2146                         count = _pointersCount;
2147                 } else {
2148                         count = e.touches.length;
2149                 }
2150
2151                 if (count > 1) { return; }
2152
2153                 var now = Date.now(),
2154                     delta = now - (last || now);
2155
2156                 touch$$1 = e.touches ? e.touches[0] : e;
2157                 doubleTap = (delta > 0 && delta <= delay);
2158                 last = now;
2159         }
2160
2161         function onTouchEnd(e) {
2162                 if (doubleTap && !touch$$1.cancelBubble) {
2163                         if (pointer) {
2164                                 if ((!edge) || e.pointerType === 'mouse') { return; }
2165                                 // work around .type being readonly with MSPointer* events
2166                                 var newTouch = {},
2167                                     prop, i;
2168
2169                                 for (i in touch$$1) {
2170                                         prop = touch$$1[i];
2171                                         newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2172                                 }
2173                                 touch$$1 = newTouch;
2174                         }
2175                         touch$$1.type = 'dblclick';
2176                         touch$$1.button = 0;
2177                         handler(touch$$1);
2178                         last = null;
2179                 }
2180         }
2181
2182         obj[_pre + _touchstart + id] = onTouchStart;
2183         obj[_pre + _touchend + id] = onTouchEnd;
2184         obj[_pre + 'dblclick' + id] = handler;
2185
2186         obj.addEventListener(_touchstart, onTouchStart, false);
2187         obj.addEventListener(_touchend, onTouchEnd, false);
2188
2189         // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2190         // the browser doesn't fire touchend/pointerup events but does fire
2191         // native dblclicks. See #4127.
2192         // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2193         obj.addEventListener('dblclick', handler, false);
2194
2195         return this;
2196 }
2197
2198 function removeDoubleTapListener(obj, id) {
2199         var touchstart = obj[_pre + _touchstart + id],
2200             touchend = obj[_pre + _touchend + id],
2201             dblclick = obj[_pre + 'dblclick' + id];
2202
2203         obj.removeEventListener(_touchstart, touchstart, false);
2204         obj.removeEventListener(_touchend, touchend, false);
2205         if (!edge) {
2206                 obj.removeEventListener('dblclick', dblclick, false);
2207         }
2208
2209         return this;
2210 }
2211
2212 /*
2213  * @namespace DomUtil
2214  *
2215  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2216  * tree, used by Leaflet internally.
2217  *
2218  * Most functions expecting or returning a `HTMLElement` also work for
2219  * SVG elements. The only difference is that classes refer to CSS classes
2220  * in HTML and SVG classes in SVG.
2221  */
2222
2223
2224 // @property TRANSFORM: String
2225 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2226 var TRANSFORM = testProp(
2227         ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2228
2229 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2230 // the same for the transitionend event, in particular the Android 4.1 stock browser
2231
2232 // @property TRANSITION: String
2233 // Vendor-prefixed transition style name.
2234 var TRANSITION = testProp(
2235         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2236
2237 // @property TRANSITION_END: String
2238 // Vendor-prefixed transitionend event name.
2239 var TRANSITION_END =
2240         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2241
2242
2243 // @function get(id: String|HTMLElement): HTMLElement
2244 // Returns an element given its DOM id, or returns the element itself
2245 // if it was passed directly.
2246 function get(id) {
2247         return typeof id === 'string' ? document.getElementById(id) : id;
2248 }
2249
2250 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2251 // Returns the value for a certain style attribute on an element,
2252 // including computed values or values set through CSS.
2253 function getStyle(el, style) {
2254         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2255
2256         if ((!value || value === 'auto') && document.defaultView) {
2257                 var css = document.defaultView.getComputedStyle(el, null);
2258                 value = css ? css[style] : null;
2259         }
2260         return value === 'auto' ? null : value;
2261 }
2262
2263 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2264 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2265 function create$1(tagName, className, container) {
2266         var el = document.createElement(tagName);
2267         el.className = className || '';
2268
2269         if (container) {
2270                 container.appendChild(el);
2271         }
2272         return el;
2273 }
2274
2275 // @function remove(el: HTMLElement)
2276 // Removes `el` from its parent element
2277 function remove(el) {
2278         var parent = el.parentNode;
2279         if (parent) {
2280                 parent.removeChild(el);
2281         }
2282 }
2283
2284 // @function empty(el: HTMLElement)
2285 // Removes all of `el`'s children elements from `el`
2286 function empty(el) {
2287         while (el.firstChild) {
2288                 el.removeChild(el.firstChild);
2289         }
2290 }
2291
2292 // @function toFront(el: HTMLElement)
2293 // Makes `el` the last child of its parent, so it renders in front of the other children.
2294 function toFront(el) {
2295         var parent = el.parentNode;
2296         if (parent && parent.lastChild !== el) {
2297                 parent.appendChild(el);
2298         }
2299 }
2300
2301 // @function toBack(el: HTMLElement)
2302 // Makes `el` the first child of its parent, so it renders behind the other children.
2303 function toBack(el) {
2304         var parent = el.parentNode;
2305         if (parent && parent.firstChild !== el) {
2306                 parent.insertBefore(el, parent.firstChild);
2307         }
2308 }
2309
2310 // @function hasClass(el: HTMLElement, name: String): Boolean
2311 // Returns `true` if the element's class attribute contains `name`.
2312 function hasClass(el, name) {
2313         if (el.classList !== undefined) {
2314                 return el.classList.contains(name);
2315         }
2316         var className = getClass(el);
2317         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2318 }
2319
2320 // @function addClass(el: HTMLElement, name: String)
2321 // Adds `name` to the element's class attribute.
2322 function addClass(el, name) {
2323         if (el.classList !== undefined) {
2324                 var classes = splitWords(name);
2325                 for (var i = 0, len = classes.length; i < len; i++) {
2326                         el.classList.add(classes[i]);
2327                 }
2328         } else if (!hasClass(el, name)) {
2329                 var className = getClass(el);
2330                 setClass(el, (className ? className + ' ' : '') + name);
2331         }
2332 }
2333
2334 // @function removeClass(el: HTMLElement, name: String)
2335 // Removes `name` from the element's class attribute.
2336 function removeClass(el, name) {
2337         if (el.classList !== undefined) {
2338                 el.classList.remove(name);
2339         } else {
2340                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2341         }
2342 }
2343
2344 // @function setClass(el: HTMLElement, name: String)
2345 // Sets the element's class.
2346 function setClass(el, name) {
2347         if (el.className.baseVal === undefined) {
2348                 el.className = name;
2349         } else {
2350                 // in case of SVG element
2351                 el.className.baseVal = name;
2352         }
2353 }
2354
2355 // @function getClass(el: HTMLElement): String
2356 // Returns the element's class.
2357 function getClass(el) {
2358         // Check if the element is an SVGElementInstance and use the correspondingElement instead
2359         // (Required for linked SVG elements in IE11.)
2360         if (el.correspondingElement) {
2361                 el = el.correspondingElement;
2362         }
2363         return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2364 }
2365
2366 // @function setOpacity(el: HTMLElement, opacity: Number)
2367 // Set the opacity of an element (including old IE support).
2368 // `opacity` must be a number from `0` to `1`.
2369 function setOpacity(el, value) {
2370         if ('opacity' in el.style) {
2371                 el.style.opacity = value;
2372         } else if ('filter' in el.style) {
2373                 _setOpacityIE(el, value);
2374         }
2375 }
2376
2377 function _setOpacityIE(el, value) {
2378         var filter = false,
2379             filterName = 'DXImageTransform.Microsoft.Alpha';
2380
2381         // filters collection throws an error if we try to retrieve a filter that doesn't exist
2382         try {
2383                 filter = el.filters.item(filterName);
2384         } catch (e) {
2385                 // don't set opacity to 1 if we haven't already set an opacity,
2386                 // it isn't needed and breaks transparent pngs.
2387                 if (value === 1) { return; }
2388         }
2389
2390         value = Math.round(value * 100);
2391
2392         if (filter) {
2393                 filter.Enabled = (value !== 100);
2394                 filter.Opacity = value;
2395         } else {
2396                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2397         }
2398 }
2399
2400 // @function testProp(props: String[]): String|false
2401 // Goes through the array of style names and returns the first name
2402 // that is a valid style name for an element. If no such name is found,
2403 // it returns false. Useful for vendor-prefixed styles like `transform`.
2404 function testProp(props) {
2405         var style = document.documentElement.style;
2406
2407         for (var i = 0; i < props.length; i++) {
2408                 if (props[i] in style) {
2409                         return props[i];
2410                 }
2411         }
2412         return false;
2413 }
2414
2415 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2416 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2417 // and optionally scaled by `scale`. Does not have an effect if the
2418 // browser doesn't support 3D CSS transforms.
2419 function setTransform(el, offset, scale) {
2420         var pos = offset || new Point(0, 0);
2421
2422         el.style[TRANSFORM] =
2423                 (ie3d ?
2424                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2425                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2426                 (scale ? ' scale(' + scale + ')' : '');
2427 }
2428
2429 // @function setPosition(el: HTMLElement, position: Point)
2430 // Sets the position of `el` to coordinates specified by `position`,
2431 // using CSS translate or top/left positioning depending on the browser
2432 // (used by Leaflet internally to position its layers).
2433 function setPosition(el, point) {
2434
2435         /*eslint-disable */
2436         el._leaflet_pos = point;
2437         /* eslint-enable */
2438
2439         if (any3d) {
2440                 setTransform(el, point);
2441         } else {
2442                 el.style.left = point.x + 'px';
2443                 el.style.top = point.y + 'px';
2444         }
2445 }
2446
2447 // @function getPosition(el: HTMLElement): Point
2448 // Returns the coordinates of an element previously positioned with setPosition.
2449 function getPosition(el) {
2450         // this method is only used for elements previously positioned using setPosition,
2451         // so it's safe to cache the position for performance
2452
2453         return el._leaflet_pos || new Point(0, 0);
2454 }
2455
2456 // @function disableTextSelection()
2457 // Prevents the user from generating `selectstart` DOM events, usually generated
2458 // when the user drags the mouse through a page with text. Used internally
2459 // by Leaflet to override the behaviour of any click-and-drag interaction on
2460 // the map. Affects drag interactions on the whole document.
2461
2462 // @function enableTextSelection()
2463 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2464 var disableTextSelection;
2465 var enableTextSelection;
2466 var _userSelect;
2467 if ('onselectstart' in document) {
2468         disableTextSelection = function () {
2469                 on(window, 'selectstart', preventDefault);
2470         };
2471         enableTextSelection = function () {
2472                 off(window, 'selectstart', preventDefault);
2473         };
2474 } else {
2475         var userSelectProperty = testProp(
2476                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2477
2478         disableTextSelection = function () {
2479                 if (userSelectProperty) {
2480                         var style = document.documentElement.style;
2481                         _userSelect = style[userSelectProperty];
2482                         style[userSelectProperty] = 'none';
2483                 }
2484         };
2485         enableTextSelection = function () {
2486                 if (userSelectProperty) {
2487                         document.documentElement.style[userSelectProperty] = _userSelect;
2488                         _userSelect = undefined;
2489                 }
2490         };
2491 }
2492
2493 // @function disableImageDrag()
2494 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2495 // for `dragstart` DOM events, usually generated when the user drags an image.
2496 function disableImageDrag() {
2497         on(window, 'dragstart', preventDefault);
2498 }
2499
2500 // @function enableImageDrag()
2501 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2502 function enableImageDrag() {
2503         off(window, 'dragstart', preventDefault);
2504 }
2505
2506 var _outlineElement;
2507 var _outlineStyle;
2508 // @function preventOutline(el: HTMLElement)
2509 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2510 // of the element `el` invisible. Used internally by Leaflet to prevent
2511 // focusable elements from displaying an outline when the user performs a
2512 // drag interaction on them.
2513 function preventOutline(element) {
2514         while (element.tabIndex === -1) {
2515                 element = element.parentNode;
2516         }
2517         if (!element.style) { return; }
2518         restoreOutline();
2519         _outlineElement = element;
2520         _outlineStyle = element.style.outline;
2521         element.style.outline = 'none';
2522         on(window, 'keydown', restoreOutline);
2523 }
2524
2525 // @function restoreOutline()
2526 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2527 function restoreOutline() {
2528         if (!_outlineElement) { return; }
2529         _outlineElement.style.outline = _outlineStyle;
2530         _outlineElement = undefined;
2531         _outlineStyle = undefined;
2532         off(window, 'keydown', restoreOutline);
2533 }
2534
2535 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2536 // Finds the closest parent node which size (width and height) is not null.
2537 function getSizedParentNode(element) {
2538         do {
2539                 element = element.parentNode;
2540         } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2541         return element;
2542 }
2543
2544 // @function getScale(el: HTMLElement): Object
2545 // Computes the CSS scale currently applied on the element.
2546 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2547 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2548 function getScale(element) {
2549         var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2550
2551         return {
2552                 x: rect.width / element.offsetWidth || 1,
2553                 y: rect.height / element.offsetHeight || 1,
2554                 boundingClientRect: rect
2555         };
2556 }
2557
2558
2559 var DomUtil = (Object.freeze || Object)({
2560         TRANSFORM: TRANSFORM,
2561         TRANSITION: TRANSITION,
2562         TRANSITION_END: TRANSITION_END,
2563         get: get,
2564         getStyle: getStyle,
2565         create: create$1,
2566         remove: remove,
2567         empty: empty,
2568         toFront: toFront,
2569         toBack: toBack,
2570         hasClass: hasClass,
2571         addClass: addClass,
2572         removeClass: removeClass,
2573         setClass: setClass,
2574         getClass: getClass,
2575         setOpacity: setOpacity,
2576         testProp: testProp,
2577         setTransform: setTransform,
2578         setPosition: setPosition,
2579         getPosition: getPosition,
2580         disableTextSelection: disableTextSelection,
2581         enableTextSelection: enableTextSelection,
2582         disableImageDrag: disableImageDrag,
2583         enableImageDrag: enableImageDrag,
2584         preventOutline: preventOutline,
2585         restoreOutline: restoreOutline,
2586         getSizedParentNode: getSizedParentNode,
2587         getScale: getScale
2588 });
2589
2590 /*
2591  * @namespace DomEvent
2592  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2593  */
2594
2595 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2596
2597 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2598 // Adds a listener function (`fn`) to a particular DOM event type of the
2599 // element `el`. You can optionally specify the context of the listener
2600 // (object the `this` keyword will point to). You can also pass several
2601 // space-separated types (e.g. `'click dblclick'`).
2602
2603 // @alternative
2604 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2605 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2606 function on(obj, types, fn, context) {
2607
2608         if (typeof types === 'object') {
2609                 for (var type in types) {
2610                         addOne(obj, type, types[type], fn);
2611                 }
2612         } else {
2613                 types = splitWords(types);
2614
2615                 for (var i = 0, len = types.length; i < len; i++) {
2616                         addOne(obj, types[i], fn, context);
2617                 }
2618         }
2619
2620         return this;
2621 }
2622
2623 var eventsKey = '_leaflet_events';
2624
2625 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2626 // Removes a previously added listener function.
2627 // Note that if you passed a custom context to on, you must pass the same
2628 // context to `off` in order to remove the listener.
2629
2630 // @alternative
2631 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2632 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2633 function off(obj, types, fn, context) {
2634
2635         if (typeof types === 'object') {
2636                 for (var type in types) {
2637                         removeOne(obj, type, types[type], fn);
2638                 }
2639         } else if (types) {
2640                 types = splitWords(types);
2641
2642                 for (var i = 0, len = types.length; i < len; i++) {
2643                         removeOne(obj, types[i], fn, context);
2644                 }
2645         } else {
2646                 for (var j in obj[eventsKey]) {
2647                         removeOne(obj, j, obj[eventsKey][j]);
2648                 }
2649                 delete obj[eventsKey];
2650         }
2651
2652         return this;
2653 }
2654
2655 function addOne(obj, type, fn, context) {
2656         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2657
2658         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2659
2660         var handler = function (e) {
2661                 return fn.call(context || obj, e || window.event);
2662         };
2663
2664         var originalHandler = handler;
2665
2666         if (pointer && type.indexOf('touch') === 0) {
2667                 // Needs DomEvent.Pointer.js
2668                 addPointerListener(obj, type, handler, id);
2669
2670         } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2671                    !(pointer && chrome)) {
2672                 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2673                 // See #5180
2674                 addDoubleTapListener(obj, handler, id);
2675
2676         } else if ('addEventListener' in obj) {
2677
2678                 if (type === 'mousewheel') {
2679                         obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2680
2681                 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2682                         handler = function (e) {
2683                                 e = e || window.event;
2684                                 if (isExternalTarget(obj, e)) {
2685                                         originalHandler(e);
2686                                 }
2687                         };
2688                         obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2689
2690                 } else {
2691                         if (type === 'click' && android) {
2692                                 handler = function (e) {
2693                                         filterClick(e, originalHandler);
2694                                 };
2695                         }
2696                         obj.addEventListener(type, handler, false);
2697                 }
2698
2699         } else if ('attachEvent' in obj) {
2700                 obj.attachEvent('on' + type, handler);
2701         }
2702
2703         obj[eventsKey] = obj[eventsKey] || {};
2704         obj[eventsKey][id] = handler;
2705 }
2706
2707 function removeOne(obj, type, fn, context) {
2708
2709         var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2710             handler = obj[eventsKey] && obj[eventsKey][id];
2711
2712         if (!handler) { return this; }
2713
2714         if (pointer && type.indexOf('touch') === 0) {
2715                 removePointerListener(obj, type, id);
2716
2717         } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2718                    !(pointer && chrome)) {
2719                 removeDoubleTapListener(obj, id);
2720
2721         } else if ('removeEventListener' in obj) {
2722
2723                 if (type === 'mousewheel') {
2724                         obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2725
2726                 } else {
2727                         obj.removeEventListener(
2728                                 type === 'mouseenter' ? 'mouseover' :
2729                                 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2730                 }
2731
2732         } else if ('detachEvent' in obj) {
2733                 obj.detachEvent('on' + type, handler);
2734         }
2735
2736         obj[eventsKey][id] = null;
2737 }
2738
2739 // @function stopPropagation(ev: DOMEvent): this
2740 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2741 // ```js
2742 // L.DomEvent.on(div, 'click', function (ev) {
2743 //      L.DomEvent.stopPropagation(ev);
2744 // });
2745 // ```
2746 function stopPropagation(e) {
2747
2748         if (e.stopPropagation) {
2749                 e.stopPropagation();
2750         } else if (e.originalEvent) {  // In case of Leaflet event.
2751                 e.originalEvent._stopped = true;
2752         } else {
2753                 e.cancelBubble = true;
2754         }
2755         skipped(e);
2756
2757         return this;
2758 }
2759
2760 // @function disableScrollPropagation(el: HTMLElement): this
2761 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2762 function disableScrollPropagation(el) {
2763         addOne(el, 'mousewheel', stopPropagation);
2764         return this;
2765 }
2766
2767 // @function disableClickPropagation(el: HTMLElement): this
2768 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2769 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2770 function disableClickPropagation(el) {
2771         on(el, 'mousedown touchstart dblclick', stopPropagation);
2772         addOne(el, 'click', fakeStop);
2773         return this;
2774 }
2775
2776 // @function preventDefault(ev: DOMEvent): this
2777 // Prevents the default action of the DOM Event `ev` from happening (such as
2778 // following a link in the href of the a element, or doing a POST request
2779 // with page reload when a `<form>` is submitted).
2780 // Use it inside listener functions.
2781 function preventDefault(e) {
2782         if (e.preventDefault) {
2783                 e.preventDefault();
2784         } else {
2785                 e.returnValue = false;
2786         }
2787         return this;
2788 }
2789
2790 // @function stop(ev: DOMEvent): this
2791 // Does `stopPropagation` and `preventDefault` at the same time.
2792 function stop(e) {
2793         preventDefault(e);
2794         stopPropagation(e);
2795         return this;
2796 }
2797
2798 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2799 // Gets normalized mouse position from a DOM event relative to the
2800 // `container` (border excluded) or to the whole page if not specified.
2801 function getMousePosition(e, container) {
2802         if (!container) {
2803                 return new Point(e.clientX, e.clientY);
2804         }
2805
2806         var scale = getScale(container),
2807             offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)
2808
2809         return new Point(
2810                 // offset.left/top values are in page scale (like clientX/Y),
2811                 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2812                 (e.clientX - offset.left) / scale.x - container.clientLeft,
2813                 (e.clientY - offset.top) / scale.y - container.clientTop
2814         );
2815 }
2816
2817 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2818 // and Firefox scrolls device pixels, not CSS pixels
2819 var wheelPxFactor =
2820         (win && chrome) ? 2 * window.devicePixelRatio :
2821         gecko ? window.devicePixelRatio : 1;
2822
2823 // @function getWheelDelta(ev: DOMEvent): Number
2824 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2825 // pixels scrolled (negative if scrolling down).
2826 // Events from pointing devices without precise scrolling are mapped to
2827 // a best guess of 60 pixels.
2828 function getWheelDelta(e) {
2829         return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2830                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2831                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2832                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2833                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
2834                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2835                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2836                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2837                0;
2838 }
2839
2840 var skipEvents = {};
2841
2842 function fakeStop(e) {
2843         // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2844         skipEvents[e.type] = true;
2845 }
2846
2847 function skipped(e) {
2848         var events = skipEvents[e.type];
2849         // reset when checking, as it's only used in map container and propagates outside of the map
2850         skipEvents[e.type] = false;
2851         return events;
2852 }
2853
2854 // check if element really left/entered the event target (for mouseenter/mouseleave)
2855 function isExternalTarget(el, e) {
2856
2857         var related = e.relatedTarget;
2858
2859         if (!related) { return true; }
2860
2861         try {
2862                 while (related && (related !== el)) {
2863                         related = related.parentNode;
2864                 }
2865         } catch (err) {
2866                 return false;
2867         }
2868         return (related !== el);
2869 }
2870
2871 var lastClick;
2872
2873 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2874 function filterClick(e, handler) {
2875         var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2876             elapsed = lastClick && (timeStamp - lastClick);
2877
2878         // are they closer together than 500ms yet more than 100ms?
2879         // Android typically triggers them ~300ms apart while multiple listeners
2880         // on the same event should be triggered far faster;
2881         // or check if click is simulated on the element, and if it is, reject any non-simulated events
2882
2883         if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2884                 stop(e);
2885                 return;
2886         }
2887         lastClick = timeStamp;
2888
2889         handler(e);
2890 }
2891
2892
2893
2894
2895 var DomEvent = (Object.freeze || Object)({
2896         on: on,
2897         off: off,
2898         stopPropagation: stopPropagation,
2899         disableScrollPropagation: disableScrollPropagation,
2900         disableClickPropagation: disableClickPropagation,
2901         preventDefault: preventDefault,
2902         stop: stop,
2903         getMousePosition: getMousePosition,
2904         getWheelDelta: getWheelDelta,
2905         fakeStop: fakeStop,
2906         skipped: skipped,
2907         isExternalTarget: isExternalTarget,
2908         addListener: on,
2909         removeListener: off
2910 });
2911
2912 /*
2913  * @class PosAnimation
2914  * @aka L.PosAnimation
2915  * @inherits Evented
2916  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2917  *
2918  * @example
2919  * ```js
2920  * var fx = new L.PosAnimation();
2921  * fx.run(el, [300, 500], 0.5);
2922  * ```
2923  *
2924  * @constructor L.PosAnimation()
2925  * Creates a `PosAnimation` object.
2926  *
2927  */
2928
2929 var PosAnimation = Evented.extend({
2930
2931         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2932         // Run an animation of a given element to a new position, optionally setting
2933         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2934         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2935         // `0.5` by default).
2936         run: function (el, newPos, duration, easeLinearity) {
2937                 this.stop();
2938
2939                 this._el = el;
2940                 this._inProgress = true;
2941                 this._duration = duration || 0.25;
2942                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2943
2944                 this._startPos = getPosition(el);
2945                 this._offset = newPos.subtract(this._startPos);
2946                 this._startTime = +new Date();
2947
2948                 // @event start: Event
2949                 // Fired when the animation starts
2950                 this.fire('start');
2951
2952                 this._animate();
2953         },
2954
2955         // @method stop()
2956         // Stops the animation (if currently running).
2957         stop: function () {
2958                 if (!this._inProgress) { return; }
2959
2960                 this._step(true);
2961                 this._complete();
2962         },
2963
2964         _animate: function () {
2965                 // animation loop
2966                 this._animId = requestAnimFrame(this._animate, this);
2967                 this._step();
2968         },
2969
2970         _step: function (round) {
2971                 var elapsed = (+new Date()) - this._startTime,
2972                     duration = this._duration * 1000;
2973
2974                 if (elapsed < duration) {
2975                         this._runFrame(this._easeOut(elapsed / duration), round);
2976                 } else {
2977                         this._runFrame(1);
2978                         this._complete();
2979                 }
2980         },
2981
2982         _runFrame: function (progress, round) {
2983                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2984                 if (round) {
2985                         pos._round();
2986                 }
2987                 setPosition(this._el, pos);
2988
2989                 // @event step: Event
2990                 // Fired continuously during the animation.
2991                 this.fire('step');
2992         },
2993
2994         _complete: function () {
2995                 cancelAnimFrame(this._animId);
2996
2997                 this._inProgress = false;
2998                 // @event end: Event
2999                 // Fired when the animation ends.
3000                 this.fire('end');
3001         },
3002
3003         _easeOut: function (t) {
3004                 return 1 - Math.pow(1 - t, this._easeOutPower);
3005         }
3006 });
3007
3008 /*
3009  * @class Map
3010  * @aka L.Map
3011  * @inherits Evented
3012  *
3013  * The central class of the API — it is used to create a map on a page and manipulate it.
3014  *
3015  * @example
3016  *
3017  * ```js
3018  * // initialize the map on the "map" div with a given center and zoom
3019  * var map = L.map('map', {
3020  *      center: [51.505, -0.09],
3021  *      zoom: 13
3022  * });
3023  * ```
3024  *
3025  */
3026
3027 var Map = Evented.extend({
3028
3029         options: {
3030                 // @section Map State Options
3031                 // @option crs: CRS = L.CRS.EPSG3857
3032                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3033                 // sure what it means.
3034                 crs: EPSG3857,
3035
3036                 // @option center: LatLng = undefined
3037                 // Initial geographic center of the map
3038                 center: undefined,
3039
3040                 // @option zoom: Number = undefined
3041                 // Initial map zoom level
3042                 zoom: undefined,
3043
3044                 // @option minZoom: Number = *
3045                 // Minimum zoom level of the map.
3046                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3047                 // the lowest of their `minZoom` options will be used instead.
3048                 minZoom: undefined,
3049
3050                 // @option maxZoom: Number = *
3051                 // Maximum zoom level of the map.
3052                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3053                 // the highest of their `maxZoom` options will be used instead.
3054                 maxZoom: undefined,
3055
3056                 // @option layers: Layer[] = []
3057                 // Array of layers that will be added to the map initially
3058                 layers: [],
3059
3060                 // @option maxBounds: LatLngBounds = null
3061                 // When this option is set, the map restricts the view to the given
3062                 // geographical bounds, bouncing the user back if the user tries to pan
3063                 // outside the view. To set the restriction dynamically, use
3064                 // [`setMaxBounds`](#map-setmaxbounds) method.
3065                 maxBounds: undefined,
3066
3067                 // @option renderer: Renderer = *
3068                 // The default method for drawing vector layers on the map. `L.SVG`
3069                 // or `L.Canvas` by default depending on browser support.
3070                 renderer: undefined,
3071
3072
3073                 // @section Animation Options
3074                 // @option zoomAnimation: Boolean = true
3075                 // Whether the map zoom animation is enabled. By default it's enabled
3076                 // in all browsers that support CSS3 Transitions except Android.
3077                 zoomAnimation: true,
3078
3079                 // @option zoomAnimationThreshold: Number = 4
3080                 // Won't animate zoom if the zoom difference exceeds this value.
3081                 zoomAnimationThreshold: 4,
3082
3083                 // @option fadeAnimation: Boolean = true
3084                 // Whether the tile fade animation is enabled. By default it's enabled
3085                 // in all browsers that support CSS3 Transitions except Android.
3086                 fadeAnimation: true,
3087
3088                 // @option markerZoomAnimation: Boolean = true
3089                 // Whether markers animate their zoom with the zoom animation, if disabled
3090                 // they will disappear for the length of the animation. By default it's
3091                 // enabled in all browsers that support CSS3 Transitions except Android.
3092                 markerZoomAnimation: true,
3093
3094                 // @option transform3DLimit: Number = 2^23
3095                 // Defines the maximum size of a CSS translation transform. The default
3096                 // value should not be changed unless a web browser positions layers in
3097                 // the wrong place after doing a large `panBy`.
3098                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3099
3100                 // @section Interaction Options
3101                 // @option zoomSnap: Number = 1
3102                 // Forces the map's zoom level to always be a multiple of this, particularly
3103                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3104                 // By default, the zoom level snaps to the nearest integer; lower values
3105                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3106                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3107                 zoomSnap: 1,
3108
3109                 // @option zoomDelta: Number = 1
3110                 // Controls how much the map's zoom level will change after a
3111                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3112                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3113                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3114                 zoomDelta: 1,
3115
3116                 // @option trackResize: Boolean = true
3117                 // Whether the map automatically handles browser window resize to update itself.
3118                 trackResize: true
3119         },
3120
3121         initialize: function (id, options) { // (HTMLElement or String, Object)
3122                 options = setOptions(this, options);
3123
3124                 // Make sure to assign internal flags at the beginning,
3125                 // to avoid inconsistent state in some edge cases.
3126                 this._handlers = [];
3127                 this._layers = {};
3128                 this._zoomBoundLayers = {};
3129                 this._sizeChanged = true;
3130
3131                 this._initContainer(id);
3132                 this._initLayout();
3133
3134                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3135                 this._onResize = bind(this._onResize, this);
3136
3137                 this._initEvents();
3138
3139                 if (options.maxBounds) {
3140                         this.setMaxBounds(options.maxBounds);
3141                 }
3142
3143                 if (options.zoom !== undefined) {
3144                         this._zoom = this._limitZoom(options.zoom);
3145                 }
3146
3147                 if (options.center && options.zoom !== undefined) {
3148                         this.setView(toLatLng(options.center), options.zoom, {reset: true});
3149                 }
3150
3151                 this.callInitHooks();
3152
3153                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3154                 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3155                                 this.options.zoomAnimation;
3156
3157                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3158                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3159                 if (this._zoomAnimated) {
3160                         this._createAnimProxy();
3161                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3162                 }
3163
3164                 this._addLayers(this.options.layers);
3165         },
3166
3167
3168         // @section Methods for modifying map state
3169
3170         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3171         // Sets the view of the map (geographical center and zoom) with the given
3172         // animation options.
3173         setView: function (center, zoom, options) {
3174
3175                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3176                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3177                 options = options || {};
3178
3179                 this._stop();
3180
3181                 if (this._loaded && !options.reset && options !== true) {
3182
3183                         if (options.animate !== undefined) {
3184                                 options.zoom = extend({animate: options.animate}, options.zoom);
3185                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3186                         }
3187
3188                         // try animating pan or zoom
3189                         var moved = (this._zoom !== zoom) ?
3190                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3191                                 this._tryAnimatedPan(center, options.pan);
3192
3193                         if (moved) {
3194                                 // prevent resize handler call, the view will refresh after animation anyway
3195                                 clearTimeout(this._sizeTimer);
3196                                 return this;
3197                         }
3198                 }
3199
3200                 // animation didn't start, just reset the map view
3201                 this._resetView(center, zoom);
3202
3203                 return this;
3204         },
3205
3206         // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3207         // Sets the zoom of the map.
3208         setZoom: function (zoom, options) {
3209                 if (!this._loaded) {
3210                         this._zoom = zoom;
3211                         return this;
3212                 }
3213                 return this.setView(this.getCenter(), zoom, {zoom: options});
3214         },
3215
3216         // @method zoomIn(delta?: Number, options?: Zoom options): this
3217         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3218         zoomIn: function (delta, options) {
3219                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3220                 return this.setZoom(this._zoom + delta, options);
3221         },
3222
3223         // @method zoomOut(delta?: Number, options?: Zoom options): this
3224         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3225         zoomOut: function (delta, options) {
3226                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3227                 return this.setZoom(this._zoom - delta, options);
3228         },
3229
3230         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3231         // Zooms the map while keeping a specified geographical point on the map
3232         // stationary (e.g. used internally for scroll zoom and double-click zoom).
3233         // @alternative
3234         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3235         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3236         setZoomAround: function (latlng, zoom, options) {
3237                 var scale = this.getZoomScale(zoom),
3238                     viewHalf = this.getSize().divideBy(2),
3239                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3240
3241                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3242                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3243
3244                 return this.setView(newCenter, zoom, {zoom: options});
3245         },
3246
3247         _getBoundsCenterZoom: function (bounds, options) {
3248
3249                 options = options || {};
3250                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3251
3252                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3253                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3254
3255                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3256
3257                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3258
3259                 if (zoom === Infinity) {
3260                         return {
3261                                 center: bounds.getCenter(),
3262                                 zoom: zoom
3263                         };
3264                 }
3265
3266                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3267
3268                     swPoint = this.project(bounds.getSouthWest(), zoom),
3269                     nePoint = this.project(bounds.getNorthEast(), zoom),
3270                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3271
3272                 return {
3273                         center: center,
3274                         zoom: zoom
3275                 };
3276         },
3277
3278         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3279         // Sets a map view that contains the given geographical bounds with the
3280         // maximum zoom level possible.
3281         fitBounds: function (bounds, options) {
3282
3283                 bounds = toLatLngBounds(bounds);
3284
3285                 if (!bounds.isValid()) {
3286                         throw new Error('Bounds are not valid.');
3287                 }
3288
3289                 var target = this._getBoundsCenterZoom(bounds, options);
3290                 return this.setView(target.center, target.zoom, options);
3291         },
3292
3293         // @method fitWorld(options?: fitBounds options): this
3294         // Sets a map view that mostly contains the whole world with the maximum
3295         // zoom level possible.
3296         fitWorld: function (options) {
3297                 return this.fitBounds([[-90, -180], [90, 180]], options);
3298         },
3299
3300         // @method panTo(latlng: LatLng, options?: Pan options): this
3301         // Pans the map to a given center.
3302         panTo: function (center, options) { // (LatLng)
3303                 return this.setView(center, this._zoom, {pan: options});
3304         },
3305
3306         // @method panBy(offset: Point, options?: Pan options): this
3307         // Pans the map by a given number of pixels (animated).
3308         panBy: function (offset, options) {
3309                 offset = toPoint(offset).round();
3310                 options = options || {};
3311
3312                 if (!offset.x && !offset.y) {
3313                         return this.fire('moveend');
3314                 }
3315                 // If we pan too far, Chrome gets issues with tiles
3316                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3317                 if (options.animate !== true && !this.getSize().contains(offset)) {
3318                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3319                         return this;
3320                 }
3321
3322                 if (!this._panAnim) {
3323                         this._panAnim = new PosAnimation();
3324
3325                         this._panAnim.on({
3326                                 'step': this._onPanTransitionStep,
3327                                 'end': this._onPanTransitionEnd
3328                         }, this);
3329                 }
3330
3331                 // don't fire movestart if animating inertia
3332                 if (!options.noMoveStart) {
3333                         this.fire('movestart');
3334                 }
3335
3336                 // animate pan unless animate: false specified
3337                 if (options.animate !== false) {
3338                         addClass(this._mapPane, 'leaflet-pan-anim');
3339
3340                         var newPos = this._getMapPanePos().subtract(offset).round();
3341                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3342                 } else {
3343                         this._rawPanBy(offset);
3344                         this.fire('move').fire('moveend');
3345                 }
3346
3347                 return this;
3348         },
3349
3350         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3351         // Sets the view of the map (geographical center and zoom) performing a smooth
3352         // pan-zoom animation.
3353         flyTo: function (targetCenter, targetZoom, options) {
3354
3355                 options = options || {};
3356                 if (options.animate === false || !any3d) {
3357                         return this.setView(targetCenter, targetZoom, options);
3358                 }
3359
3360                 this._stop();
3361
3362                 var from = this.project(this.getCenter()),
3363                     to = this.project(targetCenter),
3364                     size = this.getSize(),
3365                     startZoom = this._zoom;
3366
3367                 targetCenter = toLatLng(targetCenter);
3368                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3369
3370                 var w0 = Math.max(size.x, size.y),
3371                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3372                     u1 = (to.distanceTo(from)) || 1,
3373                     rho = 1.42,
3374                     rho2 = rho * rho;
3375
3376                 function r(i) {
3377                         var s1 = i ? -1 : 1,
3378                             s2 = i ? w1 : w0,
3379                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3380                             b1 = 2 * s2 * rho2 * u1,
3381                             b = t1 / b1,
3382                             sq = Math.sqrt(b * b + 1) - b;
3383
3384                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
3385                             // thus triggering an infinite loop in flyTo
3386                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
3387
3388                         return log;
3389                 }
3390
3391                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3392                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3393                 function tanh(n) { return sinh(n) / cosh(n); }
3394
3395                 var r0 = r(0);
3396
3397                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3398                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3399
3400                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3401
3402                 var start = Date.now(),
3403                     S = (r(1) - r0) / rho,
3404                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3405
3406                 function frame() {
3407                         var t = (Date.now() - start) / duration,
3408                             s = easeOut(t) * S;
3409
3410                         if (t <= 1) {
3411                                 this._flyToFrame = requestAnimFrame(frame, this);
3412
3413                                 this._move(
3414                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3415                                         this.getScaleZoom(w0 / w(s), startZoom),
3416                                         {flyTo: true});
3417
3418                         } else {
3419                                 this
3420                                         ._move(targetCenter, targetZoom)
3421                                         ._moveEnd(true);
3422                         }
3423                 }
3424
3425                 this._moveStart(true, options.noMoveStart);
3426
3427                 frame.call(this);
3428                 return this;
3429         },
3430
3431         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3432         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3433         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3434         flyToBounds: function (bounds, options) {
3435                 var target = this._getBoundsCenterZoom(bounds, options);
3436                 return this.flyTo(target.center, target.zoom, options);
3437         },
3438
3439         // @method setMaxBounds(bounds: Bounds): this
3440         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3441         setMaxBounds: function (bounds) {
3442                 bounds = toLatLngBounds(bounds);
3443
3444                 if (!bounds.isValid()) {
3445                         this.options.maxBounds = null;
3446                         return this.off('moveend', this._panInsideMaxBounds);
3447                 } else if (this.options.maxBounds) {
3448                         this.off('moveend', this._panInsideMaxBounds);
3449                 }
3450
3451                 this.options.maxBounds = bounds;
3452
3453                 if (this._loaded) {
3454                         this._panInsideMaxBounds();
3455                 }
3456
3457                 return this.on('moveend', this._panInsideMaxBounds);
3458         },
3459
3460         // @method setMinZoom(zoom: Number): this
3461         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3462         setMinZoom: function (zoom) {
3463                 var oldZoom = this.options.minZoom;
3464                 this.options.minZoom = zoom;
3465
3466                 if (this._loaded && oldZoom !== zoom) {
3467                         this.fire('zoomlevelschange');
3468
3469                         if (this.getZoom() < this.options.minZoom) {
3470                                 return this.setZoom(zoom);
3471                         }
3472                 }
3473
3474                 return this;
3475         },
3476
3477         // @method setMaxZoom(zoom: Number): this
3478         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3479         setMaxZoom: function (zoom) {
3480                 var oldZoom = this.options.maxZoom;
3481                 this.options.maxZoom = zoom;
3482
3483                 if (this._loaded && oldZoom !== zoom) {
3484                         this.fire('zoomlevelschange');
3485
3486                         if (this.getZoom() > this.options.maxZoom) {
3487                                 return this.setZoom(zoom);
3488                         }
3489                 }
3490
3491                 return this;
3492         },
3493
3494         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3495         // 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.
3496         panInsideBounds: function (bounds, options) {
3497                 this._enforcingBounds = true;
3498                 var center = this.getCenter(),
3499                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3500
3501                 if (!center.equals(newCenter)) {
3502                         this.panTo(newCenter, options);
3503                 }
3504
3505                 this._enforcingBounds = false;
3506                 return this;
3507         },
3508
3509         // @method panInside(latlng: LatLng, options?: options): this
3510         // Pans the map the minimum amount to make the `latlng` visible. Use
3511         // `padding`, `paddingTopLeft` and `paddingTopRight` options to fit
3512         // the display to more restricted bounds, like [`fitBounds`](#map-fitbounds).
3513         // If `latlng` is already within the (optionally padded) display bounds,
3514         // the map will not be panned.
3515         panInside: function (latlng, options) {
3516                 options = options || {};
3517
3518                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3519                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3520                     center = this.getCenter(),
3521                     pixelCenter = this.project(center),
3522                     pixelPoint = this.project(latlng),
3523                     pixelBounds = this.getPixelBounds(),
3524                     halfPixelBounds = pixelBounds.getSize().divideBy(2),
3525                     paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]);
3526
3527                 if (!paddedBounds.contains(pixelPoint)) {
3528                         this._enforcingBounds = true;
3529                         var diff = pixelCenter.subtract(pixelPoint),
3530                             newCenter = toPoint(pixelPoint.x + diff.x, pixelPoint.y + diff.y);
3531
3532                         if (pixelPoint.x < paddedBounds.min.x || pixelPoint.x > paddedBounds.max.x) {
3533                                 newCenter.x = pixelCenter.x - diff.x;
3534                                 if (diff.x > 0) {
3535                                         newCenter.x += halfPixelBounds.x - paddingTL.x;
3536                                 } else {
3537                                         newCenter.x -= halfPixelBounds.x - paddingBR.x;
3538                                 }
3539                         }
3540                         if (pixelPoint.y < paddedBounds.min.y || pixelPoint.y > paddedBounds.max.y) {
3541                                 newCenter.y = pixelCenter.y - diff.y;
3542                                 if (diff.y > 0) {
3543                                         newCenter.y += halfPixelBounds.y - paddingTL.y;
3544                                 } else {
3545                                         newCenter.y -= halfPixelBounds.y - paddingBR.y;
3546                                 }
3547                         }
3548                         this.panTo(this.unproject(newCenter), options);
3549                         this._enforcingBounds = false;
3550                 }
3551                 return this;
3552         },
3553
3554         // @method invalidateSize(options: Zoom/pan options): this
3555         // Checks if the map container size changed and updates the map if so —
3556         // call it after you've changed the map size dynamically, also animating
3557         // pan by default. If `options.pan` is `false`, panning will not occur.
3558         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3559         // that it doesn't happen often even if the method is called many
3560         // times in a row.
3561
3562         // @alternative
3563         // @method invalidateSize(animate: Boolean): this
3564         // Checks if the map container size changed and updates the map if so —
3565         // call it after you've changed the map size dynamically, also animating
3566         // pan by default.
3567         invalidateSize: function (options) {
3568                 if (!this._loaded) { return this; }
3569
3570                 options = extend({
3571                         animate: false,
3572                         pan: true
3573                 }, options === true ? {animate: true} : options);
3574
3575                 var oldSize = this.getSize();
3576                 this._sizeChanged = true;
3577                 this._lastCenter = null;
3578
3579                 var newSize = this.getSize(),
3580                     oldCenter = oldSize.divideBy(2).round(),
3581                     newCenter = newSize.divideBy(2).round(),
3582                     offset = oldCenter.subtract(newCenter);
3583
3584                 if (!offset.x && !offset.y) { return this; }
3585
3586                 if (options.animate && options.pan) {
3587                         this.panBy(offset);
3588
3589                 } else {
3590                         if (options.pan) {
3591                                 this._rawPanBy(offset);
3592                         }
3593
3594                         this.fire('move');
3595
3596                         if (options.debounceMoveend) {
3597                                 clearTimeout(this._sizeTimer);
3598                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3599                         } else {
3600                                 this.fire('moveend');
3601                         }
3602                 }
3603
3604                 // @section Map state change events
3605                 // @event resize: ResizeEvent
3606                 // Fired when the map is resized.
3607                 return this.fire('resize', {
3608                         oldSize: oldSize,
3609                         newSize: newSize
3610                 });
3611         },
3612
3613         // @section Methods for modifying map state
3614         // @method stop(): this
3615         // Stops the currently running `panTo` or `flyTo` animation, if any.
3616         stop: function () {
3617                 this.setZoom(this._limitZoom(this._zoom));
3618                 if (!this.options.zoomSnap) {
3619                         this.fire('viewreset');
3620                 }
3621                 return this._stop();
3622         },
3623
3624         // @section Geolocation methods
3625         // @method locate(options?: Locate options): this
3626         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3627         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3628         // and optionally sets the map view to the user's location with respect to
3629         // detection accuracy (or to the world view if geolocation failed).
3630         // Note that, if your page doesn't use HTTPS, this method will fail in
3631         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3632         // See `Locate options` for more details.
3633         locate: function (options) {
3634
3635                 options = this._locateOptions = extend({
3636                         timeout: 10000,
3637                         watch: false
3638                         // setView: false
3639                         // maxZoom: <Number>
3640                         // maximumAge: 0
3641                         // enableHighAccuracy: false
3642                 }, options);
3643
3644                 if (!('geolocation' in navigator)) {
3645                         this._handleGeolocationError({
3646                                 code: 0,
3647                                 message: 'Geolocation not supported.'
3648                         });
3649                         return this;
3650                 }
3651
3652                 var onResponse = bind(this._handleGeolocationResponse, this),
3653                     onError = bind(this._handleGeolocationError, this);
3654
3655                 if (options.watch) {
3656                         this._locationWatchId =
3657                                 navigator.geolocation.watchPosition(onResponse, onError, options);
3658                 } else {
3659                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3660                 }
3661                 return this;
3662         },
3663
3664         // @method stopLocate(): this
3665         // Stops watching location previously initiated by `map.locate({watch: true})`
3666         // and aborts resetting the map view if map.locate was called with
3667         // `{setView: true}`.
3668         stopLocate: function () {
3669                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3670                         navigator.geolocation.clearWatch(this._locationWatchId);
3671                 }
3672                 if (this._locateOptions) {
3673                         this._locateOptions.setView = false;
3674                 }
3675                 return this;
3676         },
3677
3678         _handleGeolocationError: function (error) {
3679                 var c = error.code,
3680                     message = error.message ||
3681                             (c === 1 ? 'permission denied' :
3682                             (c === 2 ? 'position unavailable' : 'timeout'));
3683
3684                 if (this._locateOptions.setView && !this._loaded) {
3685                         this.fitWorld();
3686                 }
3687
3688                 // @section Location events
3689                 // @event locationerror: ErrorEvent
3690                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3691                 this.fire('locationerror', {
3692                         code: c,
3693                         message: 'Geolocation error: ' + message + '.'
3694                 });
3695         },
3696
3697         _handleGeolocationResponse: function (pos) {
3698                 var lat = pos.coords.latitude,
3699                     lng = pos.coords.longitude,
3700                     latlng = new LatLng(lat, lng),
3701                     bounds = latlng.toBounds(pos.coords.accuracy * 2),
3702                     options = this._locateOptions;
3703
3704                 if (options.setView) {
3705                         var zoom = this.getBoundsZoom(bounds);
3706                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3707                 }
3708
3709                 var data = {
3710                         latlng: latlng,
3711                         bounds: bounds,
3712                         timestamp: pos.timestamp
3713                 };
3714
3715                 for (var i in pos.coords) {
3716                         if (typeof pos.coords[i] === 'number') {
3717                                 data[i] = pos.coords[i];
3718                         }
3719                 }
3720
3721                 // @event locationfound: LocationEvent
3722                 // Fired when geolocation (using the [`locate`](#map-locate) method)
3723                 // went successfully.
3724                 this.fire('locationfound', data);
3725         },
3726
3727         // TODO Appropriate docs section?
3728         // @section Other Methods
3729         // @method addHandler(name: String, HandlerClass: Function): this
3730         // Adds a new `Handler` to the map, given its name and constructor function.
3731         addHandler: function (name, HandlerClass) {
3732                 if (!HandlerClass) { return this; }
3733
3734                 var handler = this[name] = new HandlerClass(this);
3735
3736                 this._handlers.push(handler);
3737
3738                 if (this.options[name]) {
3739                         handler.enable();
3740                 }
3741
3742                 return this;
3743         },
3744
3745         // @method remove(): this
3746         // Destroys the map and clears all related event listeners.
3747         remove: function () {
3748
3749                 this._initEvents(true);
3750
3751                 if (this._containerId !== this._container._leaflet_id) {
3752                         throw new Error('Map container is being reused by another instance');
3753                 }
3754
3755                 try {
3756                         // throws error in IE6-8
3757                         delete this._container._leaflet_id;
3758                         delete this._containerId;
3759                 } catch (e) {
3760                         /*eslint-disable */
3761                         this._container._leaflet_id = undefined;
3762                         /* eslint-enable */
3763                         this._containerId = undefined;
3764                 }
3765
3766                 if (this._locationWatchId !== undefined) {
3767                         this.stopLocate();
3768                 }
3769
3770                 this._stop();
3771
3772                 remove(this._mapPane);
3773
3774                 if (this._clearControlPos) {
3775                         this._clearControlPos();
3776                 }
3777                 if (this._resizeRequest) {
3778                         cancelAnimFrame(this._resizeRequest);
3779                         this._resizeRequest = null;
3780                 }
3781
3782                 this._clearHandlers();
3783
3784                 if (this._loaded) {
3785                         // @section Map state change events
3786                         // @event unload: Event
3787                         // Fired when the map is destroyed with [remove](#map-remove) method.
3788                         this.fire('unload');
3789                 }
3790
3791                 var i;
3792                 for (i in this._layers) {
3793                         this._layers[i].remove();
3794                 }
3795                 for (i in this._panes) {
3796                         remove(this._panes[i]);
3797                 }
3798
3799                 this._layers = [];
3800                 this._panes = [];
3801                 delete this._mapPane;
3802                 delete this._renderer;
3803
3804                 return this;
3805         },
3806
3807         // @section Other Methods
3808         // @method createPane(name: String, container?: HTMLElement): HTMLElement
3809         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3810         // then returns it. The pane is created as a child of `container`, or
3811         // as a child of the main map pane if not set.
3812         createPane: function (name, container) {
3813                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3814                     pane = create$1('div', className, container || this._mapPane);
3815
3816                 if (name) {
3817                         this._panes[name] = pane;
3818                 }
3819                 return pane;
3820         },
3821
3822         // @section Methods for Getting Map State
3823
3824         // @method getCenter(): LatLng
3825         // Returns the geographical center of the map view
3826         getCenter: function () {
3827                 this._checkIfLoaded();
3828
3829                 if (this._lastCenter && !this._moved()) {
3830                         return this._lastCenter;
3831                 }
3832                 return this.layerPointToLatLng(this._getCenterLayerPoint());
3833         },
3834
3835         // @method getZoom(): Number
3836         // Returns the current zoom level of the map view
3837         getZoom: function () {
3838                 return this._zoom;
3839         },
3840
3841         // @method getBounds(): LatLngBounds
3842         // Returns the geographical bounds visible in the current map view
3843         getBounds: function () {
3844                 var bounds = this.getPixelBounds(),
3845                     sw = this.unproject(bounds.getBottomLeft()),
3846                     ne = this.unproject(bounds.getTopRight());
3847
3848                 return new LatLngBounds(sw, ne);
3849         },
3850
3851         // @method getMinZoom(): Number
3852         // 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.
3853         getMinZoom: function () {
3854                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3855         },
3856
3857         // @method getMaxZoom(): Number
3858         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3859         getMaxZoom: function () {
3860                 return this.options.maxZoom === undefined ?
3861                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3862                         this.options.maxZoom;
3863         },
3864
3865         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3866         // Returns the maximum zoom level on which the given bounds fit to the map
3867         // view in its entirety. If `inside` (optional) is set to `true`, the method
3868         // instead returns the minimum zoom level on which the map view fits into
3869         // the given bounds in its entirety.
3870         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3871                 bounds = toLatLngBounds(bounds);
3872                 padding = toPoint(padding || [0, 0]);
3873
3874                 var zoom = this.getZoom() || 0,
3875                     min = this.getMinZoom(),
3876                     max = this.getMaxZoom(),
3877                     nw = bounds.getNorthWest(),
3878                     se = bounds.getSouthEast(),
3879                     size = this.getSize().subtract(padding),
3880                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3881                     snap = any3d ? this.options.zoomSnap : 1,
3882                     scalex = size.x / boundsSize.x,
3883                     scaley = size.y / boundsSize.y,
3884                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3885
3886                 zoom = this.getScaleZoom(scale, zoom);
3887
3888                 if (snap) {
3889                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3890                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3891                 }
3892
3893                 return Math.max(min, Math.min(max, zoom));
3894         },
3895
3896         // @method getSize(): Point
3897         // Returns the current size of the map container (in pixels).
3898         getSize: function () {
3899                 if (!this._size || this._sizeChanged) {
3900                         this._size = new Point(
3901                                 this._container.clientWidth || 0,
3902                                 this._container.clientHeight || 0);
3903
3904                         this._sizeChanged = false;
3905                 }
3906                 return this._size.clone();
3907         },
3908
3909         // @method getPixelBounds(): Bounds
3910         // Returns the bounds of the current map view in projected pixel
3911         // coordinates (sometimes useful in layer and overlay implementations).
3912         getPixelBounds: function (center, zoom) {
3913                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3914                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3915         },
3916
3917         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3918         // the map pane? "left point of the map layer" can be confusing, specially
3919         // since there can be negative offsets.
3920         // @method getPixelOrigin(): Point
3921         // Returns the projected pixel coordinates of the top left point of
3922         // the map layer (useful in custom layer and overlay implementations).
3923         getPixelOrigin: function () {
3924                 this._checkIfLoaded();
3925                 return this._pixelOrigin;
3926         },
3927
3928         // @method getPixelWorldBounds(zoom?: Number): Bounds
3929         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3930         // If `zoom` is omitted, the map's current zoom level is used.
3931         getPixelWorldBounds: function (zoom) {
3932                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3933         },
3934
3935         // @section Other Methods
3936
3937         // @method getPane(pane: String|HTMLElement): HTMLElement
3938         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3939         getPane: function (pane) {
3940                 return typeof pane === 'string' ? this._panes[pane] : pane;
3941         },
3942
3943         // @method getPanes(): Object
3944         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3945         // the panes as values.
3946         getPanes: function () {
3947                 return this._panes;
3948         },
3949
3950         // @method getContainer: HTMLElement
3951         // Returns the HTML element that contains the map.
3952         getContainer: function () {
3953                 return this._container;
3954         },
3955
3956
3957         // @section Conversion Methods
3958
3959         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3960         // Returns the scale factor to be applied to a map transition from zoom level
3961         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3962         getZoomScale: function (toZoom, fromZoom) {
3963                 // TODO replace with universal implementation after refactoring projections
3964                 var crs = this.options.crs;
3965                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3966                 return crs.scale(toZoom) / crs.scale(fromZoom);
3967         },
3968
3969         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3970         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3971         // level and everything is scaled by a factor of `scale`. Inverse of
3972         // [`getZoomScale`](#map-getZoomScale).
3973         getScaleZoom: function (scale, fromZoom) {
3974                 var crs = this.options.crs;
3975                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3976                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3977                 return isNaN(zoom) ? Infinity : zoom;
3978         },
3979
3980         // @method project(latlng: LatLng, zoom: Number): Point
3981         // Projects a geographical coordinate `LatLng` according to the projection
3982         // of the map's CRS, then scales it according to `zoom` and the CRS's
3983         // `Transformation`. The result is pixel coordinate relative to
3984         // the CRS origin.
3985         project: function (latlng, zoom) {
3986                 zoom = zoom === undefined ? this._zoom : zoom;
3987                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3988         },
3989
3990         // @method unproject(point: Point, zoom: Number): LatLng
3991         // Inverse of [`project`](#map-project).
3992         unproject: function (point, zoom) {
3993                 zoom = zoom === undefined ? this._zoom : zoom;
3994                 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3995         },
3996
3997         // @method layerPointToLatLng(point: Point): LatLng
3998         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3999         // returns the corresponding geographical coordinate (for the current zoom level).
4000         layerPointToLatLng: function (point) {
4001                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
4002                 return this.unproject(projectedPoint);
4003         },
4004
4005         // @method latLngToLayerPoint(latlng: LatLng): Point
4006         // Given a geographical coordinate, returns the corresponding pixel coordinate
4007         // relative to the [origin pixel](#map-getpixelorigin).
4008         latLngToLayerPoint: function (latlng) {
4009                 var projectedPoint = this.project(toLatLng(latlng))._round();
4010                 return projectedPoint._subtract(this.getPixelOrigin());
4011         },
4012
4013         // @method wrapLatLng(latlng: LatLng): LatLng
4014         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
4015         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
4016         // CRS's bounds.
4017         // By default this means longitude is wrapped around the dateline so its
4018         // value is between -180 and +180 degrees.
4019         wrapLatLng: function (latlng) {
4020                 return this.options.crs.wrapLatLng(toLatLng(latlng));
4021         },
4022
4023         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
4024         // Returns a `LatLngBounds` with the same size as the given one, ensuring that
4025         // its center is within the CRS's bounds.
4026         // By default this means the center longitude is wrapped around the dateline so its
4027         // value is between -180 and +180 degrees, and the majority of the bounds
4028         // overlaps the CRS's bounds.
4029         wrapLatLngBounds: function (latlng) {
4030                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
4031         },
4032
4033         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
4034         // Returns the distance between two geographical coordinates according to
4035         // the map's CRS. By default this measures distance in meters.
4036         distance: function (latlng1, latlng2) {
4037                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
4038         },
4039
4040         // @method containerPointToLayerPoint(point: Point): Point
4041         // Given a pixel coordinate relative to the map container, returns the corresponding
4042         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
4043         containerPointToLayerPoint: function (point) { // (Point)
4044                 return toPoint(point).subtract(this._getMapPanePos());
4045         },
4046
4047         // @method layerPointToContainerPoint(point: Point): Point
4048         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
4049         // returns the corresponding pixel coordinate relative to the map container.
4050         layerPointToContainerPoint: function (point) { // (Point)
4051                 return toPoint(point).add(this._getMapPanePos());
4052         },
4053
4054         // @method containerPointToLatLng(point: Point): LatLng
4055         // Given a pixel coordinate relative to the map container, returns
4056         // the corresponding geographical coordinate (for the current zoom level).
4057         containerPointToLatLng: function (point) {
4058                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4059                 return this.layerPointToLatLng(layerPoint);
4060         },
4061
4062         // @method latLngToContainerPoint(latlng: LatLng): Point
4063         // Given a geographical coordinate, returns the corresponding pixel coordinate
4064         // relative to the map container.
4065         latLngToContainerPoint: function (latlng) {
4066                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4067         },
4068
4069         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4070         // Given a MouseEvent object, returns the pixel coordinate relative to the
4071         // map container where the event took place.
4072         mouseEventToContainerPoint: function (e) {
4073                 return getMousePosition(e, this._container);
4074         },
4075
4076         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4077         // Given a MouseEvent object, returns the pixel coordinate relative to
4078         // the [origin pixel](#map-getpixelorigin) where the event took place.
4079         mouseEventToLayerPoint: function (e) {
4080                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4081         },
4082
4083         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4084         // Given a MouseEvent object, returns geographical coordinate where the
4085         // event took place.
4086         mouseEventToLatLng: function (e) { // (MouseEvent)
4087                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4088         },
4089
4090
4091         // map initialization methods
4092
4093         _initContainer: function (id) {
4094                 var container = this._container = get(id);
4095
4096                 if (!container) {
4097                         throw new Error('Map container not found.');
4098                 } else if (container._leaflet_id) {
4099                         throw new Error('Map container is already initialized.');
4100                 }
4101
4102                 on(container, 'scroll', this._onScroll, this);
4103                 this._containerId = stamp(container);
4104         },
4105
4106         _initLayout: function () {
4107                 var container = this._container;
4108
4109                 this._fadeAnimated = this.options.fadeAnimation && any3d;
4110
4111                 addClass(container, 'leaflet-container' +
4112                         (touch ? ' leaflet-touch' : '') +
4113                         (retina ? ' leaflet-retina' : '') +
4114                         (ielt9 ? ' leaflet-oldie' : '') +
4115                         (safari ? ' leaflet-safari' : '') +
4116                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4117
4118                 var position = getStyle(container, 'position');
4119
4120                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4121                         container.style.position = 'relative';
4122                 }
4123
4124                 this._initPanes();
4125
4126                 if (this._initControlPos) {
4127                         this._initControlPos();
4128                 }
4129         },
4130
4131         _initPanes: function () {
4132                 var panes = this._panes = {};
4133                 this._paneRenderers = {};
4134
4135                 // @section
4136                 //
4137                 // Panes are DOM elements used to control the ordering of layers on the map. You
4138                 // can access panes with [`map.getPane`](#map-getpane) or
4139                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4140                 // [`map.createPane`](#map-createpane) method.
4141                 //
4142                 // Every map has the following default panes that differ only in zIndex.
4143                 //
4144                 // @pane mapPane: HTMLElement = 'auto'
4145                 // Pane that contains all other map panes
4146
4147                 this._mapPane = this.createPane('mapPane', this._container);
4148                 setPosition(this._mapPane, new Point(0, 0));
4149
4150                 // @pane tilePane: HTMLElement = 200
4151                 // Pane for `GridLayer`s and `TileLayer`s
4152                 this.createPane('tilePane');
4153                 // @pane overlayPane: HTMLElement = 400
4154                 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4155                 this.createPane('shadowPane');
4156                 // @pane shadowPane: HTMLElement = 500
4157                 // Pane for overlay shadows (e.g. `Marker` shadows)
4158                 this.createPane('overlayPane');
4159                 // @pane markerPane: HTMLElement = 600
4160                 // Pane for `Icon`s of `Marker`s
4161                 this.createPane('markerPane');
4162                 // @pane tooltipPane: HTMLElement = 650
4163                 // Pane for `Tooltip`s.
4164                 this.createPane('tooltipPane');
4165                 // @pane popupPane: HTMLElement = 700
4166                 // Pane for `Popup`s.
4167                 this.createPane('popupPane');
4168
4169                 if (!this.options.markerZoomAnimation) {
4170                         addClass(panes.markerPane, 'leaflet-zoom-hide');
4171                         addClass(panes.shadowPane, 'leaflet-zoom-hide');
4172                 }
4173         },
4174
4175
4176         // private methods that modify map state
4177
4178         // @section Map state change events
4179         _resetView: function (center, zoom) {
4180                 setPosition(this._mapPane, new Point(0, 0));
4181
4182                 var loading = !this._loaded;
4183                 this._loaded = true;
4184                 zoom = this._limitZoom(zoom);
4185
4186                 this.fire('viewprereset');
4187
4188                 var zoomChanged = this._zoom !== zoom;
4189                 this
4190                         ._moveStart(zoomChanged, false)
4191                         ._move(center, zoom)
4192                         ._moveEnd(zoomChanged);
4193
4194                 // @event viewreset: Event
4195                 // Fired when the map needs to redraw its content (this usually happens
4196                 // on map zoom or load). Very useful for creating custom overlays.
4197                 this.fire('viewreset');
4198
4199                 // @event load: Event
4200                 // Fired when the map is initialized (when its center and zoom are set
4201                 // for the first time).
4202                 if (loading) {
4203                         this.fire('load');
4204                 }
4205         },
4206
4207         _moveStart: function (zoomChanged, noMoveStart) {
4208                 // @event zoomstart: Event
4209                 // Fired when the map zoom is about to change (e.g. before zoom animation).
4210                 // @event movestart: Event
4211                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4212                 if (zoomChanged) {
4213                         this.fire('zoomstart');
4214                 }
4215                 if (!noMoveStart) {
4216                         this.fire('movestart');
4217                 }
4218                 return this;
4219         },
4220
4221         _move: function (center, zoom, data) {
4222                 if (zoom === undefined) {
4223                         zoom = this._zoom;
4224                 }
4225                 var zoomChanged = this._zoom !== zoom;
4226
4227                 this._zoom = zoom;
4228                 this._lastCenter = center;
4229                 this._pixelOrigin = this._getNewPixelOrigin(center);
4230
4231                 // @event zoom: Event
4232                 // Fired repeatedly during any change in zoom level, including zoom
4233                 // and fly animations.
4234                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
4235                         this.fire('zoom', data);
4236                 }
4237
4238                 // @event move: Event
4239                 // Fired repeatedly during any movement of the map, including pan and
4240                 // fly animations.
4241                 return this.fire('move', data);
4242         },
4243
4244         _moveEnd: function (zoomChanged) {
4245                 // @event zoomend: Event
4246                 // Fired when the map has changed, after any animations.
4247                 if (zoomChanged) {
4248                         this.fire('zoomend');
4249                 }
4250
4251                 // @event moveend: Event
4252                 // Fired when the center of the map stops changing (e.g. user stopped
4253                 // dragging the map).
4254                 return this.fire('moveend');
4255         },
4256
4257         _stop: function () {
4258                 cancelAnimFrame(this._flyToFrame);
4259                 if (this._panAnim) {
4260                         this._panAnim.stop();
4261                 }
4262                 return this;
4263         },
4264
4265         _rawPanBy: function (offset) {
4266                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4267         },
4268
4269         _getZoomSpan: function () {
4270                 return this.getMaxZoom() - this.getMinZoom();
4271         },
4272
4273         _panInsideMaxBounds: function () {
4274                 if (!this._enforcingBounds) {
4275                         this.panInsideBounds(this.options.maxBounds);
4276                 }
4277         },
4278
4279         _checkIfLoaded: function () {
4280                 if (!this._loaded) {
4281                         throw new Error('Set map center and zoom first.');
4282                 }
4283         },
4284
4285         // DOM event handling
4286
4287         // @section Interaction events
4288         _initEvents: function (remove$$1) {
4289                 this._targets = {};
4290                 this._targets[stamp(this._container)] = this;
4291
4292                 var onOff = remove$$1 ? off : on;
4293
4294                 // @event click: MouseEvent
4295                 // Fired when the user clicks (or taps) the map.
4296                 // @event dblclick: MouseEvent
4297                 // Fired when the user double-clicks (or double-taps) the map.
4298                 // @event mousedown: MouseEvent
4299                 // Fired when the user pushes the mouse button on the map.
4300                 // @event mouseup: MouseEvent
4301                 // Fired when the user releases the mouse button on the map.
4302                 // @event mouseover: MouseEvent
4303                 // Fired when the mouse enters the map.
4304                 // @event mouseout: MouseEvent
4305                 // Fired when the mouse leaves the map.
4306                 // @event mousemove: MouseEvent
4307                 // Fired while the mouse moves over the map.
4308                 // @event contextmenu: MouseEvent
4309                 // Fired when the user pushes the right mouse button on the map, prevents
4310                 // default browser context menu from showing if there are listeners on
4311                 // this event. Also fired on mobile when the user holds a single touch
4312                 // for a second (also called long press).
4313                 // @event keypress: KeyboardEvent
4314                 // Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
4315                 // @event keydown: KeyboardEvent
4316                 // Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
4317                 // the `keydown` event is fired for keys that produce a character value and for keys
4318                 // that do not produce a character value.
4319                 // @event keyup: KeyboardEvent
4320                 // Fired when the user releases a key from the keyboard while the map is focused.
4321                 onOff(this._container, 'click dblclick mousedown mouseup ' +
4322                         'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);
4323
4324                 if (this.options.trackResize) {
4325                         onOff(window, 'resize', this._onResize, this);
4326                 }
4327
4328                 if (any3d && this.options.transform3DLimit) {
4329                         (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4330                 }
4331         },
4332
4333         _onResize: function () {
4334                 cancelAnimFrame(this._resizeRequest);
4335                 this._resizeRequest = requestAnimFrame(
4336                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
4337         },
4338
4339         _onScroll: function () {
4340                 this._container.scrollTop  = 0;
4341                 this._container.scrollLeft = 0;
4342         },
4343
4344         _onMoveEnd: function () {
4345                 var pos = this._getMapPanePos();
4346                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4347                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4348                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4349                         this._resetView(this.getCenter(), this.getZoom());
4350                 }
4351         },
4352
4353         _findEventTargets: function (e, type) {
4354                 var targets = [],
4355                     target,
4356                     isHover = type === 'mouseout' || type === 'mouseover',
4357                     src = e.target || e.srcElement,
4358                     dragging = false;
4359
4360                 while (src) {
4361                         target = this._targets[stamp(src)];
4362                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4363                                 // Prevent firing click after you just dragged an object.
4364                                 dragging = true;
4365                                 break;
4366                         }
4367                         if (target && target.listens(type, true)) {
4368                                 if (isHover && !isExternalTarget(src, e)) { break; }
4369                                 targets.push(target);
4370                                 if (isHover) { break; }
4371                         }
4372                         if (src === this._container) { break; }
4373                         src = src.parentNode;
4374                 }
4375                 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4376                         targets = [this];
4377                 }
4378                 return targets;
4379         },
4380
4381         _handleDOMEvent: function (e) {
4382                 if (!this._loaded || skipped(e)) { return; }
4383
4384                 var type = e.type;
4385
4386                 if (type === 'mousedown' || type === 'keypress' || type === 'keyup' || type === 'keydown') {
4387                         // prevents outline when clicking on keyboard-focusable element
4388                         preventOutline(e.target || e.srcElement);
4389                 }
4390
4391                 this._fireDOMEvent(e, type);
4392         },
4393
4394         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4395
4396         _fireDOMEvent: function (e, type, targets) {
4397
4398                 if (e.type === 'click') {
4399                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4400                         // @event preclick: MouseEvent
4401                         // Fired before mouse click on the map (sometimes useful when you
4402                         // want something to happen on click before any existing click
4403                         // handlers start running).
4404                         var synth = extend({}, e);
4405                         synth.type = 'preclick';
4406                         this._fireDOMEvent(synth, synth.type, targets);
4407                 }
4408
4409                 if (e._stopped) { return; }
4410
4411                 // Find the layer the event is propagating from and its parents.
4412                 targets = (targets || []).concat(this._findEventTargets(e, type));
4413
4414                 if (!targets.length) { return; }
4415
4416                 var target = targets[0];
4417                 if (type === 'contextmenu' && target.listens(type, true)) {
4418                         preventDefault(e);
4419                 }
4420
4421                 var data = {
4422                         originalEvent: e
4423                 };
4424
4425                 if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
4426                         var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4427                         data.containerPoint = isMarker ?
4428                                 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4429                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4430                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4431                 }
4432
4433                 for (var i = 0; i < targets.length; i++) {
4434                         targets[i].fire(type, data, true);
4435                         if (data.originalEvent._stopped ||
4436                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4437                 }
4438         },
4439
4440         _draggableMoved: function (obj) {
4441                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4442                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4443         },
4444
4445         _clearHandlers: function () {
4446                 for (var i = 0, len = this._handlers.length; i < len; i++) {
4447                         this._handlers[i].disable();
4448                 }
4449         },
4450
4451         // @section Other Methods
4452
4453         // @method whenReady(fn: Function, context?: Object): this
4454         // Runs the given function `fn` when the map gets initialized with
4455         // a view (center and zoom) and at least one layer, or immediately
4456         // if it's already initialized, optionally passing a function context.
4457         whenReady: function (callback, context) {
4458                 if (this._loaded) {
4459                         callback.call(context || this, {target: this});
4460                 } else {
4461                         this.on('load', callback, context);
4462                 }
4463                 return this;
4464         },
4465
4466
4467         // private methods for getting map state
4468
4469         _getMapPanePos: function () {
4470                 return getPosition(this._mapPane) || new Point(0, 0);
4471         },
4472
4473         _moved: function () {
4474                 var pos = this._getMapPanePos();
4475                 return pos && !pos.equals([0, 0]);
4476         },
4477
4478         _getTopLeftPoint: function (center, zoom) {
4479                 var pixelOrigin = center && zoom !== undefined ?
4480                         this._getNewPixelOrigin(center, zoom) :
4481                         this.getPixelOrigin();
4482                 return pixelOrigin.subtract(this._getMapPanePos());
4483         },
4484
4485         _getNewPixelOrigin: function (center, zoom) {
4486                 var viewHalf = this.getSize()._divideBy(2);
4487                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4488         },
4489
4490         _latLngToNewLayerPoint: function (latlng, zoom, center) {
4491                 var topLeft = this._getNewPixelOrigin(center, zoom);
4492                 return this.project(latlng, zoom)._subtract(topLeft);
4493         },
4494
4495         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4496                 var topLeft = this._getNewPixelOrigin(center, zoom);
4497                 return toBounds([
4498                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4499                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4500                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4501                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4502                 ]);
4503         },
4504
4505         // layer point of the current center
4506         _getCenterLayerPoint: function () {
4507                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4508         },
4509
4510         // offset of the specified place to the current center in pixels
4511         _getCenterOffset: function (latlng) {
4512                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4513         },
4514
4515         // adjust center for view to get inside bounds
4516         _limitCenter: function (center, zoom, bounds) {
4517
4518                 if (!bounds) { return center; }
4519
4520                 var centerPoint = this.project(center, zoom),
4521                     viewHalf = this.getSize().divideBy(2),
4522                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4523                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4524
4525                 // If offset is less than a pixel, ignore.
4526                 // This prevents unstable projections from getting into
4527                 // an infinite loop of tiny offsets.
4528                 if (offset.round().equals([0, 0])) {
4529                         return center;
4530                 }
4531
4532                 return this.unproject(centerPoint.add(offset), zoom);
4533         },
4534
4535         // adjust offset for view to get inside bounds
4536         _limitOffset: function (offset, bounds) {
4537                 if (!bounds) { return offset; }
4538
4539                 var viewBounds = this.getPixelBounds(),
4540                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4541
4542                 return offset.add(this._getBoundsOffset(newBounds, bounds));
4543         },
4544
4545         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4546         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4547                 var projectedMaxBounds = toBounds(
4548                         this.project(maxBounds.getNorthEast(), zoom),
4549                         this.project(maxBounds.getSouthWest(), zoom)
4550                     ),
4551                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4552                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4553
4554                     dx = this._rebound(minOffset.x, -maxOffset.x),
4555                     dy = this._rebound(minOffset.y, -maxOffset.y);
4556
4557                 return new Point(dx, dy);
4558         },
4559
4560         _rebound: function (left, right) {
4561                 return left + right > 0 ?
4562                         Math.round(left - right) / 2 :
4563                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4564         },
4565
4566         _limitZoom: function (zoom) {
4567                 var min = this.getMinZoom(),
4568                     max = this.getMaxZoom(),
4569                     snap = any3d ? this.options.zoomSnap : 1;
4570                 if (snap) {
4571                         zoom = Math.round(zoom / snap) * snap;
4572                 }
4573                 return Math.max(min, Math.min(max, zoom));
4574         },
4575
4576         _onPanTransitionStep: function () {
4577                 this.fire('move');
4578         },
4579
4580         _onPanTransitionEnd: function () {
4581                 removeClass(this._mapPane, 'leaflet-pan-anim');
4582                 this.fire('moveend');
4583         },
4584
4585         _tryAnimatedPan: function (center, options) {
4586                 // difference between the new and current centers in pixels
4587                 var offset = this._getCenterOffset(center)._trunc();
4588
4589                 // don't animate too far unless animate: true specified in options
4590                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4591
4592                 this.panBy(offset, options);
4593
4594                 return true;
4595         },
4596
4597         _createAnimProxy: function () {
4598
4599                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4600                 this._panes.mapPane.appendChild(proxy);
4601
4602                 this.on('zoomanim', function (e) {
4603                         var prop = TRANSFORM,
4604                             transform = this._proxy.style[prop];
4605
4606                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4607
4608                         // workaround for case when transform is the same and so transitionend event is not fired
4609                         if (transform === this._proxy.style[prop] && this._animatingZoom) {
4610                                 this._onZoomTransitionEnd();
4611                         }
4612                 }, this);
4613
4614                 this.on('load moveend', function () {
4615                         var c = this.getCenter(),
4616                             z = this.getZoom();
4617                         setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4618                 }, this);
4619
4620                 this._on('unload', this._destroyAnimProxy, this);
4621         },
4622
4623         _destroyAnimProxy: function () {
4624                 remove(this._proxy);
4625                 delete this._proxy;
4626         },
4627
4628         _catchTransitionEnd: function (e) {
4629                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4630                         this._onZoomTransitionEnd();
4631                 }
4632         },
4633
4634         _nothingToAnimate: function () {
4635                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4636         },
4637
4638         _tryAnimatedZoom: function (center, zoom, options) {
4639
4640                 if (this._animatingZoom) { return true; }
4641
4642                 options = options || {};
4643
4644                 // don't animate if disabled, not supported or zoom difference is too large
4645                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4646                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4647
4648                 // offset is the pixel coords of the zoom origin relative to the current center
4649                 var scale = this.getZoomScale(zoom),
4650                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4651
4652                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4653                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4654
4655                 requestAnimFrame(function () {
4656                         this
4657                             ._moveStart(true, false)
4658                             ._animateZoom(center, zoom, true);
4659                 }, this);
4660
4661                 return true;
4662         },
4663
4664         _animateZoom: function (center, zoom, startAnim, noUpdate) {
4665                 if (!this._mapPane) { return; }
4666
4667                 if (startAnim) {
4668                         this._animatingZoom = true;
4669
4670                         // remember what center/zoom to set after animation
4671                         this._animateToCenter = center;
4672                         this._animateToZoom = zoom;
4673
4674                         addClass(this._mapPane, 'leaflet-zoom-anim');
4675                 }
4676
4677                 // @event zoomanim: ZoomAnimEvent
4678                 // Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
4679                 this.fire('zoomanim', {
4680                         center: center,
4681                         zoom: zoom,
4682                         noUpdate: noUpdate
4683                 });
4684
4685                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4686                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4687         },
4688
4689         _onZoomTransitionEnd: function () {
4690                 if (!this._animatingZoom) { return; }
4691
4692                 if (this._mapPane) {
4693                         removeClass(this._mapPane, 'leaflet-zoom-anim');
4694                 }
4695
4696                 this._animatingZoom = false;
4697
4698                 this._move(this._animateToCenter, this._animateToZoom);
4699
4700                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4701                 requestAnimFrame(function () {
4702                         this._moveEnd(true);
4703                 }, this);
4704         }
4705 });
4706
4707 // @section
4708
4709 // @factory L.map(id: String, options?: Map options)
4710 // Instantiates a map object given the DOM ID of a `<div>` element
4711 // and optionally an object literal with `Map options`.
4712 //
4713 // @alternative
4714 // @factory L.map(el: HTMLElement, options?: Map options)
4715 // Instantiates a map object given an instance of a `<div>` HTML element
4716 // and optionally an object literal with `Map options`.
4717 function createMap(id, options) {
4718         return new Map(id, options);
4719 }
4720
4721 /*
4722  * @class Control
4723  * @aka L.Control
4724  * @inherits Class
4725  *
4726  * L.Control is a base class for implementing map controls. Handles positioning.
4727  * All other controls extend from this class.
4728  */
4729
4730 var Control = Class.extend({
4731         // @section
4732         // @aka Control options
4733         options: {
4734                 // @option position: String = 'topright'
4735                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4736                 // `'topright'`, `'bottomleft'` or `'bottomright'`
4737                 position: 'topright'
4738         },
4739
4740         initialize: function (options) {
4741                 setOptions(this, options);
4742         },
4743
4744         /* @section
4745          * Classes extending L.Control will inherit the following methods:
4746          *
4747          * @method getPosition: string
4748          * Returns the position of the control.
4749          */
4750         getPosition: function () {
4751                 return this.options.position;
4752         },
4753
4754         // @method setPosition(position: string): this
4755         // Sets the position of the control.
4756         setPosition: function (position) {
4757                 var map = this._map;
4758
4759                 if (map) {
4760                         map.removeControl(this);
4761                 }
4762
4763                 this.options.position = position;
4764
4765                 if (map) {
4766                         map.addControl(this);
4767                 }
4768
4769                 return this;
4770         },
4771
4772         // @method getContainer: HTMLElement
4773         // Returns the HTMLElement that contains the control.
4774         getContainer: function () {
4775                 return this._container;
4776         },
4777
4778         // @method addTo(map: Map): this
4779         // Adds the control to the given map.
4780         addTo: function (map) {
4781                 this.remove();
4782                 this._map = map;
4783
4784                 var container = this._container = this.onAdd(map),
4785                     pos = this.getPosition(),
4786                     corner = map._controlCorners[pos];
4787
4788                 addClass(container, 'leaflet-control');
4789
4790                 if (pos.indexOf('bottom') !== -1) {
4791                         corner.insertBefore(container, corner.firstChild);
4792                 } else {
4793                         corner.appendChild(container);
4794                 }
4795
4796                 this._map.on('unload', this.remove, this);
4797
4798                 return this;
4799         },
4800
4801         // @method remove: this
4802         // Removes the control from the map it is currently active on.
4803         remove: function () {
4804                 if (!this._map) {
4805                         return this;
4806                 }
4807
4808                 remove(this._container);
4809
4810                 if (this.onRemove) {
4811                         this.onRemove(this._map);
4812                 }
4813
4814                 this._map.off('unload', this.remove, this);
4815                 this._map = null;
4816
4817                 return this;
4818         },
4819
4820         _refocusOnMap: function (e) {
4821                 // if map exists and event is not a keyboard event
4822                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4823                         this._map.getContainer().focus();
4824                 }
4825         }
4826 });
4827
4828 var control = function (options) {
4829         return new Control(options);
4830 };
4831
4832 /* @section Extension methods
4833  * @uninheritable
4834  *
4835  * Every control should extend from `L.Control` and (re-)implement the following methods.
4836  *
4837  * @method onAdd(map: Map): HTMLElement
4838  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4839  *
4840  * @method onRemove(map: Map)
4841  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4842  */
4843
4844 /* @namespace Map
4845  * @section Methods for Layers and Controls
4846  */
4847 Map.include({
4848         // @method addControl(control: Control): this
4849         // Adds the given control to the map
4850         addControl: function (control) {
4851                 control.addTo(this);
4852                 return this;
4853         },
4854
4855         // @method removeControl(control: Control): this
4856         // Removes the given control from the map
4857         removeControl: function (control) {
4858                 control.remove();
4859                 return this;
4860         },
4861
4862         _initControlPos: function () {
4863                 var corners = this._controlCorners = {},
4864                     l = 'leaflet-',
4865                     container = this._controlContainer =
4866                             create$1('div', l + 'control-container', this._container);
4867
4868                 function createCorner(vSide, hSide) {
4869                         var className = l + vSide + ' ' + l + hSide;
4870
4871                         corners[vSide + hSide] = create$1('div', className, container);
4872                 }
4873
4874                 createCorner('top', 'left');
4875                 createCorner('top', 'right');
4876                 createCorner('bottom', 'left');
4877                 createCorner('bottom', 'right');
4878         },
4879
4880         _clearControlPos: function () {
4881                 for (var i in this._controlCorners) {
4882                         remove(this._controlCorners[i]);
4883                 }
4884                 remove(this._controlContainer);
4885                 delete this._controlCorners;
4886                 delete this._controlContainer;
4887         }
4888 });
4889
4890 /*
4891  * @class Control.Layers
4892  * @aka L.Control.Layers
4893  * @inherits Control
4894  *
4895  * 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`.
4896  *
4897  * @example
4898  *
4899  * ```js
4900  * var baseLayers = {
4901  *      "Mapbox": mapbox,
4902  *      "OpenStreetMap": osm
4903  * };
4904  *
4905  * var overlays = {
4906  *      "Marker": marker,
4907  *      "Roads": roadsLayer
4908  * };
4909  *
4910  * L.control.layers(baseLayers, overlays).addTo(map);
4911  * ```
4912  *
4913  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4914  *
4915  * ```js
4916  * {
4917  *     "<someName1>": layer1,
4918  *     "<someName2>": layer2
4919  * }
4920  * ```
4921  *
4922  * The layer names can contain HTML, which allows you to add additional styling to the items:
4923  *
4924  * ```js
4925  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4926  * ```
4927  */
4928
4929 var Layers = Control.extend({
4930         // @section
4931         // @aka Control.Layers options
4932         options: {
4933                 // @option collapsed: Boolean = true
4934                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4935                 collapsed: true,
4936                 position: 'topright',
4937
4938                 // @option autoZIndex: Boolean = true
4939                 // 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.
4940                 autoZIndex: true,
4941
4942                 // @option hideSingleBase: Boolean = false
4943                 // If `true`, the base layers in the control will be hidden when there is only one.
4944                 hideSingleBase: false,
4945
4946                 // @option sortLayers: Boolean = false
4947                 // Whether to sort the layers. When `false`, layers will keep the order
4948                 // in which they were added to the control.
4949                 sortLayers: false,
4950
4951                 // @option sortFunction: Function = *
4952                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4953                 // that will be used for sorting the layers, when `sortLayers` is `true`.
4954                 // The function receives both the `L.Layer` instances and their names, as in
4955                 // `sortFunction(layerA, layerB, nameA, nameB)`.
4956                 // By default, it sorts layers alphabetically by their name.
4957                 sortFunction: function (layerA, layerB, nameA, nameB) {
4958                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4959                 }
4960         },
4961
4962         initialize: function (baseLayers, overlays, options) {
4963                 setOptions(this, options);
4964
4965                 this._layerControlInputs = [];
4966                 this._layers = [];
4967                 this._lastZIndex = 0;
4968                 this._handlingClick = false;
4969
4970                 for (var i in baseLayers) {
4971                         this._addLayer(baseLayers[i], i);
4972                 }
4973
4974                 for (i in overlays) {
4975                         this._addLayer(overlays[i], i, true);
4976                 }
4977         },
4978
4979         onAdd: function (map) {
4980                 this._initLayout();
4981                 this._update();
4982
4983                 this._map = map;
4984                 map.on('zoomend', this._checkDisabledLayers, this);
4985
4986                 for (var i = 0; i < this._layers.length; i++) {
4987                         this._layers[i].layer.on('add remove', this._onLayerChange, this);
4988                 }
4989
4990                 return this._container;
4991         },
4992
4993         addTo: function (map) {
4994                 Control.prototype.addTo.call(this, map);
4995                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4996                 return this._expandIfNotCollapsed();
4997         },
4998
4999         onRemove: function () {
5000                 this._map.off('zoomend', this._checkDisabledLayers, this);
5001
5002                 for (var i = 0; i < this._layers.length; i++) {
5003                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
5004                 }
5005         },
5006
5007         // @method addBaseLayer(layer: Layer, name: String): this
5008         // Adds a base layer (radio button entry) with the given name to the control.
5009         addBaseLayer: function (layer, name) {
5010                 this._addLayer(layer, name);
5011                 return (this._map) ? this._update() : this;
5012         },
5013
5014         // @method addOverlay(layer: Layer, name: String): this
5015         // Adds an overlay (checkbox entry) with the given name to the control.
5016         addOverlay: function (layer, name) {
5017                 this._addLayer(layer, name, true);
5018                 return (this._map) ? this._update() : this;
5019         },
5020
5021         // @method removeLayer(layer: Layer): this
5022         // Remove the given layer from the control.
5023         removeLayer: function (layer) {
5024                 layer.off('add remove', this._onLayerChange, this);
5025
5026                 var obj = this._getLayer(stamp(layer));
5027                 if (obj) {
5028                         this._layers.splice(this._layers.indexOf(obj), 1);
5029                 }
5030                 return (this._map) ? this._update() : this;
5031         },
5032
5033         // @method expand(): this
5034         // Expand the control container if collapsed.
5035         expand: function () {
5036                 addClass(this._container, 'leaflet-control-layers-expanded');
5037                 this._section.style.height = null;
5038                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
5039                 if (acceptableHeight < this._section.clientHeight) {
5040                         addClass(this._section, 'leaflet-control-layers-scrollbar');
5041                         this._section.style.height = acceptableHeight + 'px';
5042                 } else {
5043                         removeClass(this._section, 'leaflet-control-layers-scrollbar');
5044                 }
5045                 this._checkDisabledLayers();
5046                 return this;
5047         },
5048
5049         // @method collapse(): this
5050         // Collapse the control container if expanded.
5051         collapse: function () {
5052                 removeClass(this._container, 'leaflet-control-layers-expanded');
5053                 return this;
5054         },
5055
5056         _initLayout: function () {
5057                 var className = 'leaflet-control-layers',
5058                     container = this._container = create$1('div', className),
5059                     collapsed = this.options.collapsed;
5060
5061                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
5062                 container.setAttribute('aria-haspopup', true);
5063
5064                 disableClickPropagation(container);
5065                 disableScrollPropagation(container);
5066
5067                 var section = this._section = create$1('section', className + '-list');
5068
5069                 if (collapsed) {
5070                         this._map.on('click', this.collapse, this);
5071
5072                         if (!android) {
5073                                 on(container, {
5074                                         mouseenter: this.expand,
5075                                         mouseleave: this.collapse
5076                                 }, this);
5077                         }
5078                 }
5079
5080                 var link = this._layersLink = create$1('a', className + '-toggle', container);
5081                 link.href = '#';
5082                 link.title = 'Layers';
5083
5084                 if (touch) {
5085                         on(link, 'click', stop);
5086                         on(link, 'click', this.expand, this);
5087                 } else {
5088                         on(link, 'focus', this.expand, this);
5089                 }
5090
5091                 if (!collapsed) {
5092                         this.expand();
5093                 }
5094
5095                 this._baseLayersList = create$1('div', className + '-base', section);
5096                 this._separator = create$1('div', className + '-separator', section);
5097                 this._overlaysList = create$1('div', className + '-overlays', section);
5098
5099                 container.appendChild(section);
5100         },
5101
5102         _getLayer: function (id) {
5103                 for (var i = 0; i < this._layers.length; i++) {
5104
5105                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5106                                 return this._layers[i];
5107                         }
5108                 }
5109         },
5110
5111         _addLayer: function (layer, name, overlay) {
5112                 if (this._map) {
5113                         layer.on('add remove', this._onLayerChange, this);
5114                 }
5115
5116                 this._layers.push({
5117                         layer: layer,
5118                         name: name,
5119                         overlay: overlay
5120                 });
5121
5122                 if (this.options.sortLayers) {
5123                         this._layers.sort(bind(function (a, b) {
5124                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5125                         }, this));
5126                 }
5127
5128                 if (this.options.autoZIndex && layer.setZIndex) {
5129                         this._lastZIndex++;
5130                         layer.setZIndex(this._lastZIndex);
5131                 }
5132
5133                 this._expandIfNotCollapsed();
5134         },
5135
5136         _update: function () {
5137                 if (!this._container) { return this; }
5138
5139                 empty(this._baseLayersList);
5140                 empty(this._overlaysList);
5141
5142                 this._layerControlInputs = [];
5143                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5144
5145                 for (i = 0; i < this._layers.length; i++) {
5146                         obj = this._layers[i];
5147                         this._addItem(obj);
5148                         overlaysPresent = overlaysPresent || obj.overlay;
5149                         baseLayersPresent = baseLayersPresent || !obj.overlay;
5150                         baseLayersCount += !obj.overlay ? 1 : 0;
5151                 }
5152
5153                 // Hide base layers section if there's only one layer.
5154                 if (this.options.hideSingleBase) {
5155                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5156                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5157                 }
5158
5159                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5160
5161                 return this;
5162         },
5163
5164         _onLayerChange: function (e) {
5165                 if (!this._handlingClick) {
5166                         this._update();
5167                 }
5168
5169                 var obj = this._getLayer(stamp(e.target));
5170
5171                 // @namespace Map
5172                 // @section Layer events
5173                 // @event baselayerchange: LayersControlEvent
5174                 // Fired when the base layer is changed through the [layer control](#control-layers).
5175                 // @event overlayadd: LayersControlEvent
5176                 // Fired when an overlay is selected through the [layer control](#control-layers).
5177                 // @event overlayremove: LayersControlEvent
5178                 // Fired when an overlay is deselected through the [layer control](#control-layers).
5179                 // @namespace Control.Layers
5180                 var type = obj.overlay ?
5181                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5182                         (e.type === 'add' ? 'baselayerchange' : null);
5183
5184                 if (type) {
5185                         this._map.fire(type, obj);
5186                 }
5187         },
5188
5189         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5190         _createRadioElement: function (name, checked) {
5191
5192                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5193                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5194
5195                 var radioFragment = document.createElement('div');
5196                 radioFragment.innerHTML = radioHtml;
5197
5198                 return radioFragment.firstChild;
5199         },
5200
5201         _addItem: function (obj) {
5202                 var label = document.createElement('label'),
5203                     checked = this._map.hasLayer(obj.layer),
5204                     input;
5205
5206                 if (obj.overlay) {
5207                         input = document.createElement('input');
5208                         input.type = 'checkbox';
5209                         input.className = 'leaflet-control-layers-selector';
5210                         input.defaultChecked = checked;
5211                 } else {
5212                         input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
5213                 }
5214
5215                 this._layerControlInputs.push(input);
5216                 input.layerId = stamp(obj.layer);
5217
5218                 on(input, 'click', this._onInputClick, this);
5219
5220                 var name = document.createElement('span');
5221                 name.innerHTML = ' ' + obj.name;
5222
5223                 // Helps from preventing layer control flicker when checkboxes are disabled
5224                 // https://github.com/Leaflet/Leaflet/issues/2771
5225                 var holder = document.createElement('div');
5226
5227                 label.appendChild(holder);
5228                 holder.appendChild(input);
5229                 holder.appendChild(name);
5230
5231                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5232                 container.appendChild(label);
5233
5234                 this._checkDisabledLayers();
5235                 return label;
5236         },
5237
5238         _onInputClick: function () {
5239                 var inputs = this._layerControlInputs,
5240                     input, layer;
5241                 var addedLayers = [],
5242                     removedLayers = [];
5243
5244                 this._handlingClick = true;
5245
5246                 for (var i = inputs.length - 1; i >= 0; i--) {
5247                         input = inputs[i];
5248                         layer = this._getLayer(input.layerId).layer;
5249
5250                         if (input.checked) {
5251                                 addedLayers.push(layer);
5252                         } else if (!input.checked) {
5253                                 removedLayers.push(layer);
5254                         }
5255                 }
5256
5257                 // Bugfix issue 2318: Should remove all old layers before readding new ones
5258                 for (i = 0; i < removedLayers.length; i++) {
5259                         if (this._map.hasLayer(removedLayers[i])) {
5260                                 this._map.removeLayer(removedLayers[i]);
5261                         }
5262                 }
5263                 for (i = 0; i < addedLayers.length; i++) {
5264                         if (!this._map.hasLayer(addedLayers[i])) {
5265                                 this._map.addLayer(addedLayers[i]);
5266                         }
5267                 }
5268
5269                 this._handlingClick = false;
5270
5271                 this._refocusOnMap();
5272         },
5273
5274         _checkDisabledLayers: function () {
5275                 var inputs = this._layerControlInputs,
5276                     input,
5277                     layer,
5278                     zoom = this._map.getZoom();
5279
5280                 for (var i = inputs.length - 1; i >= 0; i--) {
5281                         input = inputs[i];
5282                         layer = this._getLayer(input.layerId).layer;
5283                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5284                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5285
5286                 }
5287         },
5288
5289         _expandIfNotCollapsed: function () {
5290                 if (this._map && !this.options.collapsed) {
5291                         this.expand();
5292                 }
5293                 return this;
5294         },
5295
5296         _expand: function () {
5297                 // Backward compatibility, remove me in 1.1.
5298                 return this.expand();
5299         },
5300
5301         _collapse: function () {
5302                 // Backward compatibility, remove me in 1.1.
5303                 return this.collapse();
5304         }
5305
5306 });
5307
5308
5309 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5310 // Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
5311 var layers = function (baseLayers, overlays, options) {
5312         return new Layers(baseLayers, overlays, options);
5313 };
5314
5315 /*
5316  * @class Control.Zoom
5317  * @aka L.Control.Zoom
5318  * @inherits Control
5319  *
5320  * 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`.
5321  */
5322
5323 var Zoom = Control.extend({
5324         // @section
5325         // @aka Control.Zoom options
5326         options: {
5327                 position: 'topleft',
5328
5329                 // @option zoomInText: String = '+'
5330                 // The text set on the 'zoom in' button.
5331                 zoomInText: '+',
5332
5333                 // @option zoomInTitle: String = 'Zoom in'
5334                 // The title set on the 'zoom in' button.
5335                 zoomInTitle: 'Zoom in',
5336
5337                 // @option zoomOutText: String = '&#x2212;'
5338                 // The text set on the 'zoom out' button.
5339                 zoomOutText: '&#x2212;',
5340
5341                 // @option zoomOutTitle: String = 'Zoom out'
5342                 // The title set on the 'zoom out' button.
5343                 zoomOutTitle: 'Zoom out'
5344         },
5345
5346         onAdd: function (map) {
5347                 var zoomName = 'leaflet-control-zoom',
5348                     container = create$1('div', zoomName + ' leaflet-bar'),
5349                     options = this.options;
5350
5351                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
5352                         zoomName + '-in',  container, this._zoomIn);
5353                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5354                         zoomName + '-out', container, this._zoomOut);
5355
5356                 this._updateDisabled();
5357                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5358
5359                 return container;
5360         },
5361
5362         onRemove: function (map) {
5363                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5364         },
5365
5366         disable: function () {
5367                 this._disabled = true;
5368                 this._updateDisabled();
5369                 return this;
5370         },
5371
5372         enable: function () {
5373                 this._disabled = false;
5374                 this._updateDisabled();
5375                 return this;
5376         },
5377
5378         _zoomIn: function (e) {
5379                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5380                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5381                 }
5382         },
5383
5384         _zoomOut: function (e) {
5385                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5386                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5387                 }
5388         },
5389
5390         _createButton: function (html, title, className, container, fn) {
5391                 var link = create$1('a', className, container);
5392                 link.innerHTML = html;
5393                 link.href = '#';
5394                 link.title = title;
5395
5396                 /*
5397                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5398                  */
5399                 link.setAttribute('role', 'button');
5400                 link.setAttribute('aria-label', title);
5401
5402                 disableClickPropagation(link);
5403                 on(link, 'click', stop);
5404                 on(link, 'click', fn, this);
5405                 on(link, 'click', this._refocusOnMap, this);
5406
5407                 return link;
5408         },
5409
5410         _updateDisabled: function () {
5411                 var map = this._map,
5412                     className = 'leaflet-disabled';
5413
5414                 removeClass(this._zoomInButton, className);
5415                 removeClass(this._zoomOutButton, className);
5416
5417                 if (this._disabled || map._zoom === map.getMinZoom()) {
5418                         addClass(this._zoomOutButton, className);
5419                 }
5420                 if (this._disabled || map._zoom === map.getMaxZoom()) {
5421                         addClass(this._zoomInButton, className);
5422                 }
5423         }
5424 });
5425
5426 // @namespace Map
5427 // @section Control options
5428 // @option zoomControl: Boolean = true
5429 // Whether a [zoom control](#control-zoom) is added to the map by default.
5430 Map.mergeOptions({
5431         zoomControl: true
5432 });
5433
5434 Map.addInitHook(function () {
5435         if (this.options.zoomControl) {
5436                 // @section Controls
5437                 // @property zoomControl: Control.Zoom
5438                 // The default zoom control (only available if the
5439                 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5440                 this.zoomControl = new Zoom();
5441                 this.addControl(this.zoomControl);
5442         }
5443 });
5444
5445 // @namespace Control.Zoom
5446 // @factory L.control.zoom(options: Control.Zoom options)
5447 // Creates a zoom control
5448 var zoom = function (options) {
5449         return new Zoom(options);
5450 };
5451
5452 /*
5453  * @class Control.Scale
5454  * @aka L.Control.Scale
5455  * @inherits Control
5456  *
5457  * 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`.
5458  *
5459  * @example
5460  *
5461  * ```js
5462  * L.control.scale().addTo(map);
5463  * ```
5464  */
5465
5466 var Scale = Control.extend({
5467         // @section
5468         // @aka Control.Scale options
5469         options: {
5470                 position: 'bottomleft',
5471
5472                 // @option maxWidth: Number = 100
5473                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5474                 maxWidth: 100,
5475
5476                 // @option metric: Boolean = True
5477                 // Whether to show the metric scale line (m/km).
5478                 metric: true,
5479
5480                 // @option imperial: Boolean = True
5481                 // Whether to show the imperial scale line (mi/ft).
5482                 imperial: true
5483
5484                 // @option updateWhenIdle: Boolean = false
5485                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5486         },
5487
5488         onAdd: function (map) {
5489                 var className = 'leaflet-control-scale',
5490                     container = create$1('div', className),
5491                     options = this.options;
5492
5493                 this._addScales(options, className + '-line', container);
5494
5495                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5496                 map.whenReady(this._update, this);
5497
5498                 return container;
5499         },
5500
5501         onRemove: function (map) {
5502                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5503         },
5504
5505         _addScales: function (options, className, container) {
5506                 if (options.metric) {
5507                         this._mScale = create$1('div', className, container);
5508                 }
5509                 if (options.imperial) {
5510                         this._iScale = create$1('div', className, container);
5511                 }
5512         },
5513
5514         _update: function () {
5515                 var map = this._map,
5516                     y = map.getSize().y / 2;
5517
5518                 var maxMeters = map.distance(
5519                         map.containerPointToLatLng([0, y]),
5520                         map.containerPointToLatLng([this.options.maxWidth, y]));
5521
5522                 this._updateScales(maxMeters);
5523         },
5524
5525         _updateScales: function (maxMeters) {
5526                 if (this.options.metric && maxMeters) {
5527                         this._updateMetric(maxMeters);
5528                 }
5529                 if (this.options.imperial && maxMeters) {
5530                         this._updateImperial(maxMeters);
5531                 }
5532         },
5533
5534         _updateMetric: function (maxMeters) {
5535                 var meters = this._getRoundNum(maxMeters),
5536                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5537
5538                 this._updateScale(this._mScale, label, meters / maxMeters);
5539         },
5540
5541         _updateImperial: function (maxMeters) {
5542                 var maxFeet = maxMeters * 3.2808399,
5543                     maxMiles, miles, feet;
5544
5545                 if (maxFeet > 5280) {
5546                         maxMiles = maxFeet / 5280;
5547                         miles = this._getRoundNum(maxMiles);
5548                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5549
5550                 } else {
5551                         feet = this._getRoundNum(maxFeet);
5552                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5553                 }
5554         },
5555
5556         _updateScale: function (scale, text, ratio) {
5557                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5558                 scale.innerHTML = text;
5559         },
5560
5561         _getRoundNum: function (num) {
5562                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5563                     d = num / pow10;
5564
5565                 d = d >= 10 ? 10 :
5566                     d >= 5 ? 5 :
5567                     d >= 3 ? 3 :
5568                     d >= 2 ? 2 : 1;
5569
5570                 return pow10 * d;
5571         }
5572 });
5573
5574
5575 // @factory L.control.scale(options?: Control.Scale options)
5576 // Creates an scale control with the given options.
5577 var scale = function (options) {
5578         return new Scale(options);
5579 };
5580
5581 /*
5582  * @class Control.Attribution
5583  * @aka L.Control.Attribution
5584  * @inherits Control
5585  *
5586  * 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.
5587  */
5588
5589 var Attribution = Control.extend({
5590         // @section
5591         // @aka Control.Attribution options
5592         options: {
5593                 position: 'bottomright',
5594
5595                 // @option prefix: String = 'Leaflet'
5596                 // The HTML text shown before the attributions. Pass `false` to disable.
5597                 prefix: '<a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5598         },
5599
5600         initialize: function (options) {
5601                 setOptions(this, options);
5602
5603                 this._attributions = {};
5604         },
5605
5606         onAdd: function (map) {
5607                 map.attributionControl = this;
5608                 this._container = create$1('div', 'leaflet-control-attribution');
5609                 disableClickPropagation(this._container);
5610
5611                 // TODO ugly, refactor
5612                 for (var i in map._layers) {
5613                         if (map._layers[i].getAttribution) {
5614                                 this.addAttribution(map._layers[i].getAttribution());
5615                         }
5616                 }
5617
5618                 this._update();
5619
5620                 return this._container;
5621         },
5622
5623         // @method setPrefix(prefix: String): this
5624         // Sets the text before the attributions.
5625         setPrefix: function (prefix) {
5626                 this.options.prefix = prefix;
5627                 this._update();
5628                 return this;
5629         },
5630
5631         // @method addAttribution(text: String): this
5632         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5633         addAttribution: function (text) {
5634                 if (!text) { return this; }
5635
5636                 if (!this._attributions[text]) {
5637                         this._attributions[text] = 0;
5638                 }
5639                 this._attributions[text]++;
5640
5641                 this._update();
5642
5643                 return this;
5644         },
5645
5646         // @method removeAttribution(text: String): this
5647         // Removes an attribution text.
5648         removeAttribution: function (text) {
5649                 if (!text) { return this; }
5650
5651                 if (this._attributions[text]) {
5652                         this._attributions[text]--;
5653                         this._update();
5654                 }
5655
5656                 return this;
5657         },
5658
5659         _update: function () {
5660                 if (!this._map) { return; }
5661
5662                 var attribs = [];
5663
5664                 for (var i in this._attributions) {
5665                         if (this._attributions[i]) {
5666                                 attribs.push(i);
5667                         }
5668                 }
5669
5670                 var prefixAndAttribs = [];
5671
5672                 if (this.options.prefix) {
5673                         prefixAndAttribs.push(this.options.prefix);
5674                 }
5675                 if (attribs.length) {
5676                         prefixAndAttribs.push(attribs.join(', '));
5677                 }
5678
5679                 this._container.innerHTML = prefixAndAttribs.join(' | ');
5680         }
5681 });
5682
5683 // @namespace Map
5684 // @section Control options
5685 // @option attributionControl: Boolean = true
5686 // Whether a [attribution control](#control-attribution) is added to the map by default.
5687 Map.mergeOptions({
5688         attributionControl: true
5689 });
5690
5691 Map.addInitHook(function () {
5692         if (this.options.attributionControl) {
5693                 new Attribution().addTo(this);
5694         }
5695 });
5696
5697 // @namespace Control.Attribution
5698 // @factory L.control.attribution(options: Control.Attribution options)
5699 // Creates an attribution control.
5700 var attribution = function (options) {
5701         return new Attribution(options);
5702 };
5703
5704 Control.Layers = Layers;
5705 Control.Zoom = Zoom;
5706 Control.Scale = Scale;
5707 Control.Attribution = Attribution;
5708
5709 control.layers = layers;
5710 control.zoom = zoom;
5711 control.scale = scale;
5712 control.attribution = attribution;
5713
5714 /*
5715         L.Handler is a base class for handler classes that are used internally to inject
5716         interaction features like dragging to classes like Map and Marker.
5717 */
5718
5719 // @class Handler
5720 // @aka L.Handler
5721 // Abstract class for map interaction handlers
5722
5723 var Handler = Class.extend({
5724         initialize: function (map) {
5725                 this._map = map;
5726         },
5727
5728         // @method enable(): this
5729         // Enables the handler
5730         enable: function () {
5731                 if (this._enabled) { return this; }
5732
5733                 this._enabled = true;
5734                 this.addHooks();
5735                 return this;
5736         },
5737
5738         // @method disable(): this
5739         // Disables the handler
5740         disable: function () {
5741                 if (!this._enabled) { return this; }
5742
5743                 this._enabled = false;
5744                 this.removeHooks();
5745                 return this;
5746         },
5747
5748         // @method enabled(): Boolean
5749         // Returns `true` if the handler is enabled
5750         enabled: function () {
5751                 return !!this._enabled;
5752         }
5753
5754         // @section Extension methods
5755         // Classes inheriting from `Handler` must implement the two following methods:
5756         // @method addHooks()
5757         // Called when the handler is enabled, should add event hooks.
5758         // @method removeHooks()
5759         // Called when the handler is disabled, should remove the event hooks added previously.
5760 });
5761
5762 // @section There is static function which can be called without instantiating L.Handler:
5763 // @function addTo(map: Map, name: String): this
5764 // Adds a new Handler to the given map with the given name.
5765 Handler.addTo = function (map, name) {
5766         map.addHandler(name, this);
5767         return this;
5768 };
5769
5770 var Mixin = {Events: Events};
5771
5772 /*
5773  * @class Draggable
5774  * @aka L.Draggable
5775  * @inherits Evented
5776  *
5777  * A class for making DOM elements draggable (including touch support).
5778  * Used internally for map and marker dragging. Only works for elements
5779  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5780  *
5781  * @example
5782  * ```js
5783  * var draggable = new L.Draggable(elementToDrag);
5784  * draggable.enable();
5785  * ```
5786  */
5787
5788 var START = touch ? 'touchstart mousedown' : 'mousedown';
5789 var END = {
5790         mousedown: 'mouseup',
5791         touchstart: 'touchend',
5792         pointerdown: 'touchend',
5793         MSPointerDown: 'touchend'
5794 };
5795 var MOVE = {
5796         mousedown: 'mousemove',
5797         touchstart: 'touchmove',
5798         pointerdown: 'touchmove',
5799         MSPointerDown: 'touchmove'
5800 };
5801
5802
5803 var Draggable = Evented.extend({
5804
5805         options: {
5806                 // @section
5807                 // @aka Draggable options
5808                 // @option clickTolerance: Number = 3
5809                 // The max number of pixels a user can shift the mouse pointer during a click
5810                 // for it to be considered a valid click (as opposed to a mouse drag).
5811                 clickTolerance: 3
5812         },
5813
5814         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5815         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5816         initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5817                 setOptions(this, options);
5818
5819                 this._element = element;
5820                 this._dragStartTarget = dragStartTarget || element;
5821                 this._preventOutline = preventOutline$$1;
5822         },
5823
5824         // @method enable()
5825         // Enables the dragging ability
5826         enable: function () {
5827                 if (this._enabled) { return; }
5828
5829                 on(this._dragStartTarget, START, this._onDown, this);
5830
5831                 this._enabled = true;
5832         },
5833
5834         // @method disable()
5835         // Disables the dragging ability
5836         disable: function () {
5837                 if (!this._enabled) { return; }
5838
5839                 // If we're currently dragging this draggable,
5840                 // disabling it counts as first ending the drag.
5841                 if (Draggable._dragging === this) {
5842                         this.finishDrag();
5843                 }
5844
5845                 off(this._dragStartTarget, START, this._onDown, this);
5846
5847                 this._enabled = false;
5848                 this._moved = false;
5849         },
5850
5851         _onDown: function (e) {
5852                 // Ignore simulated events, since we handle both touch and
5853                 // mouse explicitly; otherwise we risk getting duplicates of
5854                 // touch events, see #4315.
5855                 // Also ignore the event if disabled; this happens in IE11
5856                 // under some circumstances, see #3666.
5857                 if (e._simulated || !this._enabled) { return; }
5858
5859                 this._moved = false;
5860
5861                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5862
5863                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5864                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.
5865
5866                 if (this._preventOutline) {
5867                         preventOutline(this._element);
5868                 }
5869
5870                 disableImageDrag();
5871                 disableTextSelection();
5872
5873                 if (this._moving) { return; }
5874
5875                 // @event down: Event
5876                 // Fired when a drag is about to start.
5877                 this.fire('down');
5878
5879                 var first = e.touches ? e.touches[0] : e,
5880                     sizedParent = getSizedParentNode(this._element);
5881
5882                 this._startPoint = new Point(first.clientX, first.clientY);
5883
5884                 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5885                 this._parentScale = getScale(sizedParent);
5886
5887                 on(document, MOVE[e.type], this._onMove, this);
5888                 on(document, END[e.type], this._onUp, this);
5889         },
5890
5891         _onMove: function (e) {
5892                 // Ignore simulated events, since we handle both touch and
5893                 // mouse explicitly; otherwise we risk getting duplicates of
5894                 // touch events, see #4315.
5895                 // Also ignore the event if disabled; this happens in IE11
5896                 // under some circumstances, see #3666.
5897                 if (e._simulated || !this._enabled) { return; }
5898
5899                 if (e.touches && e.touches.length > 1) {
5900                         this._moved = true;
5901                         return;
5902                 }
5903
5904                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5905                     offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5906
5907                 if (!offset.x && !offset.y) { return; }
5908                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5909
5910                 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5911                 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5912                 // and we can use the cached value for the scale.
5913                 offset.x /= this._parentScale.x;
5914                 offset.y /= this._parentScale.y;
5915
5916                 preventDefault(e);
5917
5918                 if (!this._moved) {
5919                         // @event dragstart: Event
5920                         // Fired when a drag starts
5921                         this.fire('dragstart');
5922
5923                         this._moved = true;
5924                         this._startPos = getPosition(this._element).subtract(offset);
5925
5926                         addClass(document.body, 'leaflet-dragging');
5927
5928                         this._lastTarget = e.target || e.srcElement;
5929                         // IE and Edge do not give the <use> element, so fetch it
5930                         // if necessary
5931                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5932                                 this._lastTarget = this._lastTarget.correspondingUseElement;
5933                         }
5934                         addClass(this._lastTarget, 'leaflet-drag-target');
5935                 }
5936
5937                 this._newPos = this._startPos.add(offset);
5938                 this._moving = true;
5939
5940                 cancelAnimFrame(this._animRequest);
5941                 this._lastEvent = e;
5942                 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5943         },
5944
5945         _updatePosition: function () {
5946                 var e = {originalEvent: this._lastEvent};
5947
5948                 // @event predrag: Event
5949                 // Fired continuously during dragging *before* each corresponding
5950                 // update of the element's position.
5951                 this.fire('predrag', e);
5952                 setPosition(this._element, this._newPos);
5953
5954                 // @event drag: Event
5955                 // Fired continuously during dragging.
5956                 this.fire('drag', e);
5957         },
5958
5959         _onUp: function (e) {
5960                 // Ignore simulated events, since we handle both touch and
5961                 // mouse explicitly; otherwise we risk getting duplicates of
5962                 // touch events, see #4315.
5963                 // Also ignore the event if disabled; this happens in IE11
5964                 // under some circumstances, see #3666.
5965                 if (e._simulated || !this._enabled) { return; }
5966                 this.finishDrag();
5967         },
5968
5969         finishDrag: function () {
5970                 removeClass(document.body, 'leaflet-dragging');
5971
5972                 if (this._lastTarget) {
5973                         removeClass(this._lastTarget, 'leaflet-drag-target');
5974                         this._lastTarget = null;
5975                 }
5976
5977                 for (var i in MOVE) {
5978                         off(document, MOVE[i], this._onMove, this);
5979                         off(document, END[i], this._onUp, this);
5980                 }
5981
5982                 enableImageDrag();
5983                 enableTextSelection();
5984
5985                 if (this._moved && this._moving) {
5986                         // ensure drag is not fired after dragend
5987                         cancelAnimFrame(this._animRequest);
5988
5989                         // @event dragend: DragEndEvent
5990                         // Fired when the drag ends.
5991                         this.fire('dragend', {
5992                                 distance: this._newPos.distanceTo(this._startPos)
5993                         });
5994                 }
5995
5996                 this._moving = false;
5997                 Draggable._dragging = false;
5998         }
5999
6000 });
6001
6002 /*
6003  * @namespace LineUtil
6004  *
6005  * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
6006  */
6007
6008 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
6009 // Improves rendering performance dramatically by lessening the number of points to draw.
6010
6011 // @function simplify(points: Point[], tolerance: Number): Point[]
6012 // Dramatically reduces the number of points in a polyline while retaining
6013 // its shape and returns a new array of simplified points, using the
6014 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
6015 // Used for a huge performance boost when processing/displaying Leaflet polylines for
6016 // each zoom level and also reducing visual noise. tolerance affects the amount of
6017 // simplification (lesser value means higher quality but slower and with more points).
6018 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
6019 function simplify(points, tolerance) {
6020         if (!tolerance || !points.length) {
6021                 return points.slice();
6022         }
6023
6024         var sqTolerance = tolerance * tolerance;
6025
6026             // stage 1: vertex reduction
6027             points = _reducePoints(points, sqTolerance);
6028
6029             // stage 2: Douglas-Peucker simplification
6030             points = _simplifyDP(points, sqTolerance);
6031
6032         return points;
6033 }
6034
6035 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
6036 // Returns the distance between point `p` and segment `p1` to `p2`.
6037 function pointToSegmentDistance(p, p1, p2) {
6038         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
6039 }
6040
6041 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
6042 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
6043 function closestPointOnSegment(p, p1, p2) {
6044         return _sqClosestPointOnSegment(p, p1, p2);
6045 }
6046
6047 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
6048 function _simplifyDP(points, sqTolerance) {
6049
6050         var len = points.length,
6051             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
6052             markers = new ArrayConstructor(len);
6053
6054             markers[0] = markers[len - 1] = 1;
6055
6056         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
6057
6058         var i,
6059             newPoints = [];
6060
6061         for (i = 0; i < len; i++) {
6062                 if (markers[i]) {
6063                         newPoints.push(points[i]);
6064                 }
6065         }
6066
6067         return newPoints;
6068 }
6069
6070 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6071
6072         var maxSqDist = 0,
6073         index, i, sqDist;
6074
6075         for (i = first + 1; i <= last - 1; i++) {
6076                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6077
6078                 if (sqDist > maxSqDist) {
6079                         index = i;
6080                         maxSqDist = sqDist;
6081                 }
6082         }
6083
6084         if (maxSqDist > sqTolerance) {
6085                 markers[index] = 1;
6086
6087                 _simplifyDPStep(points, markers, sqTolerance, first, index);
6088                 _simplifyDPStep(points, markers, sqTolerance, index, last);
6089         }
6090 }
6091
6092 // reduce points that are too close to each other to a single point
6093 function _reducePoints(points, sqTolerance) {
6094         var reducedPoints = [points[0]];
6095
6096         for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6097                 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6098                         reducedPoints.push(points[i]);
6099                         prev = i;
6100                 }
6101         }
6102         if (prev < len - 1) {
6103                 reducedPoints.push(points[len - 1]);
6104         }
6105         return reducedPoints;
6106 }
6107
6108 var _lastCode;
6109
6110 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6111 // Clips the segment a to b by rectangular bounds with the
6112 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6113 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6114 // points that are on the screen or near, increasing performance.
6115 function clipSegment(a, b, bounds, useLastCode, round) {
6116         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6117             codeB = _getBitCode(b, bounds),
6118
6119             codeOut, p, newCode;
6120
6121             // save 2nd code to avoid calculating it on the next segment
6122             _lastCode = codeB;
6123
6124         while (true) {
6125                 // if a,b is inside the clip window (trivial accept)
6126                 if (!(codeA | codeB)) {
6127                         return [a, b];
6128                 }
6129
6130                 // if a,b is outside the clip window (trivial reject)
6131                 if (codeA & codeB) {
6132                         return false;
6133                 }
6134
6135                 // other cases
6136                 codeOut = codeA || codeB;
6137                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6138                 newCode = _getBitCode(p, bounds);
6139
6140                 if (codeOut === codeA) {
6141                         a = p;
6142                         codeA = newCode;
6143                 } else {
6144                         b = p;
6145                         codeB = newCode;
6146                 }
6147         }
6148 }
6149
6150 function _getEdgeIntersection(a, b, code, bounds, round) {
6151         var dx = b.x - a.x,
6152             dy = b.y - a.y,
6153             min = bounds.min,
6154             max = bounds.max,
6155             x, y;
6156
6157         if (code & 8) { // top
6158                 x = a.x + dx * (max.y - a.y) / dy;
6159                 y = max.y;
6160
6161         } else if (code & 4) { // bottom
6162                 x = a.x + dx * (min.y - a.y) / dy;
6163                 y = min.y;
6164
6165         } else if (code & 2) { // right
6166                 x = max.x;
6167                 y = a.y + dy * (max.x - a.x) / dx;
6168
6169         } else if (code & 1) { // left
6170                 x = min.x;
6171                 y = a.y + dy * (min.x - a.x) / dx;
6172         }
6173
6174         return new Point(x, y, round);
6175 }
6176
6177 function _getBitCode(p, bounds) {
6178         var code = 0;
6179
6180         if (p.x < bounds.min.x) { // left
6181                 code |= 1;
6182         } else if (p.x > bounds.max.x) { // right
6183                 code |= 2;
6184         }
6185
6186         if (p.y < bounds.min.y) { // bottom
6187                 code |= 4;
6188         } else if (p.y > bounds.max.y) { // top
6189                 code |= 8;
6190         }
6191
6192         return code;
6193 }
6194
6195 // square distance (to avoid unnecessary Math.sqrt calls)
6196 function _sqDist(p1, p2) {
6197         var dx = p2.x - p1.x,
6198             dy = p2.y - p1.y;
6199         return dx * dx + dy * dy;
6200 }
6201
6202 // return closest point on segment or distance to that point
6203 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6204         var x = p1.x,
6205             y = p1.y,
6206             dx = p2.x - x,
6207             dy = p2.y - y,
6208             dot = dx * dx + dy * dy,
6209             t;
6210
6211         if (dot > 0) {
6212                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6213
6214                 if (t > 1) {
6215                         x = p2.x;
6216                         y = p2.y;
6217                 } else if (t > 0) {
6218                         x += dx * t;
6219                         y += dy * t;
6220                 }
6221         }
6222
6223         dx = p.x - x;
6224         dy = p.y - y;
6225
6226         return sqDist ? dx * dx + dy * dy : new Point(x, y);
6227 }
6228
6229
6230 // @function isFlat(latlngs: LatLng[]): Boolean
6231 // Returns true if `latlngs` is a flat array, false is nested.
6232 function isFlat(latlngs) {
6233         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6234 }
6235
6236 function _flat(latlngs) {
6237         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6238         return isFlat(latlngs);
6239 }
6240
6241
6242 var LineUtil = (Object.freeze || Object)({
6243         simplify: simplify,
6244         pointToSegmentDistance: pointToSegmentDistance,
6245         closestPointOnSegment: closestPointOnSegment,
6246         clipSegment: clipSegment,
6247         _getEdgeIntersection: _getEdgeIntersection,
6248         _getBitCode: _getBitCode,
6249         _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6250         isFlat: isFlat,
6251         _flat: _flat
6252 });
6253
6254 /*
6255  * @namespace PolyUtil
6256  * Various utility functions for polygon geometries.
6257  */
6258
6259 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6260  * 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)).
6261  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6262  * performance. Note that polygon points needs different algorithm for clipping
6263  * than polyline, so there's a separate method for it.
6264  */
6265 function clipPolygon(points, bounds, round) {
6266         var clippedPoints,
6267             edges = [1, 4, 2, 8],
6268             i, j, k,
6269             a, b,
6270             len, edge, p;
6271
6272         for (i = 0, len = points.length; i < len; i++) {
6273                 points[i]._code = _getBitCode(points[i], bounds);
6274         }
6275
6276         // for each edge (left, bottom, right, top)
6277         for (k = 0; k < 4; k++) {
6278                 edge = edges[k];
6279                 clippedPoints = [];
6280
6281                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6282                         a = points[i];
6283                         b = points[j];
6284
6285                         // if a is inside the clip window
6286                         if (!(a._code & edge)) {
6287                                 // if b is outside the clip window (a->b goes out of screen)
6288                                 if (b._code & edge) {
6289                                         p = _getEdgeIntersection(b, a, edge, bounds, round);
6290                                         p._code = _getBitCode(p, bounds);
6291                                         clippedPoints.push(p);
6292                                 }
6293                                 clippedPoints.push(a);
6294
6295                         // else if b is inside the clip window (a->b enters the screen)
6296                         } else if (!(b._code & edge)) {
6297                                 p = _getEdgeIntersection(b, a, edge, bounds, round);
6298                                 p._code = _getBitCode(p, bounds);
6299                                 clippedPoints.push(p);
6300                         }
6301                 }
6302                 points = clippedPoints;
6303         }
6304
6305         return points;
6306 }
6307
6308
6309 var PolyUtil = (Object.freeze || Object)({
6310         clipPolygon: clipPolygon
6311 });
6312
6313 /*
6314  * @namespace Projection
6315  * @section
6316  * Leaflet comes with a set of already defined Projections out of the box:
6317  *
6318  * @projection L.Projection.LonLat
6319  *
6320  * Equirectangular, or Plate Carree projection — the most simple projection,
6321  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6322  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6323  * `EPSG:4326` and `Simple` CRS.
6324  */
6325
6326 var LonLat = {
6327         project: function (latlng) {
6328                 return new Point(latlng.lng, latlng.lat);
6329         },
6330
6331         unproject: function (point) {
6332                 return new LatLng(point.y, point.x);
6333         },
6334
6335         bounds: new Bounds([-180, -90], [180, 90])
6336 };
6337
6338 /*
6339  * @namespace Projection
6340  * @projection L.Projection.Mercator
6341  *
6342  * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
6343  */
6344
6345 var Mercator = {
6346         R: 6378137,
6347         R_MINOR: 6356752.314245179,
6348
6349         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6350
6351         project: function (latlng) {
6352                 var d = Math.PI / 180,
6353                     r = this.R,
6354                     y = latlng.lat * d,
6355                     tmp = this.R_MINOR / r,
6356                     e = Math.sqrt(1 - tmp * tmp),
6357                     con = e * Math.sin(y);
6358
6359                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6360                 y = -r * Math.log(Math.max(ts, 1E-10));
6361
6362                 return new Point(latlng.lng * d * r, y);
6363         },
6364
6365         unproject: function (point) {
6366                 var d = 180 / Math.PI,
6367                     r = this.R,
6368                     tmp = this.R_MINOR / r,
6369                     e = Math.sqrt(1 - tmp * tmp),
6370                     ts = Math.exp(-point.y / r),
6371                     phi = Math.PI / 2 - 2 * Math.atan(ts);
6372
6373                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6374                         con = e * Math.sin(phi);
6375                         con = Math.pow((1 - con) / (1 + con), e / 2);
6376                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6377                         phi += dphi;
6378                 }
6379
6380                 return new LatLng(phi * d, point.x * d / r);
6381         }
6382 };
6383
6384 /*
6385  * @class Projection
6386
6387  * An object with methods for projecting geographical coordinates of the world onto
6388  * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6389
6390  * @property bounds: Bounds
6391  * The bounds (specified in CRS units) where the projection is valid
6392
6393  * @method project(latlng: LatLng): Point
6394  * Projects geographical coordinates into a 2D point.
6395  * Only accepts actual `L.LatLng` instances, not arrays.
6396
6397  * @method unproject(point: Point): LatLng
6398  * The inverse of `project`. Projects a 2D point into a geographical location.
6399  * Only accepts actual `L.Point` instances, not arrays.
6400
6401  * Note that the projection instances do not inherit from Leafet's `Class` object,
6402  * and can't be instantiated. Also, new classes can't inherit from them,
6403  * and methods can't be added to them with the `include` function.
6404
6405  */
6406
6407
6408
6409
6410 var index = (Object.freeze || Object)({
6411         LonLat: LonLat,
6412         Mercator: Mercator,
6413         SphericalMercator: SphericalMercator
6414 });
6415
6416 /*
6417  * @namespace CRS
6418  * @crs L.CRS.EPSG3395
6419  *
6420  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6421  */
6422 var EPSG3395 = extend({}, Earth, {
6423         code: 'EPSG:3395',
6424         projection: Mercator,
6425
6426         transformation: (function () {
6427                 var scale = 0.5 / (Math.PI * Mercator.R);
6428                 return toTransformation(scale, 0.5, -scale, 0.5);
6429         }())
6430 });
6431
6432 /*
6433  * @namespace CRS
6434  * @crs L.CRS.EPSG4326
6435  *
6436  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6437  *
6438  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6439  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
6440  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6441  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6442  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6443  */
6444
6445 var EPSG4326 = extend({}, Earth, {
6446         code: 'EPSG:4326',
6447         projection: LonLat,
6448         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6449 });
6450
6451 /*
6452  * @namespace CRS
6453  * @crs L.CRS.Simple
6454  *
6455  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6456  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6457  * axis should still be inverted (going from bottom to top). `distance()` returns
6458  * simple euclidean distance.
6459  */
6460
6461 var Simple = extend({}, CRS, {
6462         projection: LonLat,
6463         transformation: toTransformation(1, 0, -1, 0),
6464
6465         scale: function (zoom) {
6466                 return Math.pow(2, zoom);
6467         },
6468
6469         zoom: function (scale) {
6470                 return Math.log(scale) / Math.LN2;
6471         },
6472
6473         distance: function (latlng1, latlng2) {
6474                 var dx = latlng2.lng - latlng1.lng,
6475                     dy = latlng2.lat - latlng1.lat;
6476
6477                 return Math.sqrt(dx * dx + dy * dy);
6478         },
6479
6480         infinite: true
6481 });
6482
6483 CRS.Earth = Earth;
6484 CRS.EPSG3395 = EPSG3395;
6485 CRS.EPSG3857 = EPSG3857;
6486 CRS.EPSG900913 = EPSG900913;
6487 CRS.EPSG4326 = EPSG4326;
6488 CRS.Simple = Simple;
6489
6490 /*
6491  * @class Layer
6492  * @inherits Evented
6493  * @aka L.Layer
6494  * @aka ILayer
6495  *
6496  * A set of methods from the Layer base class that all Leaflet layers use.
6497  * Inherits all methods, options and events from `L.Evented`.
6498  *
6499  * @example
6500  *
6501  * ```js
6502  * var layer = L.marker(latlng).addTo(map);
6503  * layer.addTo(map);
6504  * layer.remove();
6505  * ```
6506  *
6507  * @event add: Event
6508  * Fired after the layer is added to a map
6509  *
6510  * @event remove: Event
6511  * Fired after the layer is removed from a map
6512  */
6513
6514
6515 var Layer = Evented.extend({
6516
6517         // Classes extending `L.Layer` will inherit the following options:
6518         options: {
6519                 // @option pane: String = 'overlayPane'
6520                 // 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.
6521                 pane: 'overlayPane',
6522
6523                 // @option attribution: String = null
6524                 // 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.
6525                 attribution: null,
6526
6527                 bubblingMouseEvents: true
6528         },
6529
6530         /* @section
6531          * Classes extending `L.Layer` will inherit the following methods:
6532          *
6533          * @method addTo(map: Map|LayerGroup): this
6534          * Adds the layer to the given map or layer group.
6535          */
6536         addTo: function (map) {
6537                 map.addLayer(this);
6538                 return this;
6539         },
6540
6541         // @method remove: this
6542         // Removes the layer from the map it is currently active on.
6543         remove: function () {
6544                 return this.removeFrom(this._map || this._mapToAdd);
6545         },
6546
6547         // @method removeFrom(map: Map): this
6548         // Removes the layer from the given map
6549         removeFrom: function (obj) {
6550                 if (obj) {
6551                         obj.removeLayer(this);
6552                 }
6553                 return this;
6554         },
6555
6556         // @method getPane(name? : String): HTMLElement
6557         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6558         getPane: function (name) {
6559                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6560         },
6561
6562         addInteractiveTarget: function (targetEl) {
6563                 this._map._targets[stamp(targetEl)] = this;
6564                 return this;
6565         },
6566
6567         removeInteractiveTarget: function (targetEl) {
6568                 delete this._map._targets[stamp(targetEl)];
6569                 return this;
6570         },
6571
6572         // @method getAttribution: String
6573         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6574         getAttribution: function () {
6575                 return this.options.attribution;
6576         },
6577
6578         _layerAdd: function (e) {
6579                 var map = e.target;
6580
6581                 // check in case layer gets added and then removed before the map is ready
6582                 if (!map.hasLayer(this)) { return; }
6583
6584                 this._map = map;
6585                 this._zoomAnimated = map._zoomAnimated;
6586
6587                 if (this.getEvents) {
6588                         var events = this.getEvents();
6589                         map.on(events, this);
6590                         this.once('remove', function () {
6591                                 map.off(events, this);
6592                         }, this);
6593                 }
6594
6595                 this.onAdd(map);
6596
6597                 if (this.getAttribution && map.attributionControl) {
6598                         map.attributionControl.addAttribution(this.getAttribution());
6599                 }
6600
6601                 this.fire('add');
6602                 map.fire('layeradd', {layer: this});
6603         }
6604 });
6605
6606 /* @section Extension methods
6607  * @uninheritable
6608  *
6609  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6610  *
6611  * @method onAdd(map: Map): this
6612  * 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).
6613  *
6614  * @method onRemove(map: Map): this
6615  * 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).
6616  *
6617  * @method getEvents(): Object
6618  * 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.
6619  *
6620  * @method getAttribution(): String
6621  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6622  *
6623  * @method beforeAdd(map: Map): this
6624  * 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.
6625  */
6626
6627
6628 /* @namespace Map
6629  * @section Layer events
6630  *
6631  * @event layeradd: LayerEvent
6632  * Fired when a new layer is added to the map.
6633  *
6634  * @event layerremove: LayerEvent
6635  * Fired when some layer is removed from the map
6636  *
6637  * @section Methods for Layers and Controls
6638  */
6639 Map.include({
6640         // @method addLayer(layer: Layer): this
6641         // Adds the given layer to the map
6642         addLayer: function (layer) {
6643                 if (!layer._layerAdd) {
6644                         throw new Error('The provided object is not a Layer.');
6645                 }
6646
6647                 var id = stamp(layer);
6648                 if (this._layers[id]) { return this; }
6649                 this._layers[id] = layer;
6650
6651                 layer._mapToAdd = this;
6652
6653                 if (layer.beforeAdd) {
6654                         layer.beforeAdd(this);
6655                 }
6656
6657                 this.whenReady(layer._layerAdd, layer);
6658
6659                 return this;
6660         },
6661
6662         // @method removeLayer(layer: Layer): this
6663         // Removes the given layer from the map.
6664         removeLayer: function (layer) {
6665                 var id = stamp(layer);
6666
6667                 if (!this._layers[id]) { return this; }
6668
6669                 if (this._loaded) {
6670                         layer.onRemove(this);
6671                 }
6672
6673                 if (layer.getAttribution && this.attributionControl) {
6674                         this.attributionControl.removeAttribution(layer.getAttribution());
6675                 }
6676
6677                 delete this._layers[id];
6678
6679                 if (this._loaded) {
6680                         this.fire('layerremove', {layer: layer});
6681                         layer.fire('remove');
6682                 }
6683
6684                 layer._map = layer._mapToAdd = null;
6685
6686                 return this;
6687         },
6688
6689         // @method hasLayer(layer: Layer): Boolean
6690         // Returns `true` if the given layer is currently added to the map
6691         hasLayer: function (layer) {
6692                 return !!layer && (stamp(layer) in this._layers);
6693         },
6694
6695         /* @method eachLayer(fn: Function, context?: Object): this
6696          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6697          * ```
6698          * map.eachLayer(function(layer){
6699          *     layer.bindPopup('Hello');
6700          * });
6701          * ```
6702          */
6703         eachLayer: function (method, context) {
6704                 for (var i in this._layers) {
6705                         method.call(context, this._layers[i]);
6706                 }
6707                 return this;
6708         },
6709
6710         _addLayers: function (layers) {
6711                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6712
6713                 for (var i = 0, len = layers.length; i < len; i++) {
6714                         this.addLayer(layers[i]);
6715                 }
6716         },
6717
6718         _addZoomLimit: function (layer) {
6719                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6720                         this._zoomBoundLayers[stamp(layer)] = layer;
6721                         this._updateZoomLevels();
6722                 }
6723         },
6724
6725         _removeZoomLimit: function (layer) {
6726                 var id = stamp(layer);
6727
6728                 if (this._zoomBoundLayers[id]) {
6729                         delete this._zoomBoundLayers[id];
6730                         this._updateZoomLevels();
6731                 }
6732         },
6733
6734         _updateZoomLevels: function () {
6735                 var minZoom = Infinity,
6736                     maxZoom = -Infinity,
6737                     oldZoomSpan = this._getZoomSpan();
6738
6739                 for (var i in this._zoomBoundLayers) {
6740                         var options = this._zoomBoundLayers[i].options;
6741
6742                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6743                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6744                 }
6745
6746                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6747                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6748
6749                 // @section Map state change events
6750                 // @event zoomlevelschange: Event
6751                 // Fired when the number of zoomlevels on the map is changed due
6752                 // to adding or removing a layer.
6753                 if (oldZoomSpan !== this._getZoomSpan()) {
6754                         this.fire('zoomlevelschange');
6755                 }
6756
6757                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6758                         this.setZoom(this._layersMaxZoom);
6759                 }
6760                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6761                         this.setZoom(this._layersMinZoom);
6762                 }
6763         }
6764 });
6765
6766 /*
6767  * @class LayerGroup
6768  * @aka L.LayerGroup
6769  * @inherits Layer
6770  *
6771  * Used to group several layers and handle them as one. If you add it to the map,
6772  * any layers added or removed from the group will be added/removed on the map as
6773  * well. Extends `Layer`.
6774  *
6775  * @example
6776  *
6777  * ```js
6778  * L.layerGroup([marker1, marker2])
6779  *      .addLayer(polyline)
6780  *      .addTo(map);
6781  * ```
6782  */
6783
6784 var LayerGroup = Layer.extend({
6785
6786         initialize: function (layers, options) {
6787                 setOptions(this, options);
6788
6789                 this._layers = {};
6790
6791                 var i, len;
6792
6793                 if (layers) {
6794                         for (i = 0, len = layers.length; i < len; i++) {
6795                                 this.addLayer(layers[i]);
6796                         }
6797                 }
6798         },
6799
6800         // @method addLayer(layer: Layer): this
6801         // Adds the given layer to the group.
6802         addLayer: function (layer) {
6803                 var id = this.getLayerId(layer);
6804
6805                 this._layers[id] = layer;
6806
6807                 if (this._map) {
6808                         this._map.addLayer(layer);
6809                 }
6810
6811                 return this;
6812         },
6813
6814         // @method removeLayer(layer: Layer): this
6815         // Removes the given layer from the group.
6816         // @alternative
6817         // @method removeLayer(id: Number): this
6818         // Removes the layer with the given internal ID from the group.
6819         removeLayer: function (layer) {
6820                 var id = layer in this._layers ? layer : this.getLayerId(layer);
6821
6822                 if (this._map && this._layers[id]) {
6823                         this._map.removeLayer(this._layers[id]);
6824                 }
6825
6826                 delete this._layers[id];
6827
6828                 return this;
6829         },
6830
6831         // @method hasLayer(layer: Layer): Boolean
6832         // Returns `true` if the given layer is currently added to the group.
6833         // @alternative
6834         // @method hasLayer(id: Number): Boolean
6835         // Returns `true` if the given internal ID is currently added to the group.
6836         hasLayer: function (layer) {
6837                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6838         },
6839
6840         // @method clearLayers(): this
6841         // Removes all the layers from the group.
6842         clearLayers: function () {
6843                 return this.eachLayer(this.removeLayer, this);
6844         },
6845
6846         // @method invoke(methodName: String, …): this
6847         // Calls `methodName` on every layer contained in this group, passing any
6848         // additional parameters. Has no effect if the layers contained do not
6849         // implement `methodName`.
6850         invoke: function (methodName) {
6851                 var args = Array.prototype.slice.call(arguments, 1),
6852                     i, layer;
6853
6854                 for (i in this._layers) {
6855                         layer = this._layers[i];
6856
6857                         if (layer[methodName]) {
6858                                 layer[methodName].apply(layer, args);
6859                         }
6860                 }
6861
6862                 return this;
6863         },
6864
6865         onAdd: function (map) {
6866                 this.eachLayer(map.addLayer, map);
6867         },
6868
6869         onRemove: function (map) {
6870                 this.eachLayer(map.removeLayer, map);
6871         },
6872
6873         // @method eachLayer(fn: Function, context?: Object): this
6874         // Iterates over the layers of the group, optionally specifying context of the iterator function.
6875         // ```js
6876         // group.eachLayer(function (layer) {
6877         //      layer.bindPopup('Hello');
6878         // });
6879         // ```
6880         eachLayer: function (method, context) {
6881                 for (var i in this._layers) {
6882                         method.call(context, this._layers[i]);
6883                 }
6884                 return this;
6885         },
6886
6887         // @method getLayer(id: Number): Layer
6888         // Returns the layer with the given internal ID.
6889         getLayer: function (id) {
6890                 return this._layers[id];
6891         },
6892
6893         // @method getLayers(): Layer[]
6894         // Returns an array of all the layers added to the group.
6895         getLayers: function () {
6896                 var layers = [];
6897                 this.eachLayer(layers.push, layers);
6898                 return layers;
6899         },
6900
6901         // @method setZIndex(zIndex: Number): this
6902         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6903         setZIndex: function (zIndex) {
6904                 return this.invoke('setZIndex', zIndex);
6905         },
6906
6907         // @method getLayerId(layer: Layer): Number
6908         // Returns the internal ID for a layer
6909         getLayerId: function (layer) {
6910                 return stamp(layer);
6911         }
6912 });
6913
6914
6915 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6916 // Create a layer group, optionally given an initial set of layers and an `options` object.
6917 var layerGroup = function (layers, options) {
6918         return new LayerGroup(layers, options);
6919 };
6920
6921 /*
6922  * @class FeatureGroup
6923  * @aka L.FeatureGroup
6924  * @inherits LayerGroup
6925  *
6926  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6927  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6928  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
6929  * handler, it will handle events from any of the layers. This includes mouse events
6930  * and custom events.
6931  *  * Has `layeradd` and `layerremove` events
6932  *
6933  * @example
6934  *
6935  * ```js
6936  * L.featureGroup([marker1, marker2, polyline])
6937  *      .bindPopup('Hello world!')
6938  *      .on('click', function() { alert('Clicked on a member of the group!'); })
6939  *      .addTo(map);
6940  * ```
6941  */
6942
6943 var FeatureGroup = LayerGroup.extend({
6944
6945         addLayer: function (layer) {
6946                 if (this.hasLayer(layer)) {
6947                         return this;
6948                 }
6949
6950                 layer.addEventParent(this);
6951
6952                 LayerGroup.prototype.addLayer.call(this, layer);
6953
6954                 // @event layeradd: LayerEvent
6955                 // Fired when a layer is added to this `FeatureGroup`
6956                 return this.fire('layeradd', {layer: layer});
6957         },
6958
6959         removeLayer: function (layer) {
6960                 if (!this.hasLayer(layer)) {
6961                         return this;
6962                 }
6963                 if (layer in this._layers) {
6964                         layer = this._layers[layer];
6965                 }
6966
6967                 layer.removeEventParent(this);
6968
6969                 LayerGroup.prototype.removeLayer.call(this, layer);
6970
6971                 // @event layerremove: LayerEvent
6972                 // Fired when a layer is removed from this `FeatureGroup`
6973                 return this.fire('layerremove', {layer: layer});
6974         },
6975
6976         // @method setStyle(style: Path options): this
6977         // Sets the given path options to each layer of the group that has a `setStyle` method.
6978         setStyle: function (style) {
6979                 return this.invoke('setStyle', style);
6980         },
6981
6982         // @method bringToFront(): this
6983         // Brings the layer group to the top of all other layers
6984         bringToFront: function () {
6985                 return this.invoke('bringToFront');
6986         },
6987
6988         // @method bringToBack(): this
6989         // Brings the layer group to the back of all other layers
6990         bringToBack: function () {
6991                 return this.invoke('bringToBack');
6992         },
6993
6994         // @method getBounds(): LatLngBounds
6995         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6996         getBounds: function () {
6997                 var bounds = new LatLngBounds();
6998
6999                 for (var id in this._layers) {
7000                         var layer = this._layers[id];
7001                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7002                 }
7003                 return bounds;
7004         }
7005 });
7006
7007 // @factory L.featureGroup(layers: Layer[])
7008 // Create a feature group, optionally given an initial set of layers.
7009 var featureGroup = function (layers) {
7010         return new FeatureGroup(layers);
7011 };
7012
7013 /*
7014  * @class Icon
7015  * @aka L.Icon
7016  *
7017  * Represents an icon to provide when creating a marker.
7018  *
7019  * @example
7020  *
7021  * ```js
7022  * var myIcon = L.icon({
7023  *     iconUrl: 'my-icon.png',
7024  *     iconRetinaUrl: 'my-icon@2x.png',
7025  *     iconSize: [38, 95],
7026  *     iconAnchor: [22, 94],
7027  *     popupAnchor: [-3, -76],
7028  *     shadowUrl: 'my-icon-shadow.png',
7029  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
7030  *     shadowSize: [68, 95],
7031  *     shadowAnchor: [22, 94]
7032  * });
7033  *
7034  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
7035  * ```
7036  *
7037  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
7038  *
7039  */
7040
7041 var Icon = Class.extend({
7042
7043         /* @section
7044          * @aka Icon options
7045          *
7046          * @option iconUrl: String = null
7047          * **(required)** The URL to the icon image (absolute or relative to your script path).
7048          *
7049          * @option iconRetinaUrl: String = null
7050          * The URL to a retina sized version of the icon image (absolute or relative to your
7051          * script path). Used for Retina screen devices.
7052          *
7053          * @option iconSize: Point = null
7054          * Size of the icon image in pixels.
7055          *
7056          * @option iconAnchor: Point = null
7057          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
7058          * will be aligned so that this point is at the marker's geographical location. Centered
7059          * by default if size is specified, also can be set in CSS with negative margins.
7060          *
7061          * @option popupAnchor: Point = [0, 0]
7062          * The coordinates of the point from which popups will "open", relative to the icon anchor.
7063          *
7064          * @option tooltipAnchor: Point = [0, 0]
7065          * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7066          *
7067          * @option shadowUrl: String = null
7068          * The URL to the icon shadow image. If not specified, no shadow image will be created.
7069          *
7070          * @option shadowRetinaUrl: String = null
7071          *
7072          * @option shadowSize: Point = null
7073          * Size of the shadow image in pixels.
7074          *
7075          * @option shadowAnchor: Point = null
7076          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7077          * as iconAnchor if not specified).
7078          *
7079          * @option className: String = ''
7080          * A custom class name to assign to both icon and shadow images. Empty by default.
7081          */
7082
7083         options: {
7084                 popupAnchor: [0, 0],
7085                 tooltipAnchor: [0, 0]
7086         },
7087
7088         initialize: function (options) {
7089                 setOptions(this, options);
7090         },
7091
7092         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7093         // Called internally when the icon has to be shown, returns a `<img>` HTML element
7094         // styled according to the options.
7095         createIcon: function (oldIcon) {
7096                 return this._createIcon('icon', oldIcon);
7097         },
7098
7099         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7100         // As `createIcon`, but for the shadow beneath it.
7101         createShadow: function (oldIcon) {
7102                 return this._createIcon('shadow', oldIcon);
7103         },
7104
7105         _createIcon: function (name, oldIcon) {
7106                 var src = this._getIconUrl(name);
7107
7108                 if (!src) {
7109                         if (name === 'icon') {
7110                                 throw new Error('iconUrl not set in Icon options (see the docs).');
7111                         }
7112                         return null;
7113                 }
7114
7115                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7116                 this._setIconStyles(img, name);
7117
7118                 return img;
7119         },
7120
7121         _setIconStyles: function (img, name) {
7122                 var options = this.options;
7123                 var sizeOption = options[name + 'Size'];
7124
7125                 if (typeof sizeOption === 'number') {
7126                         sizeOption = [sizeOption, sizeOption];
7127                 }
7128
7129                 var size = toPoint(sizeOption),
7130                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7131                             size && size.divideBy(2, true));
7132
7133                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7134
7135                 if (anchor) {
7136                         img.style.marginLeft = (-anchor.x) + 'px';
7137                         img.style.marginTop  = (-anchor.y) + 'px';
7138                 }
7139
7140                 if (size) {
7141                         img.style.width  = size.x + 'px';
7142                         img.style.height = size.y + 'px';
7143                 }
7144         },
7145
7146         _createImg: function (src, el) {
7147                 el = el || document.createElement('img');
7148                 el.src = src;
7149                 return el;
7150         },
7151
7152         _getIconUrl: function (name) {
7153                 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7154         }
7155 });
7156
7157
7158 // @factory L.icon(options: Icon options)
7159 // Creates an icon instance with the given options.
7160 function icon(options) {
7161         return new Icon(options);
7162 }
7163
7164 /*
7165  * @miniclass Icon.Default (Icon)
7166  * @aka L.Icon.Default
7167  * @section
7168  *
7169  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7170  * no icon is specified. Points to the blue marker image distributed with Leaflet
7171  * releases.
7172  *
7173  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7174  * (which is a set of `Icon options`).
7175  *
7176  * If you want to _completely_ replace the default icon, override the
7177  * `L.Marker.prototype.options.icon` with your own icon instead.
7178  */
7179
7180 var IconDefault = Icon.extend({
7181
7182         options: {
7183                 iconUrl:       'marker-icon.png',
7184                 iconRetinaUrl: 'marker-icon-2x.png',
7185                 shadowUrl:     'marker-shadow.png',
7186                 iconSize:    [25, 41],
7187                 iconAnchor:  [12, 41],
7188                 popupAnchor: [1, -34],
7189                 tooltipAnchor: [16, -28],
7190                 shadowSize:  [41, 41]
7191         },
7192
7193         _getIconUrl: function (name) {
7194                 if (!IconDefault.imagePath) {   // Deprecated, backwards-compatibility only
7195                         IconDefault.imagePath = this._detectIconPath();
7196                 }
7197
7198                 // @option imagePath: String
7199                 // `Icon.Default` will try to auto-detect the location of the
7200                 // blue icon images. If you are placing these images in a non-standard
7201                 // way, set this option to point to the right path.
7202                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7203         },
7204
7205         _detectIconPath: function () {
7206                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7207                 var path = getStyle(el, 'background-image') ||
7208                            getStyle(el, 'backgroundImage');     // IE8
7209
7210                 document.body.removeChild(el);
7211
7212                 if (path === null || path.indexOf('url') !== 0) {
7213                         path = '';
7214                 } else {
7215                         path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7216                 }
7217
7218                 return path;
7219         }
7220 });
7221
7222 /*
7223  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7224  */
7225
7226
7227 /* @namespace Marker
7228  * @section Interaction handlers
7229  *
7230  * 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:
7231  *
7232  * ```js
7233  * marker.dragging.disable();
7234  * ```
7235  *
7236  * @property dragging: Handler
7237  * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7238  */
7239
7240 var MarkerDrag = Handler.extend({
7241         initialize: function (marker) {
7242                 this._marker = marker;
7243         },
7244
7245         addHooks: function () {
7246                 var icon = this._marker._icon;
7247
7248                 if (!this._draggable) {
7249                         this._draggable = new Draggable(icon, icon, true);
7250                 }
7251
7252                 this._draggable.on({
7253                         dragstart: this._onDragStart,
7254                         predrag: this._onPreDrag,
7255                         drag: this._onDrag,
7256                         dragend: this._onDragEnd
7257                 }, this).enable();
7258
7259                 addClass(icon, 'leaflet-marker-draggable');
7260         },
7261
7262         removeHooks: function () {
7263                 this._draggable.off({
7264                         dragstart: this._onDragStart,
7265                         predrag: this._onPreDrag,
7266                         drag: this._onDrag,
7267                         dragend: this._onDragEnd
7268                 }, this).disable();
7269
7270                 if (this._marker._icon) {
7271                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7272                 }
7273         },
7274
7275         moved: function () {
7276                 return this._draggable && this._draggable._moved;
7277         },
7278
7279         _adjustPan: function (e) {
7280                 var marker = this._marker,
7281                     map = marker._map,
7282                     speed = this._marker.options.autoPanSpeed,
7283                     padding = this._marker.options.autoPanPadding,
7284                     iconPos = getPosition(marker._icon),
7285                     bounds = map.getPixelBounds(),
7286                     origin = map.getPixelOrigin();
7287
7288                 var panBounds = toBounds(
7289                         bounds.min._subtract(origin).add(padding),
7290                         bounds.max._subtract(origin).subtract(padding)
7291                 );
7292
7293                 if (!panBounds.contains(iconPos)) {
7294                         // Compute incremental movement
7295                         var movement = toPoint(
7296                                 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7297                                 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7298
7299                                 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7300                                 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7301                         ).multiplyBy(speed);
7302
7303                         map.panBy(movement, {animate: false});
7304
7305                         this._draggable._newPos._add(movement);
7306                         this._draggable._startPos._add(movement);
7307
7308                         setPosition(marker._icon, this._draggable._newPos);
7309                         this._onDrag(e);
7310
7311                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7312                 }
7313         },
7314
7315         _onDragStart: function () {
7316                 // @section Dragging events
7317                 // @event dragstart: Event
7318                 // Fired when the user starts dragging the marker.
7319
7320                 // @event movestart: Event
7321                 // Fired when the marker starts moving (because of dragging).
7322
7323                 this._oldLatLng = this._marker.getLatLng();
7324                 this._marker
7325                     .closePopup()
7326                     .fire('movestart')
7327                     .fire('dragstart');
7328         },
7329
7330         _onPreDrag: function (e) {
7331                 if (this._marker.options.autoPan) {
7332                         cancelAnimFrame(this._panRequest);
7333                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7334                 }
7335         },
7336
7337         _onDrag: function (e) {
7338                 var marker = this._marker,
7339                     shadow = marker._shadow,
7340                     iconPos = getPosition(marker._icon),
7341                     latlng = marker._map.layerPointToLatLng(iconPos);
7342
7343                 // update shadow position
7344                 if (shadow) {
7345                         setPosition(shadow, iconPos);
7346                 }
7347
7348                 marker._latlng = latlng;
7349                 e.latlng = latlng;
7350                 e.oldLatLng = this._oldLatLng;
7351
7352                 // @event drag: Event
7353                 // Fired repeatedly while the user drags the marker.
7354                 marker
7355                     .fire('move', e)
7356                     .fire('drag', e);
7357         },
7358
7359         _onDragEnd: function (e) {
7360                 // @event dragend: DragEndEvent
7361                 // Fired when the user stops dragging the marker.
7362
7363                  cancelAnimFrame(this._panRequest);
7364
7365                 // @event moveend: Event
7366                 // Fired when the marker stops moving (because of dragging).
7367                 delete this._oldLatLng;
7368                 this._marker
7369                     .fire('moveend')
7370                     .fire('dragend', e);
7371         }
7372 });
7373
7374 /*
7375  * @class Marker
7376  * @inherits Interactive layer
7377  * @aka L.Marker
7378  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7379  *
7380  * @example
7381  *
7382  * ```js
7383  * L.marker([50.5, 30.5]).addTo(map);
7384  * ```
7385  */
7386
7387 var Marker = Layer.extend({
7388
7389         // @section
7390         // @aka Marker options
7391         options: {
7392                 // @option icon: Icon = *
7393                 // Icon instance to use for rendering the marker.
7394                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7395                 // If not specified, a common instance of `L.Icon.Default` is used.
7396                 icon: new IconDefault(),
7397
7398                 // Option inherited from "Interactive layer" abstract class
7399                 interactive: true,
7400
7401                 // @option keyboard: Boolean = true
7402                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7403                 keyboard: true,
7404
7405                 // @option title: String = ''
7406                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7407                 title: '',
7408
7409                 // @option alt: String = ''
7410                 // Text for the `alt` attribute of the icon image (useful for accessibility).
7411                 alt: '',
7412
7413                 // @option zIndexOffset: Number = 0
7414                 // 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).
7415                 zIndexOffset: 0,
7416
7417                 // @option opacity: Number = 1.0
7418                 // The opacity of the marker.
7419                 opacity: 1,
7420
7421                 // @option riseOnHover: Boolean = false
7422                 // If `true`, the marker will get on top of others when you hover the mouse over it.
7423                 riseOnHover: false,
7424
7425                 // @option riseOffset: Number = 250
7426                 // The z-index offset used for the `riseOnHover` feature.
7427                 riseOffset: 250,
7428
7429                 // @option pane: String = 'markerPane'
7430                 // `Map pane` where the markers icon will be added.
7431                 pane: 'markerPane',
7432
7433                 // @option pane: String = 'shadowPane'
7434                 // `Map pane` where the markers shadow will be added.
7435                 shadowPane: 'shadowPane',
7436
7437                 // @option bubblingMouseEvents: Boolean = false
7438                 // When `true`, a mouse event on this marker will trigger the same event on the map
7439                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7440                 bubblingMouseEvents: false,
7441
7442                 // @section Draggable marker options
7443                 // @option draggable: Boolean = false
7444                 // Whether the marker is draggable with mouse/touch or not.
7445                 draggable: false,
7446
7447                 // @option autoPan: Boolean = false
7448                 // Whether to pan the map when dragging this marker near its edge or not.
7449                 autoPan: false,
7450
7451                 // @option autoPanPadding: Point = Point(50, 50)
7452                 // Distance (in pixels to the left/right and to the top/bottom) of the
7453                 // map edge to start panning the map.
7454                 autoPanPadding: [50, 50],
7455
7456                 // @option autoPanSpeed: Number = 10
7457                 // Number of pixels the map should pan by.
7458                 autoPanSpeed: 10
7459         },
7460
7461         /* @section
7462          *
7463          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7464          */
7465
7466         initialize: function (latlng, options) {
7467                 setOptions(this, options);
7468                 this._latlng = toLatLng(latlng);
7469         },
7470
7471         onAdd: function (map) {
7472                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7473
7474                 if (this._zoomAnimated) {
7475                         map.on('zoomanim', this._animateZoom, this);
7476                 }
7477
7478                 this._initIcon();
7479                 this.update();
7480         },
7481
7482         onRemove: function (map) {
7483                 if (this.dragging && this.dragging.enabled()) {
7484                         this.options.draggable = true;
7485                         this.dragging.removeHooks();
7486                 }
7487                 delete this.dragging;
7488
7489                 if (this._zoomAnimated) {
7490                         map.off('zoomanim', this._animateZoom, this);
7491                 }
7492
7493                 this._removeIcon();
7494                 this._removeShadow();
7495         },
7496
7497         getEvents: function () {
7498                 return {
7499                         zoom: this.update,
7500                         viewreset: this.update
7501                 };
7502         },
7503
7504         // @method getLatLng: LatLng
7505         // Returns the current geographical position of the marker.
7506         getLatLng: function () {
7507                 return this._latlng;
7508         },
7509
7510         // @method setLatLng(latlng: LatLng): this
7511         // Changes the marker position to the given point.
7512         setLatLng: function (latlng) {
7513                 var oldLatLng = this._latlng;
7514                 this._latlng = toLatLng(latlng);
7515                 this.update();
7516
7517                 // @event move: Event
7518                 // 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`.
7519                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7520         },
7521
7522         // @method setZIndexOffset(offset: Number): this
7523         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7524         setZIndexOffset: function (offset) {
7525                 this.options.zIndexOffset = offset;
7526                 return this.update();
7527         },
7528
7529         // @method getIcon: Icon
7530         // Returns the current icon used by the marker
7531         getIcon: function () {
7532                 return this.options.icon;
7533         },
7534
7535         // @method setIcon(icon: Icon): this
7536         // Changes the marker icon.
7537         setIcon: function (icon) {
7538
7539                 this.options.icon = icon;
7540
7541                 if (this._map) {
7542                         this._initIcon();
7543                         this.update();
7544                 }
7545
7546                 if (this._popup) {
7547                         this.bindPopup(this._popup, this._popup.options);
7548                 }
7549
7550                 return this;
7551         },
7552
7553         getElement: function () {
7554                 return this._icon;
7555         },
7556
7557         update: function () {
7558
7559                 if (this._icon && this._map) {
7560                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
7561                         this._setPos(pos);
7562                 }
7563
7564                 return this;
7565         },
7566
7567         _initIcon: function () {
7568                 var options = this.options,
7569                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7570
7571                 var icon = options.icon.createIcon(this._icon),
7572                     addIcon = false;
7573
7574                 // if we're not reusing the icon, remove the old one and init new one
7575                 if (icon !== this._icon) {
7576                         if (this._icon) {
7577                                 this._removeIcon();
7578                         }
7579                         addIcon = true;
7580
7581                         if (options.title) {
7582                                 icon.title = options.title;
7583                         }
7584
7585                         if (icon.tagName === 'IMG') {
7586                                 icon.alt = options.alt || '';
7587                         }
7588                 }
7589
7590                 addClass(icon, classToAdd);
7591
7592                 if (options.keyboard) {
7593                         icon.tabIndex = '0';
7594                 }
7595
7596                 this._icon = icon;
7597
7598                 if (options.riseOnHover) {
7599                         this.on({
7600                                 mouseover: this._bringToFront,
7601                                 mouseout: this._resetZIndex
7602                         });
7603                 }
7604
7605                 var newShadow = options.icon.createShadow(this._shadow),
7606                     addShadow = false;
7607
7608                 if (newShadow !== this._shadow) {
7609                         this._removeShadow();
7610                         addShadow = true;
7611                 }
7612
7613                 if (newShadow) {
7614                         addClass(newShadow, classToAdd);
7615                         newShadow.alt = '';
7616                 }
7617                 this._shadow = newShadow;
7618
7619
7620                 if (options.opacity < 1) {
7621                         this._updateOpacity();
7622                 }
7623
7624
7625                 if (addIcon) {
7626                         this.getPane().appendChild(this._icon);
7627                 }
7628                 this._initInteraction();
7629                 if (newShadow && addShadow) {
7630                         this.getPane(options.shadowPane).appendChild(this._shadow);
7631                 }
7632         },
7633
7634         _removeIcon: function () {
7635                 if (this.options.riseOnHover) {
7636                         this.off({
7637                                 mouseover: this._bringToFront,
7638                                 mouseout: this._resetZIndex
7639                         });
7640                 }
7641
7642                 remove(this._icon);
7643                 this.removeInteractiveTarget(this._icon);
7644
7645                 this._icon = null;
7646         },
7647
7648         _removeShadow: function () {
7649                 if (this._shadow) {
7650                         remove(this._shadow);
7651                 }
7652                 this._shadow = null;
7653         },
7654
7655         _setPos: function (pos) {
7656                 setPosition(this._icon, pos);
7657
7658                 if (this._shadow) {
7659                         setPosition(this._shadow, pos);
7660                 }
7661
7662                 this._zIndex = pos.y + this.options.zIndexOffset;
7663
7664                 this._resetZIndex();
7665         },
7666
7667         _updateZIndex: function (offset) {
7668                 this._icon.style.zIndex = this._zIndex + offset;
7669         },
7670
7671         _animateZoom: function (opt) {
7672                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7673
7674                 this._setPos(pos);
7675         },
7676
7677         _initInteraction: function () {
7678
7679                 if (!this.options.interactive) { return; }
7680
7681                 addClass(this._icon, 'leaflet-interactive');
7682
7683                 this.addInteractiveTarget(this._icon);
7684
7685                 if (MarkerDrag) {
7686                         var draggable = this.options.draggable;
7687                         if (this.dragging) {
7688                                 draggable = this.dragging.enabled();
7689                                 this.dragging.disable();
7690                         }
7691
7692                         this.dragging = new MarkerDrag(this);
7693
7694                         if (draggable) {
7695                                 this.dragging.enable();
7696                         }
7697                 }
7698         },
7699
7700         // @method setOpacity(opacity: Number): this
7701         // Changes the opacity of the marker.
7702         setOpacity: function (opacity) {
7703                 this.options.opacity = opacity;
7704                 if (this._map) {
7705                         this._updateOpacity();
7706                 }
7707
7708                 return this;
7709         },
7710
7711         _updateOpacity: function () {
7712                 var opacity = this.options.opacity;
7713
7714                 if (this._icon) {
7715                         setOpacity(this._icon, opacity);
7716                 }
7717
7718                 if (this._shadow) {
7719                         setOpacity(this._shadow, opacity);
7720                 }
7721         },
7722
7723         _bringToFront: function () {
7724                 this._updateZIndex(this.options.riseOffset);
7725         },
7726
7727         _resetZIndex: function () {
7728                 this._updateZIndex(0);
7729         },
7730
7731         _getPopupAnchor: function () {
7732                 return this.options.icon.options.popupAnchor;
7733         },
7734
7735         _getTooltipAnchor: function () {
7736                 return this.options.icon.options.tooltipAnchor;
7737         }
7738 });
7739
7740
7741 // factory L.marker(latlng: LatLng, options? : Marker options)
7742
7743 // @factory L.marker(latlng: LatLng, options? : Marker options)
7744 // Instantiates a Marker object given a geographical point and optionally an options object.
7745 function marker(latlng, options) {
7746         return new Marker(latlng, options);
7747 }
7748
7749 /*
7750  * @class Path
7751  * @aka L.Path
7752  * @inherits Interactive layer
7753  *
7754  * An abstract class that contains options and constants shared between vector
7755  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7756  */
7757
7758 var Path = Layer.extend({
7759
7760         // @section
7761         // @aka Path options
7762         options: {
7763                 // @option stroke: Boolean = true
7764                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7765                 stroke: true,
7766
7767                 // @option color: String = '#3388ff'
7768                 // Stroke color
7769                 color: '#3388ff',
7770
7771                 // @option weight: Number = 3
7772                 // Stroke width in pixels
7773                 weight: 3,
7774
7775                 // @option opacity: Number = 1.0
7776                 // Stroke opacity
7777                 opacity: 1,
7778
7779                 // @option lineCap: String= 'round'
7780                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7781                 lineCap: 'round',
7782
7783                 // @option lineJoin: String = 'round'
7784                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7785                 lineJoin: 'round',
7786
7787                 // @option dashArray: String = null
7788                 // 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).
7789                 dashArray: null,
7790
7791                 // @option dashOffset: String = null
7792                 // 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).
7793                 dashOffset: null,
7794
7795                 // @option fill: Boolean = depends
7796                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7797                 fill: false,
7798
7799                 // @option fillColor: String = *
7800                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7801                 fillColor: null,
7802
7803                 // @option fillOpacity: Number = 0.2
7804                 // Fill opacity.
7805                 fillOpacity: 0.2,
7806
7807                 // @option fillRule: String = 'evenodd'
7808                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7809                 fillRule: 'evenodd',
7810
7811                 // className: '',
7812
7813                 // Option inherited from "Interactive layer" abstract class
7814                 interactive: true,
7815
7816                 // @option bubblingMouseEvents: Boolean = true
7817                 // When `true`, a mouse event on this path will trigger the same event on the map
7818                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7819                 bubblingMouseEvents: true
7820         },
7821
7822         beforeAdd: function (map) {
7823                 // Renderer is set here because we need to call renderer.getEvents
7824                 // before this.getEvents.
7825                 this._renderer = map.getRenderer(this);
7826         },
7827
7828         onAdd: function () {
7829                 this._renderer._initPath(this);
7830                 this._reset();
7831                 this._renderer._addPath(this);
7832         },
7833
7834         onRemove: function () {
7835                 this._renderer._removePath(this);
7836         },
7837
7838         // @method redraw(): this
7839         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7840         redraw: function () {
7841                 if (this._map) {
7842                         this._renderer._updatePath(this);
7843                 }
7844                 return this;
7845         },
7846
7847         // @method setStyle(style: Path options): this
7848         // Changes the appearance of a Path based on the options in the `Path options` object.
7849         setStyle: function (style) {
7850                 setOptions(this, style);
7851                 if (this._renderer) {
7852                         this._renderer._updateStyle(this);
7853                         if (this.options.stroke && style.hasOwnProperty('weight')) {
7854                                 this._updateBounds();
7855                         }
7856                 }
7857                 return this;
7858         },
7859
7860         // @method bringToFront(): this
7861         // Brings the layer to the top of all path layers.
7862         bringToFront: function () {
7863                 if (this._renderer) {
7864                         this._renderer._bringToFront(this);
7865                 }
7866                 return this;
7867         },
7868
7869         // @method bringToBack(): this
7870         // Brings the layer to the bottom of all path layers.
7871         bringToBack: function () {
7872                 if (this._renderer) {
7873                         this._renderer._bringToBack(this);
7874                 }
7875                 return this;
7876         },
7877
7878         getElement: function () {
7879                 return this._path;
7880         },
7881
7882         _reset: function () {
7883                 // defined in child classes
7884                 this._project();
7885                 this._update();
7886         },
7887
7888         _clickTolerance: function () {
7889                 // used when doing hit detection for Canvas layers
7890                 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7891         }
7892 });
7893
7894 /*
7895  * @class CircleMarker
7896  * @aka L.CircleMarker
7897  * @inherits Path
7898  *
7899  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7900  */
7901
7902 var CircleMarker = Path.extend({
7903
7904         // @section
7905         // @aka CircleMarker options
7906         options: {
7907                 fill: true,
7908
7909                 // @option radius: Number = 10
7910                 // Radius of the circle marker, in pixels
7911                 radius: 10
7912         },
7913
7914         initialize: function (latlng, options) {
7915                 setOptions(this, options);
7916                 this._latlng = toLatLng(latlng);
7917                 this._radius = this.options.radius;
7918         },
7919
7920         // @method setLatLng(latLng: LatLng): this
7921         // Sets the position of a circle marker to a new location.
7922         setLatLng: function (latlng) {
7923                 this._latlng = toLatLng(latlng);
7924                 this.redraw();
7925                 return this.fire('move', {latlng: this._latlng});
7926         },
7927
7928         // @method getLatLng(): LatLng
7929         // Returns the current geographical position of the circle marker
7930         getLatLng: function () {
7931                 return this._latlng;
7932         },
7933
7934         // @method setRadius(radius: Number): this
7935         // Sets the radius of a circle marker. Units are in pixels.
7936         setRadius: function (radius) {
7937                 this.options.radius = this._radius = radius;
7938                 return this.redraw();
7939         },
7940
7941         // @method getRadius(): Number
7942         // Returns the current radius of the circle
7943         getRadius: function () {
7944                 return this._radius;
7945         },
7946
7947         setStyle : function (options) {
7948                 var radius = options && options.radius || this._radius;
7949                 Path.prototype.setStyle.call(this, options);
7950                 this.setRadius(radius);
7951                 return this;
7952         },
7953
7954         _project: function () {
7955                 this._point = this._map.latLngToLayerPoint(this._latlng);
7956                 this._updateBounds();
7957         },
7958
7959         _updateBounds: function () {
7960                 var r = this._radius,
7961                     r2 = this._radiusY || r,
7962                     w = this._clickTolerance(),
7963                     p = [r + w, r2 + w];
7964                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7965         },
7966
7967         _update: function () {
7968                 if (this._map) {
7969                         this._updatePath();
7970                 }
7971         },
7972
7973         _updatePath: function () {
7974                 this._renderer._updateCircle(this);
7975         },
7976
7977         _empty: function () {
7978                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7979         },
7980
7981         // Needed by the `Canvas` renderer for interactivity
7982         _containsPoint: function (p) {
7983                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7984         }
7985 });
7986
7987
7988 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7989 // Instantiates a circle marker object given a geographical point, and an optional options object.
7990 function circleMarker(latlng, options) {
7991         return new CircleMarker(latlng, options);
7992 }
7993
7994 /*
7995  * @class Circle
7996  * @aka L.Circle
7997  * @inherits CircleMarker
7998  *
7999  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8000  *
8001  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8002  *
8003  * @example
8004  *
8005  * ```js
8006  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
8007  * ```
8008  */
8009
8010 var Circle = CircleMarker.extend({
8011
8012         initialize: function (latlng, options, legacyOptions) {
8013                 if (typeof options === 'number') {
8014                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8015                         options = extend({}, legacyOptions, {radius: options});
8016                 }
8017                 setOptions(this, options);
8018                 this._latlng = toLatLng(latlng);
8019
8020                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8021
8022                 // @section
8023                 // @aka Circle options
8024                 // @option radius: Number; Radius of the circle, in meters.
8025                 this._mRadius = this.options.radius;
8026         },
8027
8028         // @method setRadius(radius: Number): this
8029         // Sets the radius of a circle. Units are in meters.
8030         setRadius: function (radius) {
8031                 this._mRadius = radius;
8032                 return this.redraw();
8033         },
8034
8035         // @method getRadius(): Number
8036         // Returns the current radius of a circle. Units are in meters.
8037         getRadius: function () {
8038                 return this._mRadius;
8039         },
8040
8041         // @method getBounds(): LatLngBounds
8042         // Returns the `LatLngBounds` of the path.
8043         getBounds: function () {
8044                 var half = [this._radius, this._radiusY || this._radius];
8045
8046                 return new LatLngBounds(
8047                         this._map.layerPointToLatLng(this._point.subtract(half)),
8048                         this._map.layerPointToLatLng(this._point.add(half)));
8049         },
8050
8051         setStyle: Path.prototype.setStyle,
8052
8053         _project: function () {
8054
8055                 var lng = this._latlng.lng,
8056                     lat = this._latlng.lat,
8057                     map = this._map,
8058                     crs = map.options.crs;
8059
8060                 if (crs.distance === Earth.distance) {
8061                         var d = Math.PI / 180,
8062                             latR = (this._mRadius / Earth.R) / d,
8063                             top = map.project([lat + latR, lng]),
8064                             bottom = map.project([lat - latR, lng]),
8065                             p = top.add(bottom).divideBy(2),
8066                             lat2 = map.unproject(p).lat,
8067                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8068                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8069
8070                         if (isNaN(lngR) || lngR === 0) {
8071                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8072                         }
8073
8074                         this._point = p.subtract(map.getPixelOrigin());
8075                         this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
8076                         this._radiusY = p.y - top.y;
8077
8078                 } else {
8079                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8080
8081                         this._point = map.latLngToLayerPoint(this._latlng);
8082                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8083                 }
8084
8085                 this._updateBounds();
8086         }
8087 });
8088
8089 // @factory L.circle(latlng: LatLng, options?: Circle options)
8090 // Instantiates a circle object given a geographical point, and an options object
8091 // which contains the circle radius.
8092 // @alternative
8093 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8094 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8095 // Do not use in new applications or plugins.
8096 function circle(latlng, options, legacyOptions) {
8097         return new Circle(latlng, options, legacyOptions);
8098 }
8099
8100 /*
8101  * @class Polyline
8102  * @aka L.Polyline
8103  * @inherits Path
8104  *
8105  * A class for drawing polyline overlays on a map. Extends `Path`.
8106  *
8107  * @example
8108  *
8109  * ```js
8110  * // create a red polyline from an array of LatLng points
8111  * var latlngs = [
8112  *      [45.51, -122.68],
8113  *      [37.77, -122.43],
8114  *      [34.04, -118.2]
8115  * ];
8116  *
8117  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8118  *
8119  * // zoom the map to the polyline
8120  * map.fitBounds(polyline.getBounds());
8121  * ```
8122  *
8123  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8124  *
8125  * ```js
8126  * // create a red polyline from an array of arrays of LatLng points
8127  * var latlngs = [
8128  *      [[45.51, -122.68],
8129  *       [37.77, -122.43],
8130  *       [34.04, -118.2]],
8131  *      [[40.78, -73.91],
8132  *       [41.83, -87.62],
8133  *       [32.76, -96.72]]
8134  * ];
8135  * ```
8136  */
8137
8138
8139 var Polyline = Path.extend({
8140
8141         // @section
8142         // @aka Polyline options
8143         options: {
8144                 // @option smoothFactor: Number = 1.0
8145                 // How much to simplify the polyline on each zoom level. More means
8146                 // better performance and smoother look, and less means more accurate representation.
8147                 smoothFactor: 1.0,
8148
8149                 // @option noClip: Boolean = false
8150                 // Disable polyline clipping.
8151                 noClip: false
8152         },
8153
8154         initialize: function (latlngs, options) {
8155                 setOptions(this, options);
8156                 this._setLatLngs(latlngs);
8157         },
8158
8159         // @method getLatLngs(): LatLng[]
8160         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8161         getLatLngs: function () {
8162                 return this._latlngs;
8163         },
8164
8165         // @method setLatLngs(latlngs: LatLng[]): this
8166         // Replaces all the points in the polyline with the given array of geographical points.
8167         setLatLngs: function (latlngs) {
8168                 this._setLatLngs(latlngs);
8169                 return this.redraw();
8170         },
8171
8172         // @method isEmpty(): Boolean
8173         // Returns `true` if the Polyline has no LatLngs.
8174         isEmpty: function () {
8175                 return !this._latlngs.length;
8176         },
8177
8178         // @method closestLayerPoint(p: Point): Point
8179         // Returns the point closest to `p` on the Polyline.
8180         closestLayerPoint: function (p) {
8181                 var minDistance = Infinity,
8182                     minPoint = null,
8183                     closest = _sqClosestPointOnSegment,
8184                     p1, p2;
8185
8186                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8187                         var points = this._parts[j];
8188
8189                         for (var i = 1, len = points.length; i < len; i++) {
8190                                 p1 = points[i - 1];
8191                                 p2 = points[i];
8192
8193                                 var sqDist = closest(p, p1, p2, true);
8194
8195                                 if (sqDist < minDistance) {
8196                                         minDistance = sqDist;
8197                                         minPoint = closest(p, p1, p2);
8198                                 }
8199                         }
8200                 }
8201                 if (minPoint) {
8202                         minPoint.distance = Math.sqrt(minDistance);
8203                 }
8204                 return minPoint;
8205         },
8206
8207         // @method getCenter(): LatLng
8208         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8209         getCenter: function () {
8210                 // throws error when not yet added to map as this center calculation requires projected coordinates
8211                 if (!this._map) {
8212                         throw new Error('Must add layer to map before using getCenter()');
8213                 }
8214
8215                 var i, halfDist, segDist, dist, p1, p2, ratio,
8216                     points = this._rings[0],
8217                     len = points.length;
8218
8219                 if (!len) { return null; }
8220
8221                 // polyline centroid algorithm; only uses the first ring if there are multiple
8222
8223                 for (i = 0, halfDist = 0; i < len - 1; i++) {
8224                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
8225                 }
8226
8227                 // The line is so small in the current view that all points are on the same pixel.
8228                 if (halfDist === 0) {
8229                         return this._map.layerPointToLatLng(points[0]);
8230                 }
8231
8232                 for (i = 0, dist = 0; i < len - 1; i++) {
8233                         p1 = points[i];
8234                         p2 = points[i + 1];
8235                         segDist = p1.distanceTo(p2);
8236                         dist += segDist;
8237
8238                         if (dist > halfDist) {
8239                                 ratio = (dist - halfDist) / segDist;
8240                                 return this._map.layerPointToLatLng([
8241                                         p2.x - ratio * (p2.x - p1.x),
8242                                         p2.y - ratio * (p2.y - p1.y)
8243                                 ]);
8244                         }
8245                 }
8246         },
8247
8248         // @method getBounds(): LatLngBounds
8249         // Returns the `LatLngBounds` of the path.
8250         getBounds: function () {
8251                 return this._bounds;
8252         },
8253
8254         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8255         // Adds a given point to the polyline. By default, adds to the first ring of
8256         // the polyline in case of a multi-polyline, but can be overridden by passing
8257         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8258         addLatLng: function (latlng, latlngs) {
8259                 latlngs = latlngs || this._defaultShape();
8260                 latlng = toLatLng(latlng);
8261                 latlngs.push(latlng);
8262                 this._bounds.extend(latlng);
8263                 return this.redraw();
8264         },
8265
8266         _setLatLngs: function (latlngs) {
8267                 this._bounds = new LatLngBounds();
8268                 this._latlngs = this._convertLatLngs(latlngs);
8269         },
8270
8271         _defaultShape: function () {
8272                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8273         },
8274
8275         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8276         _convertLatLngs: function (latlngs) {
8277                 var result = [],
8278                     flat = isFlat(latlngs);
8279
8280                 for (var i = 0, len = latlngs.length; i < len; i++) {
8281                         if (flat) {
8282                                 result[i] = toLatLng(latlngs[i]);
8283                                 this._bounds.extend(result[i]);
8284                         } else {
8285                                 result[i] = this._convertLatLngs(latlngs[i]);
8286                         }
8287                 }
8288
8289                 return result;
8290         },
8291
8292         _project: function () {
8293                 var pxBounds = new Bounds();
8294                 this._rings = [];
8295                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8296
8297                 if (this._bounds.isValid() && pxBounds.isValid()) {
8298                         this._rawPxBounds = pxBounds;
8299                         this._updateBounds();
8300                 }
8301         },
8302
8303         _updateBounds: function () {
8304                 var w = this._clickTolerance(),
8305                     p = new Point(w, w);
8306                 this._pxBounds = new Bounds([
8307                         this._rawPxBounds.min.subtract(p),
8308                         this._rawPxBounds.max.add(p)
8309                 ]);
8310         },
8311
8312         // recursively turns latlngs into a set of rings with projected coordinates
8313         _projectLatlngs: function (latlngs, result, projectedBounds) {
8314                 var flat = latlngs[0] instanceof LatLng,
8315                     len = latlngs.length,
8316                     i, ring;
8317
8318                 if (flat) {
8319                         ring = [];
8320                         for (i = 0; i < len; i++) {
8321                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8322                                 projectedBounds.extend(ring[i]);
8323                         }
8324                         result.push(ring);
8325                 } else {
8326                         for (i = 0; i < len; i++) {
8327                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8328                         }
8329                 }
8330         },
8331
8332         // clip polyline by renderer bounds so that we have less to render for performance
8333         _clipPoints: function () {
8334                 var bounds = this._renderer._bounds;
8335
8336                 this._parts = [];
8337                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8338                         return;
8339                 }
8340
8341                 if (this.options.noClip) {
8342                         this._parts = this._rings;
8343                         return;
8344                 }
8345
8346                 var parts = this._parts,
8347                     i, j, k, len, len2, segment, points;
8348
8349                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8350                         points = this._rings[i];
8351
8352                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8353                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8354
8355                                 if (!segment) { continue; }
8356
8357                                 parts[k] = parts[k] || [];
8358                                 parts[k].push(segment[0]);
8359
8360                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8361                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8362                                         parts[k].push(segment[1]);
8363                                         k++;
8364                                 }
8365                         }
8366                 }
8367         },
8368
8369         // simplify each clipped part of the polyline for performance
8370         _simplifyPoints: function () {
8371                 var parts = this._parts,
8372                     tolerance = this.options.smoothFactor;
8373
8374                 for (var i = 0, len = parts.length; i < len; i++) {
8375                         parts[i] = simplify(parts[i], tolerance);
8376                 }
8377         },
8378
8379         _update: function () {
8380                 if (!this._map) { return; }
8381
8382                 this._clipPoints();
8383                 this._simplifyPoints();
8384                 this._updatePath();
8385         },
8386
8387         _updatePath: function () {
8388                 this._renderer._updatePoly(this);
8389         },
8390
8391         // Needed by the `Canvas` renderer for interactivity
8392         _containsPoint: function (p, closed) {
8393                 var i, j, k, len, len2, part,
8394                     w = this._clickTolerance();
8395
8396                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8397
8398                 // hit detection for polylines
8399                 for (i = 0, len = this._parts.length; i < len; i++) {
8400                         part = this._parts[i];
8401
8402                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8403                                 if (!closed && (j === 0)) { continue; }
8404
8405                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8406                                         return true;
8407                                 }
8408                         }
8409                 }
8410                 return false;
8411         }
8412 });
8413
8414 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8415 // Instantiates a polyline object given an array of geographical points and
8416 // optionally an options object. You can create a `Polyline` object with
8417 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8418 // of geographic points.
8419 function polyline(latlngs, options) {
8420         return new Polyline(latlngs, options);
8421 }
8422
8423 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8424 Polyline._flat = _flat;
8425
8426 /*
8427  * @class Polygon
8428  * @aka L.Polygon
8429  * @inherits Polyline
8430  *
8431  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8432  *
8433  * 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.
8434  *
8435  *
8436  * @example
8437  *
8438  * ```js
8439  * // create a red polygon from an array of LatLng points
8440  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8441  *
8442  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8443  *
8444  * // zoom the map to the polygon
8445  * map.fitBounds(polygon.getBounds());
8446  * ```
8447  *
8448  * 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:
8449  *
8450  * ```js
8451  * var latlngs = [
8452  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8453  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8454  * ];
8455  * ```
8456  *
8457  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8458  *
8459  * ```js
8460  * var latlngs = [
8461  *   [ // first polygon
8462  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8463  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8464  *   ],
8465  *   [ // second polygon
8466  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8467  *   ]
8468  * ];
8469  * ```
8470  */
8471
8472 var Polygon = Polyline.extend({
8473
8474         options: {
8475                 fill: true
8476         },
8477
8478         isEmpty: function () {
8479                 return !this._latlngs.length || !this._latlngs[0].length;
8480         },
8481
8482         getCenter: function () {
8483                 // throws error when not yet added to map as this center calculation requires projected coordinates
8484                 if (!this._map) {
8485                         throw new Error('Must add layer to map before using getCenter()');
8486                 }
8487
8488                 var i, j, p1, p2, f, area, x, y, center,
8489                     points = this._rings[0],
8490                     len = points.length;
8491
8492                 if (!len) { return null; }
8493
8494                 // polygon centroid algorithm; only uses the first ring if there are multiple
8495
8496                 area = x = y = 0;
8497
8498                 for (i = 0, j = len - 1; i < len; j = i++) {
8499                         p1 = points[i];
8500                         p2 = points[j];
8501
8502                         f = p1.y * p2.x - p2.y * p1.x;
8503                         x += (p1.x + p2.x) * f;
8504                         y += (p1.y + p2.y) * f;
8505                         area += f * 3;
8506                 }
8507
8508                 if (area === 0) {
8509                         // Polygon is so small that all points are on same pixel.
8510                         center = points[0];
8511                 } else {
8512                         center = [x / area, y / area];
8513                 }
8514                 return this._map.layerPointToLatLng(center);
8515         },
8516
8517         _convertLatLngs: function (latlngs) {
8518                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8519                     len = result.length;
8520
8521                 // remove last point if it equals first one
8522                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8523                         result.pop();
8524                 }
8525                 return result;
8526         },
8527
8528         _setLatLngs: function (latlngs) {
8529                 Polyline.prototype._setLatLngs.call(this, latlngs);
8530                 if (isFlat(this._latlngs)) {
8531                         this._latlngs = [this._latlngs];
8532                 }
8533         },
8534
8535         _defaultShape: function () {
8536                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8537         },
8538
8539         _clipPoints: function () {
8540                 // polygons need a different clipping algorithm so we redefine that
8541
8542                 var bounds = this._renderer._bounds,
8543                     w = this.options.weight,
8544                     p = new Point(w, w);
8545
8546                 // increase clip padding by stroke width to avoid stroke on clip edges
8547                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8548
8549                 this._parts = [];
8550                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8551                         return;
8552                 }
8553
8554                 if (this.options.noClip) {
8555                         this._parts = this._rings;
8556                         return;
8557                 }
8558
8559                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8560                         clipped = clipPolygon(this._rings[i], bounds, true);
8561                         if (clipped.length) {
8562                                 this._parts.push(clipped);
8563                         }
8564                 }
8565         },
8566
8567         _updatePath: function () {
8568                 this._renderer._updatePoly(this, true);
8569         },
8570
8571         // Needed by the `Canvas` renderer for interactivity
8572         _containsPoint: function (p) {
8573                 var inside = false,
8574                     part, p1, p2, i, j, k, len, len2;
8575
8576                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8577
8578                 // ray casting algorithm for detecting if point is in polygon
8579                 for (i = 0, len = this._parts.length; i < len; i++) {
8580                         part = this._parts[i];
8581
8582                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8583                                 p1 = part[j];
8584                                 p2 = part[k];
8585
8586                                 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)) {
8587                                         inside = !inside;
8588                                 }
8589                         }
8590                 }
8591
8592                 // also check if it's on polygon stroke
8593                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8594         }
8595
8596 });
8597
8598
8599 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8600 function polygon(latlngs, options) {
8601         return new Polygon(latlngs, options);
8602 }
8603
8604 /*
8605  * @class GeoJSON
8606  * @aka L.GeoJSON
8607  * @inherits FeatureGroup
8608  *
8609  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8610  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8611  *
8612  * @example
8613  *
8614  * ```js
8615  * L.geoJSON(data, {
8616  *      style: function (feature) {
8617  *              return {color: feature.properties.color};
8618  *      }
8619  * }).bindPopup(function (layer) {
8620  *      return layer.feature.properties.description;
8621  * }).addTo(map);
8622  * ```
8623  */
8624
8625 var GeoJSON = FeatureGroup.extend({
8626
8627         /* @section
8628          * @aka GeoJSON options
8629          *
8630          * @option pointToLayer: Function = *
8631          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8632          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8633          * The default is to spawn a default `Marker`:
8634          * ```js
8635          * function(geoJsonPoint, latlng) {
8636          *      return L.marker(latlng);
8637          * }
8638          * ```
8639          *
8640          * @option style: Function = *
8641          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8642          * called internally when data is added.
8643          * The default value is to not override any defaults:
8644          * ```js
8645          * function (geoJsonFeature) {
8646          *      return {}
8647          * }
8648          * ```
8649          *
8650          * @option onEachFeature: Function = *
8651          * A `Function` that will be called once for each created `Feature`, after it has
8652          * been created and styled. Useful for attaching events and popups to features.
8653          * The default is to do nothing with the newly created layers:
8654          * ```js
8655          * function (feature, layer) {}
8656          * ```
8657          *
8658          * @option filter: Function = *
8659          * A `Function` that will be used to decide whether to include a feature or not.
8660          * The default is to include all features:
8661          * ```js
8662          * function (geoJsonFeature) {
8663          *      return true;
8664          * }
8665          * ```
8666          * Note: dynamically changing the `filter` option will have effect only on newly
8667          * added data. It will _not_ re-evaluate already included features.
8668          *
8669          * @option coordsToLatLng: Function = *
8670          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8671          * The default is the `coordsToLatLng` static method.
8672          */
8673
8674         initialize: function (geojson, options) {
8675                 setOptions(this, options);
8676
8677                 this._layers = {};
8678
8679                 if (geojson) {
8680                         this.addData(geojson);
8681                 }
8682         },
8683
8684         // @method addData( <GeoJSON> data ): this
8685         // Adds a GeoJSON object to the layer.
8686         addData: function (geojson) {
8687                 var features = isArray(geojson) ? geojson : geojson.features,
8688                     i, len, feature;
8689
8690                 if (features) {
8691                         for (i = 0, len = features.length; i < len; i++) {
8692                                 // only add this if geometry or geometries are set and not null
8693                                 feature = features[i];
8694                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8695                                         this.addData(feature);
8696                                 }
8697                         }
8698                         return this;
8699                 }
8700
8701                 var options = this.options;
8702
8703                 if (options.filter && !options.filter(geojson)) { return this; }
8704
8705                 var layer = geometryToLayer(geojson, options);
8706                 if (!layer) {
8707                         return this;
8708                 }
8709                 layer.feature = asFeature(geojson);
8710
8711                 layer.defaultOptions = layer.options;
8712                 this.resetStyle(layer);
8713
8714                 if (options.onEachFeature) {
8715                         options.onEachFeature(geojson, layer);
8716                 }
8717
8718                 return this.addLayer(layer);
8719         },
8720
8721         // @method resetStyle( <Path> layer ): this
8722         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8723         resetStyle: function (layer) {
8724                 // reset any custom styles
8725                 layer.options = extend({}, layer.defaultOptions);
8726                 this._setLayerStyle(layer, this.options.style);
8727                 return this;
8728         },
8729
8730         // @method setStyle( <Function> style ): this
8731         // Changes styles of GeoJSON vector layers with the given style function.
8732         setStyle: function (style) {
8733                 return this.eachLayer(function (layer) {
8734                         this._setLayerStyle(layer, style);
8735                 }, this);
8736         },
8737
8738         _setLayerStyle: function (layer, style) {
8739                 if (layer.setStyle) {
8740                         if (typeof style === 'function') {
8741                                 style = style(layer.feature);
8742                         }
8743                         layer.setStyle(style);
8744                 }
8745         }
8746 });
8747
8748 // @section
8749 // There are several static functions which can be called without instantiating L.GeoJSON:
8750
8751 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8752 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8753 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8754 // functions if provided as options.
8755 function geometryToLayer(geojson, options) {
8756
8757         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8758             coords = geometry ? geometry.coordinates : null,
8759             layers = [],
8760             pointToLayer = options && options.pointToLayer,
8761             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8762             latlng, latlngs, i, len;
8763
8764         if (!coords && !geometry) {
8765                 return null;
8766         }
8767
8768         switch (geometry.type) {
8769         case 'Point':
8770                 latlng = _coordsToLatLng(coords);
8771                 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8772
8773         case 'MultiPoint':
8774                 for (i = 0, len = coords.length; i < len; i++) {
8775                         latlng = _coordsToLatLng(coords[i]);
8776                         layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8777                 }
8778                 return new FeatureGroup(layers);
8779
8780         case 'LineString':
8781         case 'MultiLineString':
8782                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8783                 return new Polyline(latlngs, options);
8784
8785         case 'Polygon':
8786         case 'MultiPolygon':
8787                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8788                 return new Polygon(latlngs, options);
8789
8790         case 'GeometryCollection':
8791                 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8792                         var layer = geometryToLayer({
8793                                 geometry: geometry.geometries[i],
8794                                 type: 'Feature',
8795                                 properties: geojson.properties
8796                         }, options);
8797
8798                         if (layer) {
8799                                 layers.push(layer);
8800                         }
8801                 }
8802                 return new FeatureGroup(layers);
8803
8804         default:
8805                 throw new Error('Invalid GeoJSON object.');
8806         }
8807 }
8808
8809 // @function coordsToLatLng(coords: Array): LatLng
8810 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8811 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8812 function coordsToLatLng(coords) {
8813         return new LatLng(coords[1], coords[0], coords[2]);
8814 }
8815
8816 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8817 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8818 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8819 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8820 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8821         var latlngs = [];
8822
8823         for (var i = 0, len = coords.length, latlng; i < len; i++) {
8824                 latlng = levelsDeep ?
8825                         coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8826                         (_coordsToLatLng || coordsToLatLng)(coords[i]);
8827
8828                 latlngs.push(latlng);
8829         }
8830
8831         return latlngs;
8832 }
8833
8834 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8835 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8836 function latLngToCoords(latlng, precision) {
8837         precision = typeof precision === 'number' ? precision : 6;
8838         return latlng.alt !== undefined ?
8839                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8840                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8841 }
8842
8843 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8844 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8845 // `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.
8846 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8847         var coords = [];
8848
8849         for (var i = 0, len = latlngs.length; i < len; i++) {
8850                 coords.push(levelsDeep ?
8851                         latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8852                         latLngToCoords(latlngs[i], precision));
8853         }
8854
8855         if (!levelsDeep && closed) {
8856                 coords.push(coords[0]);
8857         }
8858
8859         return coords;
8860 }
8861
8862 function getFeature(layer, newGeometry) {
8863         return layer.feature ?
8864                 extend({}, layer.feature, {geometry: newGeometry}) :
8865                 asFeature(newGeometry);
8866 }
8867
8868 // @function asFeature(geojson: Object): Object
8869 // Normalize GeoJSON geometries/features into GeoJSON features.
8870 function asFeature(geojson) {
8871         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8872                 return geojson;
8873         }
8874
8875         return {
8876                 type: 'Feature',
8877                 properties: {},
8878                 geometry: geojson
8879         };
8880 }
8881
8882 var PointToGeoJSON = {
8883         toGeoJSON: function (precision) {
8884                 return getFeature(this, {
8885                         type: 'Point',
8886                         coordinates: latLngToCoords(this.getLatLng(), precision)
8887                 });
8888         }
8889 };
8890
8891 // @namespace Marker
8892 // @method toGeoJSON(precision?: Number): Object
8893 // `precision` is the number of decimal places for coordinates.
8894 // The default value is 6 places.
8895 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8896 Marker.include(PointToGeoJSON);
8897
8898 // @namespace CircleMarker
8899 // @method toGeoJSON(precision?: Number): Object
8900 // `precision` is the number of decimal places for coordinates.
8901 // The default value is 6 places.
8902 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8903 Circle.include(PointToGeoJSON);
8904 CircleMarker.include(PointToGeoJSON);
8905
8906
8907 // @namespace Polyline
8908 // @method toGeoJSON(precision?: Number): Object
8909 // `precision` is the number of decimal places for coordinates.
8910 // The default value is 6 places.
8911 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8912 Polyline.include({
8913         toGeoJSON: function (precision) {
8914                 var multi = !isFlat(this._latlngs);
8915
8916                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8917
8918                 return getFeature(this, {
8919                         type: (multi ? 'Multi' : '') + 'LineString',
8920                         coordinates: coords
8921                 });
8922         }
8923 });
8924
8925 // @namespace Polygon
8926 // @method toGeoJSON(precision?: Number): Object
8927 // `precision` is the number of decimal places for coordinates.
8928 // The default value is 6 places.
8929 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8930 Polygon.include({
8931         toGeoJSON: function (precision) {
8932                 var holes = !isFlat(this._latlngs),
8933                     multi = holes && !isFlat(this._latlngs[0]);
8934
8935                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8936
8937                 if (!holes) {
8938                         coords = [coords];
8939                 }
8940
8941                 return getFeature(this, {
8942                         type: (multi ? 'Multi' : '') + 'Polygon',
8943                         coordinates: coords
8944                 });
8945         }
8946 });
8947
8948
8949 // @namespace LayerGroup
8950 LayerGroup.include({
8951         toMultiPoint: function (precision) {
8952                 var coords = [];
8953
8954                 this.eachLayer(function (layer) {
8955                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8956                 });
8957
8958                 return getFeature(this, {
8959                         type: 'MultiPoint',
8960                         coordinates: coords
8961                 });
8962         },
8963
8964         // @method toGeoJSON(precision?: Number): Object
8965         // `precision` is the number of decimal places for coordinates.
8966         // The default value is 6 places.
8967         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8968         toGeoJSON: function (precision) {
8969
8970                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8971
8972                 if (type === 'MultiPoint') {
8973                         return this.toMultiPoint(precision);
8974                 }
8975
8976                 var isGeometryCollection = type === 'GeometryCollection',
8977                     jsons = [];
8978
8979                 this.eachLayer(function (layer) {
8980                         if (layer.toGeoJSON) {
8981                                 var json = layer.toGeoJSON(precision);
8982                                 if (isGeometryCollection) {
8983                                         jsons.push(json.geometry);
8984                                 } else {
8985                                         var feature = asFeature(json);
8986                                         // Squash nested feature collections
8987                                         if (feature.type === 'FeatureCollection') {
8988                                                 jsons.push.apply(jsons, feature.features);
8989                                         } else {
8990                                                 jsons.push(feature);
8991                                         }
8992                                 }
8993                         }
8994                 });
8995
8996                 if (isGeometryCollection) {
8997                         return getFeature(this, {
8998                                 geometries: jsons,
8999                                 type: 'GeometryCollection'
9000                         });
9001                 }
9002
9003                 return {
9004                         type: 'FeatureCollection',
9005                         features: jsons
9006                 };
9007         }
9008 });
9009
9010 // @namespace GeoJSON
9011 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9012 // Creates a GeoJSON layer. Optionally accepts an object in
9013 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
9014 // (you can alternatively add it later with `addData` method) and an `options` object.
9015 function geoJSON(geojson, options) {
9016         return new GeoJSON(geojson, options);
9017 }
9018
9019 // Backward compatibility.
9020 var geoJson = geoJSON;
9021
9022 /*
9023  * @class ImageOverlay
9024  * @aka L.ImageOverlay
9025  * @inherits Interactive layer
9026  *
9027  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
9028  *
9029  * @example
9030  *
9031  * ```js
9032  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
9033  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
9034  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
9035  * ```
9036  */
9037
9038 var ImageOverlay = Layer.extend({
9039
9040         // @section
9041         // @aka ImageOverlay options
9042         options: {
9043                 // @option opacity: Number = 1.0
9044                 // The opacity of the image overlay.
9045                 opacity: 1,
9046
9047                 // @option alt: String = ''
9048                 // Text for the `alt` attribute of the image (useful for accessibility).
9049                 alt: '',
9050
9051                 // @option interactive: Boolean = false
9052                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
9053                 interactive: false,
9054
9055                 // @option crossOrigin: Boolean|String = false
9056                 // Whether the crossOrigin attribute will be added to the image.
9057                 // 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.
9058                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
9059                 crossOrigin: false,
9060
9061                 // @option errorOverlayUrl: String = ''
9062                 // URL to the overlay image to show in place of the overlay that failed to load.
9063                 errorOverlayUrl: '',
9064
9065                 // @option zIndex: Number = 1
9066                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
9067                 zIndex: 1,
9068
9069                 // @option className: String = ''
9070                 // A custom class name to assign to the image. Empty by default.
9071                 className: ''
9072         },
9073
9074         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
9075                 this._url = url;
9076                 this._bounds = toLatLngBounds(bounds);
9077
9078                 setOptions(this, options);
9079         },
9080
9081         onAdd: function () {
9082                 if (!this._image) {
9083                         this._initImage();
9084
9085                         if (this.options.opacity < 1) {
9086                                 this._updateOpacity();
9087                         }
9088                 }
9089
9090                 if (this.options.interactive) {
9091                         addClass(this._image, 'leaflet-interactive');
9092                         this.addInteractiveTarget(this._image);
9093                 }
9094
9095                 this.getPane().appendChild(this._image);
9096                 this._reset();
9097         },
9098
9099         onRemove: function () {
9100                 remove(this._image);
9101                 if (this.options.interactive) {
9102                         this.removeInteractiveTarget(this._image);
9103                 }
9104         },
9105
9106         // @method setOpacity(opacity: Number): this
9107         // Sets the opacity of the overlay.
9108         setOpacity: function (opacity) {
9109                 this.options.opacity = opacity;
9110
9111                 if (this._image) {
9112                         this._updateOpacity();
9113                 }
9114                 return this;
9115         },
9116
9117         setStyle: function (styleOpts) {
9118                 if (styleOpts.opacity) {
9119                         this.setOpacity(styleOpts.opacity);
9120                 }
9121                 return this;
9122         },
9123
9124         // @method bringToFront(): this
9125         // Brings the layer to the top of all overlays.
9126         bringToFront: function () {
9127                 if (this._map) {
9128                         toFront(this._image);
9129                 }
9130                 return this;
9131         },
9132
9133         // @method bringToBack(): this
9134         // Brings the layer to the bottom of all overlays.
9135         bringToBack: function () {
9136                 if (this._map) {
9137                         toBack(this._image);
9138                 }
9139                 return this;
9140         },
9141
9142         // @method setUrl(url: String): this
9143         // Changes the URL of the image.
9144         setUrl: function (url) {
9145                 this._url = url;
9146
9147                 if (this._image) {
9148                         this._image.src = url;
9149                 }
9150                 return this;
9151         },
9152
9153         // @method setBounds(bounds: LatLngBounds): this
9154         // Update the bounds that this ImageOverlay covers
9155         setBounds: function (bounds) {
9156                 this._bounds = toLatLngBounds(bounds);
9157
9158                 if (this._map) {
9159                         this._reset();
9160                 }
9161                 return this;
9162         },
9163
9164         getEvents: function () {
9165                 var events = {
9166                         zoom: this._reset,
9167                         viewreset: this._reset
9168                 };
9169
9170                 if (this._zoomAnimated) {
9171                         events.zoomanim = this._animateZoom;
9172                 }
9173
9174                 return events;
9175         },
9176
9177         // @method setZIndex(value: Number): this
9178         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9179         setZIndex: function (value) {
9180                 this.options.zIndex = value;
9181                 this._updateZIndex();
9182                 return this;
9183         },
9184
9185         // @method getBounds(): LatLngBounds
9186         // Get the bounds that this ImageOverlay covers
9187         getBounds: function () {
9188                 return this._bounds;
9189         },
9190
9191         // @method getElement(): HTMLElement
9192         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9193         // used by this overlay.
9194         getElement: function () {
9195                 return this._image;
9196         },
9197
9198         _initImage: function () {
9199                 var wasElementSupplied = this._url.tagName === 'IMG';
9200                 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9201
9202                 addClass(img, 'leaflet-image-layer');
9203                 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9204                 if (this.options.className) { addClass(img, this.options.className); }
9205
9206                 img.onselectstart = falseFn;
9207                 img.onmousemove = falseFn;
9208
9209                 // @event load: Event
9210                 // Fired when the ImageOverlay layer has loaded its image
9211                 img.onload = bind(this.fire, this, 'load');
9212                 img.onerror = bind(this._overlayOnError, this, 'error');
9213
9214                 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9215                         img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9216                 }
9217
9218                 if (this.options.zIndex) {
9219                         this._updateZIndex();
9220                 }
9221
9222                 if (wasElementSupplied) {
9223                         this._url = img.src;
9224                         return;
9225                 }
9226
9227                 img.src = this._url;
9228                 img.alt = this.options.alt;
9229         },
9230
9231         _animateZoom: function (e) {
9232                 var scale = this._map.getZoomScale(e.zoom),
9233                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9234
9235                 setTransform(this._image, offset, scale);
9236         },
9237
9238         _reset: function () {
9239                 var image = this._image,
9240                     bounds = new Bounds(
9241                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9242                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9243                     size = bounds.getSize();
9244
9245                 setPosition(image, bounds.min);
9246
9247                 image.style.width  = size.x + 'px';
9248                 image.style.height = size.y + 'px';
9249         },
9250
9251         _updateOpacity: function () {
9252                 setOpacity(this._image, this.options.opacity);
9253         },
9254
9255         _updateZIndex: function () {
9256                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9257                         this._image.style.zIndex = this.options.zIndex;
9258                 }
9259         },
9260
9261         _overlayOnError: function () {
9262                 // @event error: Event
9263                 // Fired when the ImageOverlay layer fails to load its image
9264                 this.fire('error');
9265
9266                 var errorUrl = this.options.errorOverlayUrl;
9267                 if (errorUrl && this._url !== errorUrl) {
9268                         this._url = errorUrl;
9269                         this._image.src = errorUrl;
9270                 }
9271         }
9272 });
9273
9274 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9275 // Instantiates an image overlay object given the URL of the image and the
9276 // geographical bounds it is tied to.
9277 var imageOverlay = function (url, bounds, options) {
9278         return new ImageOverlay(url, bounds, options);
9279 };
9280
9281 /*
9282  * @class VideoOverlay
9283  * @aka L.VideoOverlay
9284  * @inherits ImageOverlay
9285  *
9286  * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9287  *
9288  * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9289  * HTML5 element.
9290  *
9291  * @example
9292  *
9293  * ```js
9294  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9295  *      videoBounds = [[ 32, -130], [ 13, -100]];
9296  * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9297  * ```
9298  */
9299
9300 var VideoOverlay = ImageOverlay.extend({
9301
9302         // @section
9303         // @aka VideoOverlay options
9304         options: {
9305                 // @option autoplay: Boolean = true
9306                 // Whether the video starts playing automatically when loaded.
9307                 autoplay: true,
9308
9309                 // @option loop: Boolean = true
9310                 // Whether the video will loop back to the beginning when played.
9311                 loop: true,
9312
9313                 // @option keepAspectRatio: Boolean = true
9314                 // Whether the video will save aspect ratio after the projection.
9315                 // Relevant for supported browsers. Browser compatibility- https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit
9316                 keepAspectRatio: true
9317         },
9318
9319         _initImage: function () {
9320                 var wasElementSupplied = this._url.tagName === 'VIDEO';
9321                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9322
9323                 addClass(vid, 'leaflet-image-layer');
9324                 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9325
9326                 vid.onselectstart = falseFn;
9327                 vid.onmousemove = falseFn;
9328
9329                 // @event load: Event
9330                 // Fired when the video has finished loading the first frame
9331                 vid.onloadeddata = bind(this.fire, this, 'load');
9332
9333                 if (wasElementSupplied) {
9334                         var sourceElements = vid.getElementsByTagName('source');
9335                         var sources = [];
9336                         for (var j = 0; j < sourceElements.length; j++) {
9337                                 sources.push(sourceElements[j].src);
9338                         }
9339
9340                         this._url = (sourceElements.length > 0) ? sources : [vid.src];
9341                         return;
9342                 }
9343
9344                 if (!isArray(this._url)) { this._url = [this._url]; }
9345
9346                 if (!this.options.keepAspectRatio && vid.style.hasOwnProperty('objectFit')) { vid.style['objectFit'] = 'fill'; }
9347                 vid.autoplay = !!this.options.autoplay;
9348                 vid.loop = !!this.options.loop;
9349                 for (var i = 0; i < this._url.length; i++) {
9350                         var source = create$1('source');
9351                         source.src = this._url[i];
9352                         vid.appendChild(source);
9353                 }
9354         }
9355
9356         // @method getElement(): HTMLVideoElement
9357         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9358         // used by this overlay.
9359 });
9360
9361
9362 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9363 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9364 // geographical bounds it is tied to.
9365
9366 function videoOverlay(video, bounds, options) {
9367         return new VideoOverlay(video, bounds, options);
9368 }
9369
9370 /*
9371  * @class SVGOverlay
9372  * @aka L.SVGOverlay
9373  * @inherits ImageOverlay
9374  *
9375  * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
9376  *
9377  * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
9378  *
9379  * @example
9380  *
9381  * ```js
9382  * var element = '<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><image xlink:href="https://mdn.mozillademos.org/files/6457/mdn_logo_only_color.png" height="200" width="200"/></svg>',
9383  *               elementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
9384  * L.svgOverlay(element, elementBounds).addTo(map);
9385  * ```
9386  */
9387
9388 var SVGOverlay = ImageOverlay.extend({
9389         _initImage: function () {
9390                 var el = this._image = this._url;
9391
9392                 addClass(el, 'leaflet-image-layer');
9393                 if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
9394
9395                 el.onselectstart = falseFn;
9396                 el.onmousemove = falseFn;
9397         }
9398
9399         // @method getElement(): SVGElement
9400         // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
9401         // used by this overlay.
9402 });
9403
9404
9405 // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
9406 // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
9407 // A viewBox attribute is required on the SVG element to zoom in and out properly.
9408
9409 function svgOverlay(el, bounds, options) {
9410         return new SVGOverlay(el, bounds, options);
9411 }
9412
9413 /*
9414  * @class DivOverlay
9415  * @inherits Layer
9416  * @aka L.DivOverlay
9417  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9418  */
9419
9420 // @namespace DivOverlay
9421 var DivOverlay = Layer.extend({
9422
9423         // @section
9424         // @aka DivOverlay options
9425         options: {
9426                 // @option offset: Point = Point(0, 7)
9427                 // The offset of the popup position. Useful to control the anchor
9428                 // of the popup when opening it on some overlays.
9429                 offset: [0, 7],
9430
9431                 // @option className: String = ''
9432                 // A custom CSS class name to assign to the popup.
9433                 className: '',
9434
9435                 // @option pane: String = 'popupPane'
9436                 // `Map pane` where the popup will be added.
9437                 pane: 'popupPane'
9438         },
9439
9440         initialize: function (options, source) {
9441                 setOptions(this, options);
9442
9443                 this._source = source;
9444         },
9445
9446         onAdd: function (map) {
9447                 this._zoomAnimated = map._zoomAnimated;
9448
9449                 if (!this._container) {
9450                         this._initLayout();
9451                 }
9452
9453                 if (map._fadeAnimated) {
9454                         setOpacity(this._container, 0);
9455                 }
9456
9457                 clearTimeout(this._removeTimeout);
9458                 this.getPane().appendChild(this._container);
9459                 this.update();
9460
9461                 if (map._fadeAnimated) {
9462                         setOpacity(this._container, 1);
9463                 }
9464
9465                 this.bringToFront();
9466         },
9467
9468         onRemove: function (map) {
9469                 if (map._fadeAnimated) {
9470                         setOpacity(this._container, 0);
9471                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9472                 } else {
9473                         remove(this._container);
9474                 }
9475         },
9476
9477         // @namespace Popup
9478         // @method getLatLng: LatLng
9479         // Returns the geographical point of popup.
9480         getLatLng: function () {
9481                 return this._latlng;
9482         },
9483
9484         // @method setLatLng(latlng: LatLng): this
9485         // Sets the geographical point where the popup will open.
9486         setLatLng: function (latlng) {
9487                 this._latlng = toLatLng(latlng);
9488                 if (this._map) {
9489                         this._updatePosition();
9490                         this._adjustPan();
9491                 }
9492                 return this;
9493         },
9494
9495         // @method getContent: String|HTMLElement
9496         // Returns the content of the popup.
9497         getContent: function () {
9498                 return this._content;
9499         },
9500
9501         // @method setContent(htmlContent: String|HTMLElement|Function): this
9502         // 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.
9503         setContent: function (content) {
9504                 this._content = content;
9505                 this.update();
9506                 return this;
9507         },
9508
9509         // @method getElement: String|HTMLElement
9510         // Alias for [getContent()](#popup-getcontent)
9511         getElement: function () {
9512                 return this._container;
9513         },
9514
9515         // @method update: null
9516         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9517         update: function () {
9518                 if (!this._map) { return; }
9519
9520                 this._container.style.visibility = 'hidden';
9521
9522                 this._updateContent();
9523                 this._updateLayout();
9524                 this._updatePosition();
9525
9526                 this._container.style.visibility = '';
9527
9528                 this._adjustPan();
9529         },
9530
9531         getEvents: function () {
9532                 var events = {
9533                         zoom: this._updatePosition,
9534                         viewreset: this._updatePosition
9535                 };
9536
9537                 if (this._zoomAnimated) {
9538                         events.zoomanim = this._animateZoom;
9539                 }
9540                 return events;
9541         },
9542
9543         // @method isOpen: Boolean
9544         // Returns `true` when the popup is visible on the map.
9545         isOpen: function () {
9546                 return !!this._map && this._map.hasLayer(this);
9547         },
9548
9549         // @method bringToFront: this
9550         // Brings this popup in front of other popups (in the same map pane).
9551         bringToFront: function () {
9552                 if (this._map) {
9553                         toFront(this._container);
9554                 }
9555                 return this;
9556         },
9557
9558         // @method bringToBack: this
9559         // Brings this popup to the back of other popups (in the same map pane).
9560         bringToBack: function () {
9561                 if (this._map) {
9562                         toBack(this._container);
9563                 }
9564                 return this;
9565         },
9566
9567         _prepareOpen: function (parent, layer, latlng) {
9568                 if (!(layer instanceof Layer)) {
9569                         latlng = layer;
9570                         layer = parent;
9571                 }
9572
9573                 if (layer instanceof FeatureGroup) {
9574                         for (var id in parent._layers) {
9575                                 layer = parent._layers[id];
9576                                 break;
9577                         }
9578                 }
9579
9580                 if (!latlng) {
9581                         if (layer.getCenter) {
9582                                 latlng = layer.getCenter();
9583                         } else if (layer.getLatLng) {
9584                                 latlng = layer.getLatLng();
9585                         } else {
9586                                 throw new Error('Unable to get source layer LatLng.');
9587                         }
9588                 }
9589
9590                 // set overlay source to this layer
9591                 this._source = layer;
9592
9593                 // update the overlay (content, layout, ect...)
9594                 this.update();
9595
9596                 return latlng;
9597         },
9598
9599         _updateContent: function () {
9600                 if (!this._content) { return; }
9601
9602                 var node = this._contentNode;
9603                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9604
9605                 if (typeof content === 'string') {
9606                         node.innerHTML = content;
9607                 } else {
9608                         while (node.hasChildNodes()) {
9609                                 node.removeChild(node.firstChild);
9610                         }
9611                         node.appendChild(content);
9612                 }
9613                 this.fire('contentupdate');
9614         },
9615
9616         _updatePosition: function () {
9617                 if (!this._map) { return; }
9618
9619                 var pos = this._map.latLngToLayerPoint(this._latlng),
9620                     offset = toPoint(this.options.offset),
9621                     anchor = this._getAnchor();
9622
9623                 if (this._zoomAnimated) {
9624                         setPosition(this._container, pos.add(anchor));
9625                 } else {
9626                         offset = offset.add(pos).add(anchor);
9627                 }
9628
9629                 var bottom = this._containerBottom = -offset.y,
9630                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9631
9632                 // bottom position the popup in case the height of the popup changes (images loading etc)
9633                 this._container.style.bottom = bottom + 'px';
9634                 this._container.style.left = left + 'px';
9635         },
9636
9637         _getAnchor: function () {
9638                 return [0, 0];
9639         }
9640
9641 });
9642
9643 /*
9644  * @class Popup
9645  * @inherits DivOverlay
9646  * @aka L.Popup
9647  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9648  * open popups while making sure that only one popup is open at one time
9649  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9650  *
9651  * @example
9652  *
9653  * If you want to just bind a popup to marker click and then open it, it's really easy:
9654  *
9655  * ```js
9656  * marker.bindPopup(popupContent).openPopup();
9657  * ```
9658  * Path overlays like polylines also have a `bindPopup` method.
9659  * Here's a more complicated way to open a popup on a map:
9660  *
9661  * ```js
9662  * var popup = L.popup()
9663  *      .setLatLng(latlng)
9664  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9665  *      .openOn(map);
9666  * ```
9667  */
9668
9669
9670 // @namespace Popup
9671 var Popup = DivOverlay.extend({
9672
9673         // @section
9674         // @aka Popup options
9675         options: {
9676                 // @option maxWidth: Number = 300
9677                 // Max width of the popup, in pixels.
9678                 maxWidth: 300,
9679
9680                 // @option minWidth: Number = 50
9681                 // Min width of the popup, in pixels.
9682                 minWidth: 50,
9683
9684                 // @option maxHeight: Number = null
9685                 // If set, creates a scrollable container of the given height
9686                 // inside a popup if its content exceeds it.
9687                 maxHeight: null,
9688
9689                 // @option autoPan: Boolean = true
9690                 // Set it to `false` if you don't want the map to do panning animation
9691                 // to fit the opened popup.
9692                 autoPan: true,
9693
9694                 // @option autoPanPaddingTopLeft: Point = null
9695                 // The margin between the popup and the top left corner of the map
9696                 // view after autopanning was performed.
9697                 autoPanPaddingTopLeft: null,
9698
9699                 // @option autoPanPaddingBottomRight: Point = null
9700                 // The margin between the popup and the bottom right corner of the map
9701                 // view after autopanning was performed.
9702                 autoPanPaddingBottomRight: null,
9703
9704                 // @option autoPanPadding: Point = Point(5, 5)
9705                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9706                 autoPanPadding: [5, 5],
9707
9708                 // @option keepInView: Boolean = false
9709                 // Set it to `true` if you want to prevent users from panning the popup
9710                 // off of the screen while it is open.
9711                 keepInView: false,
9712
9713                 // @option closeButton: Boolean = true
9714                 // Controls the presence of a close button in the popup.
9715                 closeButton: true,
9716
9717                 // @option autoClose: Boolean = true
9718                 // Set it to `false` if you want to override the default behavior of
9719                 // the popup closing when another popup is opened.
9720                 autoClose: true,
9721
9722                 // @option closeOnEscapeKey: Boolean = true
9723                 // Set it to `false` if you want to override the default behavior of
9724                 // the ESC key for closing of the popup.
9725                 closeOnEscapeKey: true,
9726
9727                 // @option closeOnClick: Boolean = *
9728                 // Set it if you want to override the default behavior of the popup closing when user clicks
9729                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9730
9731                 // @option className: String = ''
9732                 // A custom CSS class name to assign to the popup.
9733                 className: ''
9734         },
9735
9736         // @namespace Popup
9737         // @method openOn(map: Map): this
9738         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9739         openOn: function (map) {
9740                 map.openPopup(this);
9741                 return this;
9742         },
9743
9744         onAdd: function (map) {
9745                 DivOverlay.prototype.onAdd.call(this, map);
9746
9747                 // @namespace Map
9748                 // @section Popup events
9749                 // @event popupopen: PopupEvent
9750                 // Fired when a popup is opened in the map
9751                 map.fire('popupopen', {popup: this});
9752
9753                 if (this._source) {
9754                         // @namespace Layer
9755                         // @section Popup events
9756                         // @event popupopen: PopupEvent
9757                         // Fired when a popup bound to this layer is opened
9758                         this._source.fire('popupopen', {popup: this}, true);
9759                         // For non-path layers, we toggle the popup when clicking
9760                         // again the layer, so prevent the map to reopen it.
9761                         if (!(this._source instanceof Path)) {
9762                                 this._source.on('preclick', stopPropagation);
9763                         }
9764                 }
9765         },
9766
9767         onRemove: function (map) {
9768                 DivOverlay.prototype.onRemove.call(this, map);
9769
9770                 // @namespace Map
9771                 // @section Popup events
9772                 // @event popupclose: PopupEvent
9773                 // Fired when a popup in the map is closed
9774                 map.fire('popupclose', {popup: this});
9775
9776                 if (this._source) {
9777                         // @namespace Layer
9778                         // @section Popup events
9779                         // @event popupclose: PopupEvent
9780                         // Fired when a popup bound to this layer is closed
9781                         this._source.fire('popupclose', {popup: this}, true);
9782                         if (!(this._source instanceof Path)) {
9783                                 this._source.off('preclick', stopPropagation);
9784                         }
9785                 }
9786         },
9787
9788         getEvents: function () {
9789                 var events = DivOverlay.prototype.getEvents.call(this);
9790
9791                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9792                         events.preclick = this._close;
9793                 }
9794
9795                 if (this.options.keepInView) {
9796                         events.moveend = this._adjustPan;
9797                 }
9798
9799                 return events;
9800         },
9801
9802         _close: function () {
9803                 if (this._map) {
9804                         this._map.closePopup(this);
9805                 }
9806         },
9807
9808         _initLayout: function () {
9809                 var prefix = 'leaflet-popup',
9810                     container = this._container = create$1('div',
9811                         prefix + ' ' + (this.options.className || '') +
9812                         ' leaflet-zoom-animated');
9813
9814                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9815                 this._contentNode = create$1('div', prefix + '-content', wrapper);
9816
9817                 disableClickPropagation(wrapper);
9818                 disableScrollPropagation(this._contentNode);
9819                 on(wrapper, 'contextmenu', stopPropagation);
9820
9821                 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9822                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9823
9824                 if (this.options.closeButton) {
9825                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9826                         closeButton.href = '#close';
9827                         closeButton.innerHTML = '&#215;';
9828
9829                         on(closeButton, 'click', this._onCloseButtonClick, this);
9830                 }
9831         },
9832
9833         _updateLayout: function () {
9834                 var container = this._contentNode,
9835                     style = container.style;
9836
9837                 style.width = '';
9838                 style.whiteSpace = 'nowrap';
9839
9840                 var width = container.offsetWidth;
9841                 width = Math.min(width, this.options.maxWidth);
9842                 width = Math.max(width, this.options.minWidth);
9843
9844                 style.width = (width + 1) + 'px';
9845                 style.whiteSpace = '';
9846
9847                 style.height = '';
9848
9849                 var height = container.offsetHeight,
9850                     maxHeight = this.options.maxHeight,
9851                     scrolledClass = 'leaflet-popup-scrolled';
9852
9853                 if (maxHeight && height > maxHeight) {
9854                         style.height = maxHeight + 'px';
9855                         addClass(container, scrolledClass);
9856                 } else {
9857                         removeClass(container, scrolledClass);
9858                 }
9859
9860                 this._containerWidth = this._container.offsetWidth;
9861         },
9862
9863         _animateZoom: function (e) {
9864                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9865                     anchor = this._getAnchor();
9866                 setPosition(this._container, pos.add(anchor));
9867         },
9868
9869         _adjustPan: function () {
9870                 if (!this.options.autoPan) { return; }
9871                 if (this._map._panAnim) { this._map._panAnim.stop(); }
9872
9873                 var map = this._map,
9874                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9875                     containerHeight = this._container.offsetHeight + marginBottom,
9876                     containerWidth = this._containerWidth,
9877                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9878
9879                 layerPos._add(getPosition(this._container));
9880
9881                 var containerPos = map.layerPointToContainerPoint(layerPos),
9882                     padding = toPoint(this.options.autoPanPadding),
9883                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9884                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9885                     size = map.getSize(),
9886                     dx = 0,
9887                     dy = 0;
9888
9889                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9890                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9891                 }
9892                 if (containerPos.x - dx - paddingTL.x < 0) { // left
9893                         dx = containerPos.x - paddingTL.x;
9894                 }
9895                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9896                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9897                 }
9898                 if (containerPos.y - dy - paddingTL.y < 0) { // top
9899                         dy = containerPos.y - paddingTL.y;
9900                 }
9901
9902                 // @namespace Map
9903                 // @section Popup events
9904                 // @event autopanstart: Event
9905                 // Fired when the map starts autopanning when opening a popup.
9906                 if (dx || dy) {
9907                         map
9908                             .fire('autopanstart')
9909                             .panBy([dx, dy]);
9910                 }
9911         },
9912
9913         _onCloseButtonClick: function (e) {
9914                 this._close();
9915                 stop(e);
9916         },
9917
9918         _getAnchor: function () {
9919                 // Where should we anchor the popup on the source layer?
9920                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9921         }
9922
9923 });
9924
9925 // @namespace Popup
9926 // @factory L.popup(options?: Popup options, source?: Layer)
9927 // 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.
9928 var popup = function (options, source) {
9929         return new Popup(options, source);
9930 };
9931
9932
9933 /* @namespace Map
9934  * @section Interaction Options
9935  * @option closePopupOnClick: Boolean = true
9936  * Set it to `false` if you don't want popups to close when user clicks the map.
9937  */
9938 Map.mergeOptions({
9939         closePopupOnClick: true
9940 });
9941
9942
9943 // @namespace Map
9944 // @section Methods for Layers and Controls
9945 Map.include({
9946         // @method openPopup(popup: Popup): this
9947         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9948         // @alternative
9949         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9950         // Creates a popup with the specified content and options and opens it in the given point on a map.
9951         openPopup: function (popup, latlng, options) {
9952                 if (!(popup instanceof Popup)) {
9953                         popup = new Popup(options).setContent(popup);
9954                 }
9955
9956                 if (latlng) {
9957                         popup.setLatLng(latlng);
9958                 }
9959
9960                 if (this.hasLayer(popup)) {
9961                         return this;
9962                 }
9963
9964                 if (this._popup && this._popup.options.autoClose) {
9965                         this.closePopup();
9966                 }
9967
9968                 this._popup = popup;
9969                 return this.addLayer(popup);
9970         },
9971
9972         // @method closePopup(popup?: Popup): this
9973         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9974         closePopup: function (popup) {
9975                 if (!popup || popup === this._popup) {
9976                         popup = this._popup;
9977                         this._popup = null;
9978                 }
9979                 if (popup) {
9980                         this.removeLayer(popup);
9981                 }
9982                 return this;
9983         }
9984 });
9985
9986 /*
9987  * @namespace Layer
9988  * @section Popup methods example
9989  *
9990  * All layers share a set of methods convenient for binding popups to it.
9991  *
9992  * ```js
9993  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9994  * layer.openPopup();
9995  * layer.closePopup();
9996  * ```
9997  *
9998  * 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.
9999  */
10000
10001 // @section Popup methods
10002 Layer.include({
10003
10004         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
10005         // Binds a popup to the layer with the passed `content` and sets up the
10006         // necessary event listeners. If a `Function` is passed it will receive
10007         // the layer as the first argument and should return a `String` or `HTMLElement`.
10008         bindPopup: function (content, options) {
10009
10010                 if (content instanceof Popup) {
10011                         setOptions(content, options);
10012                         this._popup = content;
10013                         content._source = this;
10014                 } else {
10015                         if (!this._popup || options) {
10016                                 this._popup = new Popup(options, this);
10017                         }
10018                         this._popup.setContent(content);
10019                 }
10020
10021                 if (!this._popupHandlersAdded) {
10022                         this.on({
10023                                 click: this._openPopup,
10024                                 keypress: this._onKeyPress,
10025                                 remove: this.closePopup,
10026                                 move: this._movePopup
10027                         });
10028                         this._popupHandlersAdded = true;
10029                 }
10030
10031                 return this;
10032         },
10033
10034         // @method unbindPopup(): this
10035         // Removes the popup previously bound with `bindPopup`.
10036         unbindPopup: function () {
10037                 if (this._popup) {
10038                         this.off({
10039                                 click: this._openPopup,
10040                                 keypress: this._onKeyPress,
10041                                 remove: this.closePopup,
10042                                 move: this._movePopup
10043                         });
10044                         this._popupHandlersAdded = false;
10045                         this._popup = null;
10046                 }
10047                 return this;
10048         },
10049
10050         // @method openPopup(latlng?: LatLng): this
10051         // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
10052         openPopup: function (layer, latlng) {
10053                 if (this._popup && this._map) {
10054                         latlng = this._popup._prepareOpen(this, layer, latlng);
10055
10056                         // open the popup on the map
10057                         this._map.openPopup(this._popup, latlng);
10058                 }
10059
10060                 return this;
10061         },
10062
10063         // @method closePopup(): this
10064         // Closes the popup bound to this layer if it is open.
10065         closePopup: function () {
10066                 if (this._popup) {
10067                         this._popup._close();
10068                 }
10069                 return this;
10070         },
10071
10072         // @method togglePopup(): this
10073         // Opens or closes the popup bound to this layer depending on its current state.
10074         togglePopup: function (target) {
10075                 if (this._popup) {
10076                         if (this._popup._map) {
10077                                 this.closePopup();
10078                         } else {
10079                                 this.openPopup(target);
10080                         }
10081                 }
10082                 return this;
10083         },
10084
10085         // @method isPopupOpen(): boolean
10086         // Returns `true` if the popup bound to this layer is currently open.
10087         isPopupOpen: function () {
10088                 return (this._popup ? this._popup.isOpen() : false);
10089         },
10090
10091         // @method setPopupContent(content: String|HTMLElement|Popup): this
10092         // Sets the content of the popup bound to this layer.
10093         setPopupContent: function (content) {
10094                 if (this._popup) {
10095                         this._popup.setContent(content);
10096                 }
10097                 return this;
10098         },
10099
10100         // @method getPopup(): Popup
10101         // Returns the popup bound to this layer.
10102         getPopup: function () {
10103                 return this._popup;
10104         },
10105
10106         _openPopup: function (e) {
10107                 var layer = e.layer || e.target;
10108
10109                 if (!this._popup) {
10110                         return;
10111                 }
10112
10113                 if (!this._map) {
10114                         return;
10115                 }
10116
10117                 // prevent map click
10118                 stop(e);
10119
10120                 // if this inherits from Path its a vector and we can just
10121                 // open the popup at the new location
10122                 if (layer instanceof Path) {
10123                         this.openPopup(e.layer || e.target, e.latlng);
10124                         return;
10125                 }
10126
10127                 // otherwise treat it like a marker and figure out
10128                 // if we should toggle it open/closed
10129                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
10130                         this.closePopup();
10131                 } else {
10132                         this.openPopup(layer, e.latlng);
10133                 }
10134         },
10135
10136         _movePopup: function (e) {
10137                 this._popup.setLatLng(e.latlng);
10138         },
10139
10140         _onKeyPress: function (e) {
10141                 if (e.originalEvent.keyCode === 13) {
10142                         this._openPopup(e);
10143                 }
10144         }
10145 });
10146
10147 /*
10148  * @class Tooltip
10149  * @inherits DivOverlay
10150  * @aka L.Tooltip
10151  * Used to display small texts on top of map layers.
10152  *
10153  * @example
10154  *
10155  * ```js
10156  * marker.bindTooltip("my tooltip text").openTooltip();
10157  * ```
10158  * Note about tooltip offset. Leaflet takes two options in consideration
10159  * for computing tooltip offsetting:
10160  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10161  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
10162  *   move it to the bottom. Negatives will move to the left and top.
10163  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10164  *   should adapt this value if you use a custom icon.
10165  */
10166
10167
10168 // @namespace Tooltip
10169 var Tooltip = DivOverlay.extend({
10170
10171         // @section
10172         // @aka Tooltip options
10173         options: {
10174                 // @option pane: String = 'tooltipPane'
10175                 // `Map pane` where the tooltip will be added.
10176                 pane: 'tooltipPane',
10177
10178                 // @option offset: Point = Point(0, 0)
10179                 // Optional offset of the tooltip position.
10180                 offset: [0, 0],
10181
10182                 // @option direction: String = 'auto'
10183                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10184                 // `top`, `bottom`, `center`, `auto`.
10185                 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10186                 // position on the map.
10187                 direction: 'auto',
10188
10189                 // @option permanent: Boolean = false
10190                 // Whether to open the tooltip permanently or only on mouseover.
10191                 permanent: false,
10192
10193                 // @option sticky: Boolean = false
10194                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10195                 sticky: false,
10196
10197                 // @option interactive: Boolean = false
10198                 // If true, the tooltip will listen to the feature events.
10199                 interactive: false,
10200
10201                 // @option opacity: Number = 0.9
10202                 // Tooltip container opacity.
10203                 opacity: 0.9
10204         },
10205
10206         onAdd: function (map) {
10207                 DivOverlay.prototype.onAdd.call(this, map);
10208                 this.setOpacity(this.options.opacity);
10209
10210                 // @namespace Map
10211                 // @section Tooltip events
10212                 // @event tooltipopen: TooltipEvent
10213                 // Fired when a tooltip is opened in the map.
10214                 map.fire('tooltipopen', {tooltip: this});
10215
10216                 if (this._source) {
10217                         // @namespace Layer
10218                         // @section Tooltip events
10219                         // @event tooltipopen: TooltipEvent
10220                         // Fired when a tooltip bound to this layer is opened.
10221                         this._source.fire('tooltipopen', {tooltip: this}, true);
10222                 }
10223         },
10224
10225         onRemove: function (map) {
10226                 DivOverlay.prototype.onRemove.call(this, map);
10227
10228                 // @namespace Map
10229                 // @section Tooltip events
10230                 // @event tooltipclose: TooltipEvent
10231                 // Fired when a tooltip in the map is closed.
10232                 map.fire('tooltipclose', {tooltip: this});
10233
10234                 if (this._source) {
10235                         // @namespace Layer
10236                         // @section Tooltip events
10237                         // @event tooltipclose: TooltipEvent
10238                         // Fired when a tooltip bound to this layer is closed.
10239                         this._source.fire('tooltipclose', {tooltip: this}, true);
10240                 }
10241         },
10242
10243         getEvents: function () {
10244                 var events = DivOverlay.prototype.getEvents.call(this);
10245
10246                 if (touch && !this.options.permanent) {
10247                         events.preclick = this._close;
10248                 }
10249
10250                 return events;
10251         },
10252
10253         _close: function () {
10254                 if (this._map) {
10255                         this._map.closeTooltip(this);
10256                 }
10257         },
10258
10259         _initLayout: function () {
10260                 var prefix = 'leaflet-tooltip',
10261                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10262
10263                 this._contentNode = this._container = create$1('div', className);
10264         },
10265
10266         _updateLayout: function () {},
10267
10268         _adjustPan: function () {},
10269
10270         _setPosition: function (pos) {
10271                 var map = this._map,
10272                     container = this._container,
10273                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
10274                     tooltipPoint = map.layerPointToContainerPoint(pos),
10275                     direction = this.options.direction,
10276                     tooltipWidth = container.offsetWidth,
10277                     tooltipHeight = container.offsetHeight,
10278                     offset = toPoint(this.options.offset),
10279                     anchor = this._getAnchor();
10280
10281                 if (direction === 'top') {
10282                         pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10283                 } else if (direction === 'bottom') {
10284                         pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10285                 } else if (direction === 'center') {
10286                         pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10287                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10288                         direction = 'right';
10289                         pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10290                 } else {
10291                         direction = 'left';
10292                         pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10293                 }
10294
10295                 removeClass(container, 'leaflet-tooltip-right');
10296                 removeClass(container, 'leaflet-tooltip-left');
10297                 removeClass(container, 'leaflet-tooltip-top');
10298                 removeClass(container, 'leaflet-tooltip-bottom');
10299                 addClass(container, 'leaflet-tooltip-' + direction);
10300                 setPosition(container, pos);
10301         },
10302
10303         _updatePosition: function () {
10304                 var pos = this._map.latLngToLayerPoint(this._latlng);
10305                 this._setPosition(pos);
10306         },
10307
10308         setOpacity: function (opacity) {
10309                 this.options.opacity = opacity;
10310
10311                 if (this._container) {
10312                         setOpacity(this._container, opacity);
10313                 }
10314         },
10315
10316         _animateZoom: function (e) {
10317                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10318                 this._setPosition(pos);
10319         },
10320
10321         _getAnchor: function () {
10322                 // Where should we anchor the tooltip on the source layer?
10323                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10324         }
10325
10326 });
10327
10328 // @namespace Tooltip
10329 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10330 // 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.
10331 var tooltip = function (options, source) {
10332         return new Tooltip(options, source);
10333 };
10334
10335 // @namespace Map
10336 // @section Methods for Layers and Controls
10337 Map.include({
10338
10339         // @method openTooltip(tooltip: Tooltip): this
10340         // Opens the specified tooltip.
10341         // @alternative
10342         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10343         // Creates a tooltip with the specified content and options and open it.
10344         openTooltip: function (tooltip, latlng, options) {
10345                 if (!(tooltip instanceof Tooltip)) {
10346                         tooltip = new Tooltip(options).setContent(tooltip);
10347                 }
10348
10349                 if (latlng) {
10350                         tooltip.setLatLng(latlng);
10351                 }
10352
10353                 if (this.hasLayer(tooltip)) {
10354                         return this;
10355                 }
10356
10357                 return this.addLayer(tooltip);
10358         },
10359
10360         // @method closeTooltip(tooltip?: Tooltip): this
10361         // Closes the tooltip given as parameter.
10362         closeTooltip: function (tooltip) {
10363                 if (tooltip) {
10364                         this.removeLayer(tooltip);
10365                 }
10366                 return this;
10367         }
10368
10369 });
10370
10371 /*
10372  * @namespace Layer
10373  * @section Tooltip methods example
10374  *
10375  * All layers share a set of methods convenient for binding tooltips to it.
10376  *
10377  * ```js
10378  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10379  * layer.openTooltip();
10380  * layer.closeTooltip();
10381  * ```
10382  */
10383
10384 // @section Tooltip methods
10385 Layer.include({
10386
10387         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10388         // Binds a tooltip to the layer with the passed `content` and sets up the
10389         // necessary event listeners. If a `Function` is passed it will receive
10390         // the layer as the first argument and should return a `String` or `HTMLElement`.
10391         bindTooltip: function (content, options) {
10392
10393                 if (content instanceof Tooltip) {
10394                         setOptions(content, options);
10395                         this._tooltip = content;
10396                         content._source = this;
10397                 } else {
10398                         if (!this._tooltip || options) {
10399                                 this._tooltip = new Tooltip(options, this);
10400                         }
10401                         this._tooltip.setContent(content);
10402
10403                 }
10404
10405                 this._initTooltipInteractions();
10406
10407                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10408                         this.openTooltip();
10409                 }
10410
10411                 return this;
10412         },
10413
10414         // @method unbindTooltip(): this
10415         // Removes the tooltip previously bound with `bindTooltip`.
10416         unbindTooltip: function () {
10417                 if (this._tooltip) {
10418                         this._initTooltipInteractions(true);
10419                         this.closeTooltip();
10420                         this._tooltip = null;
10421                 }
10422                 return this;
10423         },
10424
10425         _initTooltipInteractions: function (remove$$1) {
10426                 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10427                 var onOff = remove$$1 ? 'off' : 'on',
10428                     events = {
10429                         remove: this.closeTooltip,
10430                         move: this._moveTooltip
10431                     };
10432                 if (!this._tooltip.options.permanent) {
10433                         events.mouseover = this._openTooltip;
10434                         events.mouseout = this.closeTooltip;
10435                         if (this._tooltip.options.sticky) {
10436                                 events.mousemove = this._moveTooltip;
10437                         }
10438                         if (touch) {
10439                                 events.click = this._openTooltip;
10440                         }
10441                 } else {
10442                         events.add = this._openTooltip;
10443                 }
10444                 this[onOff](events);
10445                 this._tooltipHandlersAdded = !remove$$1;
10446         },
10447
10448         // @method openTooltip(latlng?: LatLng): this
10449         // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10450         openTooltip: function (layer, latlng) {
10451                 if (this._tooltip && this._map) {
10452                         latlng = this._tooltip._prepareOpen(this, layer, latlng);
10453
10454                         // open the tooltip on the map
10455                         this._map.openTooltip(this._tooltip, latlng);
10456
10457                         // Tooltip container may not be defined if not permanent and never
10458                         // opened.
10459                         if (this._tooltip.options.interactive && this._tooltip._container) {
10460                                 addClass(this._tooltip._container, 'leaflet-clickable');
10461                                 this.addInteractiveTarget(this._tooltip._container);
10462                         }
10463                 }
10464
10465                 return this;
10466         },
10467
10468         // @method closeTooltip(): this
10469         // Closes the tooltip bound to this layer if it is open.
10470         closeTooltip: function () {
10471                 if (this._tooltip) {
10472                         this._tooltip._close();
10473                         if (this._tooltip.options.interactive && this._tooltip._container) {
10474                                 removeClass(this._tooltip._container, 'leaflet-clickable');
10475                                 this.removeInteractiveTarget(this._tooltip._container);
10476                         }
10477                 }
10478                 return this;
10479         },
10480
10481         // @method toggleTooltip(): this
10482         // Opens or closes the tooltip bound to this layer depending on its current state.
10483         toggleTooltip: function (target) {
10484                 if (this._tooltip) {
10485                         if (this._tooltip._map) {
10486                                 this.closeTooltip();
10487                         } else {
10488                                 this.openTooltip(target);
10489                         }
10490                 }
10491                 return this;
10492         },
10493
10494         // @method isTooltipOpen(): boolean
10495         // Returns `true` if the tooltip bound to this layer is currently open.
10496         isTooltipOpen: function () {
10497                 return this._tooltip.isOpen();
10498         },
10499
10500         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10501         // Sets the content of the tooltip bound to this layer.
10502         setTooltipContent: function (content) {
10503                 if (this._tooltip) {
10504                         this._tooltip.setContent(content);
10505                 }
10506                 return this;
10507         },
10508
10509         // @method getTooltip(): Tooltip
10510         // Returns the tooltip bound to this layer.
10511         getTooltip: function () {
10512                 return this._tooltip;
10513         },
10514
10515         _openTooltip: function (e) {
10516                 var layer = e.layer || e.target;
10517
10518                 if (!this._tooltip || !this._map) {
10519                         return;
10520                 }
10521                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10522         },
10523
10524         _moveTooltip: function (e) {
10525                 var latlng = e.latlng, containerPoint, layerPoint;
10526                 if (this._tooltip.options.sticky && e.originalEvent) {
10527                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10528                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10529                         latlng = this._map.layerPointToLatLng(layerPoint);
10530                 }
10531                 this._tooltip.setLatLng(latlng);
10532         }
10533 });
10534
10535 /*
10536  * @class DivIcon
10537  * @aka L.DivIcon
10538  * @inherits Icon
10539  *
10540  * Represents a lightweight icon for markers that uses a simple `<div>`
10541  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10542  *
10543  * @example
10544  * ```js
10545  * var myIcon = L.divIcon({className: 'my-div-icon'});
10546  * // you can set .my-div-icon styles in CSS
10547  *
10548  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10549  * ```
10550  *
10551  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10552  */
10553
10554 var DivIcon = Icon.extend({
10555         options: {
10556                 // @section
10557                 // @aka DivIcon options
10558                 iconSize: [12, 12], // also can be set through CSS
10559
10560                 // iconAnchor: (Point),
10561                 // popupAnchor: (Point),
10562
10563                 // @option html: String|HTMLElement = ''
10564                 // Custom HTML code to put inside the div element, empty by default. Alternatively,
10565                 // an instance of `HTMLElement`.
10566                 html: false,
10567
10568                 // @option bgPos: Point = [0, 0]
10569                 // Optional relative position of the background, in pixels
10570                 bgPos: null,
10571
10572                 className: 'leaflet-div-icon'
10573         },
10574
10575         createIcon: function (oldIcon) {
10576                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10577                     options = this.options;
10578
10579                 if (options.html instanceof Element) {
10580                         empty(div);
10581                         div.appendChild(options.html);
10582                 } else {
10583                         div.innerHTML = options.html !== false ? options.html : '';
10584                 }
10585
10586                 if (options.bgPos) {
10587                         var bgPos = toPoint(options.bgPos);
10588                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10589                 }
10590                 this._setIconStyles(div, 'icon');
10591
10592                 return div;
10593         },
10594
10595         createShadow: function () {
10596                 return null;
10597         }
10598 });
10599
10600 // @factory L.divIcon(options: DivIcon options)
10601 // Creates a `DivIcon` instance with the given options.
10602 function divIcon(options) {
10603         return new DivIcon(options);
10604 }
10605
10606 Icon.Default = IconDefault;
10607
10608 /*
10609  * @class GridLayer
10610  * @inherits Layer
10611  * @aka L.GridLayer
10612  *
10613  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10614  * 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.
10615  *
10616  *
10617  * @section Synchronous usage
10618  * @example
10619  *
10620  * 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.
10621  *
10622  * ```js
10623  * var CanvasLayer = L.GridLayer.extend({
10624  *     createTile: function(coords){
10625  *         // create a <canvas> element for drawing
10626  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10627  *
10628  *         // setup tile width and height according to the options
10629  *         var size = this.getTileSize();
10630  *         tile.width = size.x;
10631  *         tile.height = size.y;
10632  *
10633  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10634  *         var ctx = tile.getContext('2d');
10635  *
10636  *         // return the tile so it can be rendered on screen
10637  *         return tile;
10638  *     }
10639  * });
10640  * ```
10641  *
10642  * @section Asynchronous usage
10643  * @example
10644  *
10645  * 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.
10646  *
10647  * ```js
10648  * var CanvasLayer = L.GridLayer.extend({
10649  *     createTile: function(coords, done){
10650  *         var error;
10651  *
10652  *         // create a <canvas> element for drawing
10653  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10654  *
10655  *         // setup tile width and height according to the options
10656  *         var size = this.getTileSize();
10657  *         tile.width = size.x;
10658  *         tile.height = size.y;
10659  *
10660  *         // draw something asynchronously and pass the tile to the done() callback
10661  *         setTimeout(function() {
10662  *             done(error, tile);
10663  *         }, 1000);
10664  *
10665  *         return tile;
10666  *     }
10667  * });
10668  * ```
10669  *
10670  * @section
10671  */
10672
10673
10674 var GridLayer = Layer.extend({
10675
10676         // @section
10677         // @aka GridLayer options
10678         options: {
10679                 // @option tileSize: Number|Point = 256
10680                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10681                 tileSize: 256,
10682
10683                 // @option opacity: Number = 1.0
10684                 // Opacity of the tiles. Can be used in the `createTile()` function.
10685                 opacity: 1,
10686
10687                 // @option updateWhenIdle: Boolean = (depends)
10688                 // Load new tiles only when panning ends.
10689                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10690                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10691                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10692                 updateWhenIdle: mobile,
10693
10694                 // @option updateWhenZooming: Boolean = true
10695                 // 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.
10696                 updateWhenZooming: true,
10697
10698                 // @option updateInterval: Number = 200
10699                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10700                 updateInterval: 200,
10701
10702                 // @option zIndex: Number = 1
10703                 // The explicit zIndex of the tile layer.
10704                 zIndex: 1,
10705
10706                 // @option bounds: LatLngBounds = undefined
10707                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10708                 bounds: null,
10709
10710                 // @option minZoom: Number = 0
10711                 // The minimum zoom level down to which this layer will be displayed (inclusive).
10712                 minZoom: 0,
10713
10714                 // @option maxZoom: Number = undefined
10715                 // The maximum zoom level up to which this layer will be displayed (inclusive).
10716                 maxZoom: undefined,
10717
10718                 // @option maxNativeZoom: Number = undefined
10719                 // Maximum zoom number the tile source has available. If it is specified,
10720                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10721                 // from `maxNativeZoom` level and auto-scaled.
10722                 maxNativeZoom: undefined,
10723
10724                 // @option minNativeZoom: Number = undefined
10725                 // Minimum zoom number the tile source has available. If it is specified,
10726                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10727                 // from `minNativeZoom` level and auto-scaled.
10728                 minNativeZoom: undefined,
10729
10730                 // @option noWrap: Boolean = false
10731                 // Whether the layer is wrapped around the antimeridian. If `true`, the
10732                 // GridLayer will only be displayed once at low zoom levels. Has no
10733                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10734                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10735                 // tiles outside the CRS limits.
10736                 noWrap: false,
10737
10738                 // @option pane: String = 'tilePane'
10739                 // `Map pane` where the grid layer will be added.
10740                 pane: 'tilePane',
10741
10742                 // @option className: String = ''
10743                 // A custom class name to assign to the tile layer. Empty by default.
10744                 className: '',
10745
10746                 // @option keepBuffer: Number = 2
10747                 // When panning the map, keep this many rows and columns of tiles before unloading them.
10748                 keepBuffer: 2
10749         },
10750
10751         initialize: function (options) {
10752                 setOptions(this, options);
10753         },
10754
10755         onAdd: function () {
10756                 this._initContainer();
10757
10758                 this._levels = {};
10759                 this._tiles = {};
10760
10761                 this._resetView();
10762                 this._update();
10763         },
10764
10765         beforeAdd: function (map) {
10766                 map._addZoomLimit(this);
10767         },
10768
10769         onRemove: function (map) {
10770                 this._removeAllTiles();
10771                 remove(this._container);
10772                 map._removeZoomLimit(this);
10773                 this._container = null;
10774                 this._tileZoom = undefined;
10775         },
10776
10777         // @method bringToFront: this
10778         // Brings the tile layer to the top of all tile layers.
10779         bringToFront: function () {
10780                 if (this._map) {
10781                         toFront(this._container);
10782                         this._setAutoZIndex(Math.max);
10783                 }
10784                 return this;
10785         },
10786
10787         // @method bringToBack: this
10788         // Brings the tile layer to the bottom of all tile layers.
10789         bringToBack: function () {
10790                 if (this._map) {
10791                         toBack(this._container);
10792                         this._setAutoZIndex(Math.min);
10793                 }
10794                 return this;
10795         },
10796
10797         // @method getContainer: HTMLElement
10798         // Returns the HTML element that contains the tiles for this layer.
10799         getContainer: function () {
10800                 return this._container;
10801         },
10802
10803         // @method setOpacity(opacity: Number): this
10804         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10805         setOpacity: function (opacity) {
10806                 this.options.opacity = opacity;
10807                 this._updateOpacity();
10808                 return this;
10809         },
10810
10811         // @method setZIndex(zIndex: Number): this
10812         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10813         setZIndex: function (zIndex) {
10814                 this.options.zIndex = zIndex;
10815                 this._updateZIndex();
10816
10817                 return this;
10818         },
10819
10820         // @method isLoading: Boolean
10821         // Returns `true` if any tile in the grid layer has not finished loading.
10822         isLoading: function () {
10823                 return this._loading;
10824         },
10825
10826         // @method redraw: this
10827         // Causes the layer to clear all the tiles and request them again.
10828         redraw: function () {
10829                 if (this._map) {
10830                         this._removeAllTiles();
10831                         this._update();
10832                 }
10833                 return this;
10834         },
10835
10836         getEvents: function () {
10837                 var events = {
10838                         viewprereset: this._invalidateAll,
10839                         viewreset: this._resetView,
10840                         zoom: this._resetView,
10841                         moveend: this._onMoveEnd
10842                 };
10843
10844                 if (!this.options.updateWhenIdle) {
10845                         // update tiles on move, but not more often than once per given interval
10846                         if (!this._onMove) {
10847                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10848                         }
10849
10850                         events.move = this._onMove;
10851                 }
10852
10853                 if (this._zoomAnimated) {
10854                         events.zoomanim = this._animateZoom;
10855                 }
10856
10857                 return events;
10858         },
10859
10860         // @section Extension methods
10861         // Layers extending `GridLayer` shall reimplement the following method.
10862         // @method createTile(coords: Object, done?: Function): HTMLElement
10863         // Called only internally, must be overridden by classes extending `GridLayer`.
10864         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10865         // is specified, it must be called when the tile has finished loading and drawing.
10866         createTile: function () {
10867                 return document.createElement('div');
10868         },
10869
10870         // @section
10871         // @method getTileSize: Point
10872         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10873         getTileSize: function () {
10874                 var s = this.options.tileSize;
10875                 return s instanceof Point ? s : new Point(s, s);
10876         },
10877
10878         _updateZIndex: function () {
10879                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10880                         this._container.style.zIndex = this.options.zIndex;
10881                 }
10882         },
10883
10884         _setAutoZIndex: function (compare) {
10885                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10886
10887                 var layers = this.getPane().children,
10888                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10889
10890                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10891
10892                         zIndex = layers[i].style.zIndex;
10893
10894                         if (layers[i] !== this._container && zIndex) {
10895                                 edgeZIndex = compare(edgeZIndex, +zIndex);
10896                         }
10897                 }
10898
10899                 if (isFinite(edgeZIndex)) {
10900                         this.options.zIndex = edgeZIndex + compare(-1, 1);
10901                         this._updateZIndex();
10902                 }
10903         },
10904
10905         _updateOpacity: function () {
10906                 if (!this._map) { return; }
10907
10908                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10909                 if (ielt9) { return; }
10910
10911                 setOpacity(this._container, this.options.opacity);
10912
10913                 var now = +new Date(),
10914                     nextFrame = false,
10915                     willPrune = false;
10916
10917                 for (var key in this._tiles) {
10918                         var tile = this._tiles[key];
10919                         if (!tile.current || !tile.loaded) { continue; }
10920
10921                         var fade = Math.min(1, (now - tile.loaded) / 200);
10922
10923                         setOpacity(tile.el, fade);
10924                         if (fade < 1) {
10925                                 nextFrame = true;
10926                         } else {
10927                                 if (tile.active) {
10928                                         willPrune = true;
10929                                 } else {
10930                                         this._onOpaqueTile(tile);
10931                                 }
10932                                 tile.active = true;
10933                         }
10934                 }
10935
10936                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10937
10938                 if (nextFrame) {
10939                         cancelAnimFrame(this._fadeFrame);
10940                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10941                 }
10942         },
10943
10944         _onOpaqueTile: falseFn,
10945
10946         _initContainer: function () {
10947                 if (this._container) { return; }
10948
10949                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10950                 this._updateZIndex();
10951
10952                 if (this.options.opacity < 1) {
10953                         this._updateOpacity();
10954                 }
10955
10956                 this.getPane().appendChild(this._container);
10957         },
10958
10959         _updateLevels: function () {
10960
10961                 var zoom = this._tileZoom,
10962                     maxZoom = this.options.maxZoom;
10963
10964                 if (zoom === undefined) { return undefined; }
10965
10966                 for (var z in this._levels) {
10967                         if (this._levels[z].el.children.length || z === zoom) {
10968                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10969                                 this._onUpdateLevel(z);
10970                         } else {
10971                                 remove(this._levels[z].el);
10972                                 this._removeTilesAtZoom(z);
10973                                 this._onRemoveLevel(z);
10974                                 delete this._levels[z];
10975                         }
10976                 }
10977
10978                 var level = this._levels[zoom],
10979                     map = this._map;
10980
10981                 if (!level) {
10982                         level = this._levels[zoom] = {};
10983
10984                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10985                         level.el.style.zIndex = maxZoom;
10986
10987                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10988                         level.zoom = zoom;
10989
10990                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
10991
10992                         // force the browser to consider the newly added element for transition
10993                         falseFn(level.el.offsetWidth);
10994
10995                         this._onCreateLevel(level);
10996                 }
10997
10998                 this._level = level;
10999
11000                 return level;
11001         },
11002
11003         _onUpdateLevel: falseFn,
11004
11005         _onRemoveLevel: falseFn,
11006
11007         _onCreateLevel: falseFn,
11008
11009         _pruneTiles: function () {
11010                 if (!this._map) {
11011                         return;
11012                 }
11013
11014                 var key, tile;
11015
11016                 var zoom = this._map.getZoom();
11017                 if (zoom > this.options.maxZoom ||
11018                         zoom < this.options.minZoom) {
11019                         this._removeAllTiles();
11020                         return;
11021                 }
11022
11023                 for (key in this._tiles) {
11024                         tile = this._tiles[key];
11025                         tile.retain = tile.current;
11026                 }
11027
11028                 for (key in this._tiles) {
11029                         tile = this._tiles[key];
11030                         if (tile.current && !tile.active) {
11031                                 var coords = tile.coords;
11032                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
11033                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
11034                                 }
11035                         }
11036                 }
11037
11038                 for (key in this._tiles) {
11039                         if (!this._tiles[key].retain) {
11040                                 this._removeTile(key);
11041                         }
11042                 }
11043         },
11044
11045         _removeTilesAtZoom: function (zoom) {
11046                 for (var key in this._tiles) {
11047                         if (this._tiles[key].coords.z !== zoom) {
11048                                 continue;
11049                         }
11050                         this._removeTile(key);
11051                 }
11052         },
11053
11054         _removeAllTiles: function () {
11055                 for (var key in this._tiles) {
11056                         this._removeTile(key);
11057                 }
11058         },
11059
11060         _invalidateAll: function () {
11061                 for (var z in this._levels) {
11062                         remove(this._levels[z].el);
11063                         this._onRemoveLevel(z);
11064                         delete this._levels[z];
11065                 }
11066                 this._removeAllTiles();
11067
11068                 this._tileZoom = undefined;
11069         },
11070
11071         _retainParent: function (x, y, z, minZoom) {
11072                 var x2 = Math.floor(x / 2),
11073                     y2 = Math.floor(y / 2),
11074                     z2 = z - 1,
11075                     coords2 = new Point(+x2, +y2);
11076                 coords2.z = +z2;
11077
11078                 var key = this._tileCoordsToKey(coords2),
11079                     tile = this._tiles[key];
11080
11081                 if (tile && tile.active) {
11082                         tile.retain = true;
11083                         return true;
11084
11085                 } else if (tile && tile.loaded) {
11086                         tile.retain = true;
11087                 }
11088
11089                 if (z2 > minZoom) {
11090                         return this._retainParent(x2, y2, z2, minZoom);
11091                 }
11092
11093                 return false;
11094         },
11095
11096         _retainChildren: function (x, y, z, maxZoom) {
11097
11098                 for (var i = 2 * x; i < 2 * x + 2; i++) {
11099                         for (var j = 2 * y; j < 2 * y + 2; j++) {
11100
11101                                 var coords = new Point(i, j);
11102                                 coords.z = z + 1;
11103
11104                                 var key = this._tileCoordsToKey(coords),
11105                                     tile = this._tiles[key];
11106
11107                                 if (tile && tile.active) {
11108                                         tile.retain = true;
11109                                         continue;
11110
11111                                 } else if (tile && tile.loaded) {
11112                                         tile.retain = true;
11113                                 }
11114
11115                                 if (z + 1 < maxZoom) {
11116                                         this._retainChildren(i, j, z + 1, maxZoom);
11117                                 }
11118                         }
11119                 }
11120         },
11121
11122         _resetView: function (e) {
11123                 var animating = e && (e.pinch || e.flyTo);
11124                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
11125         },
11126
11127         _animateZoom: function (e) {
11128                 this._setView(e.center, e.zoom, true, e.noUpdate);
11129         },
11130
11131         _clampZoom: function (zoom) {
11132                 var options = this.options;
11133
11134                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
11135                         return options.minNativeZoom;
11136                 }
11137
11138                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
11139                         return options.maxNativeZoom;
11140                 }
11141
11142                 return zoom;
11143         },
11144
11145         _setView: function (center, zoom, noPrune, noUpdate) {
11146                 var tileZoom = this._clampZoom(Math.round(zoom));
11147                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11148                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11149                         tileZoom = undefined;
11150                 }
11151
11152                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11153
11154                 if (!noUpdate || tileZoomChanged) {
11155
11156                         this._tileZoom = tileZoom;
11157
11158                         if (this._abortLoading) {
11159                                 this._abortLoading();
11160                         }
11161
11162                         this._updateLevels();
11163                         this._resetGrid();
11164
11165                         if (tileZoom !== undefined) {
11166                                 this._update(center);
11167                         }
11168
11169                         if (!noPrune) {
11170                                 this._pruneTiles();
11171                         }
11172
11173                         // Flag to prevent _updateOpacity from pruning tiles during
11174                         // a zoom anim or a pinch gesture
11175                         this._noPrune = !!noPrune;
11176                 }
11177
11178                 this._setZoomTransforms(center, zoom);
11179         },
11180
11181         _setZoomTransforms: function (center, zoom) {
11182                 for (var i in this._levels) {
11183                         this._setZoomTransform(this._levels[i], center, zoom);
11184                 }
11185         },
11186
11187         _setZoomTransform: function (level, center, zoom) {
11188                 var scale = this._map.getZoomScale(zoom, level.zoom),
11189                     translate = level.origin.multiplyBy(scale)
11190                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11191
11192                 if (any3d) {
11193                         setTransform(level.el, translate, scale);
11194                 } else {
11195                         setPosition(level.el, translate);
11196                 }
11197         },
11198
11199         _resetGrid: function () {
11200                 var map = this._map,
11201                     crs = map.options.crs,
11202                     tileSize = this._tileSize = this.getTileSize(),
11203                     tileZoom = this._tileZoom;
11204
11205                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11206                 if (bounds) {
11207                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
11208                 }
11209
11210                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11211                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11212                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11213                 ];
11214                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11215                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11216                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11217                 ];
11218         },
11219
11220         _onMoveEnd: function () {
11221                 if (!this._map || this._map._animatingZoom) { return; }
11222
11223                 this._update();
11224         },
11225
11226         _getTiledPixelBounds: function (center) {
11227                 var map = this._map,
11228                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11229                     scale = map.getZoomScale(mapZoom, this._tileZoom),
11230                     pixelCenter = map.project(center, this._tileZoom).floor(),
11231                     halfSize = map.getSize().divideBy(scale * 2);
11232
11233                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11234         },
11235
11236         // Private method to load tiles in the grid's active zoom level according to map bounds
11237         _update: function (center) {
11238                 var map = this._map;
11239                 if (!map) { return; }
11240                 var zoom = this._clampZoom(map.getZoom());
11241
11242                 if (center === undefined) { center = map.getCenter(); }
11243                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
11244
11245                 var pixelBounds = this._getTiledPixelBounds(center),
11246                     tileRange = this._pxBoundsToTileRange(pixelBounds),
11247                     tileCenter = tileRange.getCenter(),
11248                     queue = [],
11249                     margin = this.options.keepBuffer,
11250                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11251                                               tileRange.getTopRight().add([margin, -margin]));
11252
11253                 // Sanity check: panic if the tile range contains Infinity somewhere.
11254                 if (!(isFinite(tileRange.min.x) &&
11255                       isFinite(tileRange.min.y) &&
11256                       isFinite(tileRange.max.x) &&
11257                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11258
11259                 for (var key in this._tiles) {
11260                         var c = this._tiles[key].coords;
11261                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11262                                 this._tiles[key].current = false;
11263                         }
11264                 }
11265
11266                 // _update just loads more tiles. If the tile zoom level differs too much
11267                 // from the map's, let _setView reset levels and prune old tiles.
11268                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11269
11270                 // create a queue of coordinates to load tiles from
11271                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11272                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11273                                 var coords = new Point(i, j);
11274                                 coords.z = this._tileZoom;
11275
11276                                 if (!this._isValidTile(coords)) { continue; }
11277
11278                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
11279                                 if (tile) {
11280                                         tile.current = true;
11281                                 } else {
11282                                         queue.push(coords);
11283                                 }
11284                         }
11285                 }
11286
11287                 // sort tile queue to load tiles in order of their distance to center
11288                 queue.sort(function (a, b) {
11289                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11290                 });
11291
11292                 if (queue.length !== 0) {
11293                         // if it's the first batch of tiles to load
11294                         if (!this._loading) {
11295                                 this._loading = true;
11296                                 // @event loading: Event
11297                                 // Fired when the grid layer starts loading tiles.
11298                                 this.fire('loading');
11299                         }
11300
11301                         // create DOM fragment to append tiles in one batch
11302                         var fragment = document.createDocumentFragment();
11303
11304                         for (i = 0; i < queue.length; i++) {
11305                                 this._addTile(queue[i], fragment);
11306                         }
11307
11308                         this._level.el.appendChild(fragment);
11309                 }
11310         },
11311
11312         _isValidTile: function (coords) {
11313                 var crs = this._map.options.crs;
11314
11315                 if (!crs.infinite) {
11316                         // don't load tile if it's out of bounds and not wrapped
11317                         var bounds = this._globalTileRange;
11318                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11319                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11320                 }
11321
11322                 if (!this.options.bounds) { return true; }
11323
11324                 // don't load tile if it doesn't intersect the bounds in options
11325                 var tileBounds = this._tileCoordsToBounds(coords);
11326                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11327         },
11328
11329         _keyToBounds: function (key) {
11330                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11331         },
11332
11333         _tileCoordsToNwSe: function (coords) {
11334                 var map = this._map,
11335                     tileSize = this.getTileSize(),
11336                     nwPoint = coords.scaleBy(tileSize),
11337                     sePoint = nwPoint.add(tileSize),
11338                     nw = map.unproject(nwPoint, coords.z),
11339                     se = map.unproject(sePoint, coords.z);
11340                 return [nw, se];
11341         },
11342
11343         // converts tile coordinates to its geographical bounds
11344         _tileCoordsToBounds: function (coords) {
11345                 var bp = this._tileCoordsToNwSe(coords),
11346                     bounds = new LatLngBounds(bp[0], bp[1]);
11347
11348                 if (!this.options.noWrap) {
11349                         bounds = this._map.wrapLatLngBounds(bounds);
11350                 }
11351                 return bounds;
11352         },
11353         // converts tile coordinates to key for the tile cache
11354         _tileCoordsToKey: function (coords) {
11355                 return coords.x + ':' + coords.y + ':' + coords.z;
11356         },
11357
11358         // converts tile cache key to coordinates
11359         _keyToTileCoords: function (key) {
11360                 var k = key.split(':'),
11361                     coords = new Point(+k[0], +k[1]);
11362                 coords.z = +k[2];
11363                 return coords;
11364         },
11365
11366         _removeTile: function (key) {
11367                 var tile = this._tiles[key];
11368                 if (!tile) { return; }
11369
11370                 remove(tile.el);
11371
11372                 delete this._tiles[key];
11373
11374                 // @event tileunload: TileEvent
11375                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11376                 this.fire('tileunload', {
11377                         tile: tile.el,
11378                         coords: this._keyToTileCoords(key)
11379                 });
11380         },
11381
11382         _initTile: function (tile) {
11383                 addClass(tile, 'leaflet-tile');
11384
11385                 var tileSize = this.getTileSize();
11386                 tile.style.width = tileSize.x + 'px';
11387                 tile.style.height = tileSize.y + 'px';
11388
11389                 tile.onselectstart = falseFn;
11390                 tile.onmousemove = falseFn;
11391
11392                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11393                 if (ielt9 && this.options.opacity < 1) {
11394                         setOpacity(tile, this.options.opacity);
11395                 }
11396
11397                 // without this hack, tiles disappear after zoom on Chrome for Android
11398                 // https://github.com/Leaflet/Leaflet/issues/2078
11399                 if (android && !android23) {
11400                         tile.style.WebkitBackfaceVisibility = 'hidden';
11401                 }
11402         },
11403
11404         _addTile: function (coords, container) {
11405                 var tilePos = this._getTilePos(coords),
11406                     key = this._tileCoordsToKey(coords);
11407
11408                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11409
11410                 this._initTile(tile);
11411
11412                 // if createTile is defined with a second argument ("done" callback),
11413                 // we know that tile is async and will be ready later; otherwise
11414                 if (this.createTile.length < 2) {
11415                         // mark tile as ready, but delay one frame for opacity animation to happen
11416                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11417                 }
11418
11419                 setPosition(tile, tilePos);
11420
11421                 // save tile in cache
11422                 this._tiles[key] = {
11423                         el: tile,
11424                         coords: coords,
11425                         current: true
11426                 };
11427
11428                 container.appendChild(tile);
11429                 // @event tileloadstart: TileEvent
11430                 // Fired when a tile is requested and starts loading.
11431                 this.fire('tileloadstart', {
11432                         tile: tile,
11433                         coords: coords
11434                 });
11435         },
11436
11437         _tileReady: function (coords, err, tile) {
11438                 if (err) {
11439                         // @event tileerror: TileErrorEvent
11440                         // Fired when there is an error loading a tile.
11441                         this.fire('tileerror', {
11442                                 error: err,
11443                                 tile: tile,
11444                                 coords: coords
11445                         });
11446                 }
11447
11448                 var key = this._tileCoordsToKey(coords);
11449
11450                 tile = this._tiles[key];
11451                 if (!tile) { return; }
11452
11453                 tile.loaded = +new Date();
11454                 if (this._map._fadeAnimated) {
11455                         setOpacity(tile.el, 0);
11456                         cancelAnimFrame(this._fadeFrame);
11457                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11458                 } else {
11459                         tile.active = true;
11460                         this._pruneTiles();
11461                 }
11462
11463                 if (!err) {
11464                         addClass(tile.el, 'leaflet-tile-loaded');
11465
11466                         // @event tileload: TileEvent
11467                         // Fired when a tile loads.
11468                         this.fire('tileload', {
11469                                 tile: tile.el,
11470                                 coords: coords
11471                         });
11472                 }
11473
11474                 if (this._noTilesToLoad()) {
11475                         this._loading = false;
11476                         // @event load: Event
11477                         // Fired when the grid layer loaded all visible tiles.
11478                         this.fire('load');
11479
11480                         if (ielt9 || !this._map._fadeAnimated) {
11481                                 requestAnimFrame(this._pruneTiles, this);
11482                         } else {
11483                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11484                                 // to trigger a pruning.
11485                                 setTimeout(bind(this._pruneTiles, this), 250);
11486                         }
11487                 }
11488         },
11489
11490         _getTilePos: function (coords) {
11491                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11492         },
11493
11494         _wrapCoords: function (coords) {
11495                 var newCoords = new Point(
11496                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11497                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11498                 newCoords.z = coords.z;
11499                 return newCoords;
11500         },
11501
11502         _pxBoundsToTileRange: function (bounds) {
11503                 var tileSize = this.getTileSize();
11504                 return new Bounds(
11505                         bounds.min.unscaleBy(tileSize).floor(),
11506                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11507         },
11508
11509         _noTilesToLoad: function () {
11510                 for (var key in this._tiles) {
11511                         if (!this._tiles[key].loaded) { return false; }
11512                 }
11513                 return true;
11514         }
11515 });
11516
11517 // @factory L.gridLayer(options?: GridLayer options)
11518 // Creates a new instance of GridLayer with the supplied options.
11519 function gridLayer(options) {
11520         return new GridLayer(options);
11521 }
11522
11523 /*
11524  * @class TileLayer
11525  * @inherits GridLayer
11526  * @aka L.TileLayer
11527  * 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`.
11528  *
11529  * @example
11530  *
11531  * ```js
11532  * 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);
11533  * ```
11534  *
11535  * @section URL template
11536  * @example
11537  *
11538  * A string of the following form:
11539  *
11540  * ```
11541  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11542  * ```
11543  *
11544  * `{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.
11545  *
11546  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11547  *
11548  * ```
11549  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11550  * ```
11551  */
11552
11553
11554 var TileLayer = GridLayer.extend({
11555
11556         // @section
11557         // @aka TileLayer options
11558         options: {
11559                 // @option minZoom: Number = 0
11560                 // The minimum zoom level down to which this layer will be displayed (inclusive).
11561                 minZoom: 0,
11562
11563                 // @option maxZoom: Number = 18
11564                 // The maximum zoom level up to which this layer will be displayed (inclusive).
11565                 maxZoom: 18,
11566
11567                 // @option subdomains: String|String[] = 'abc'
11568                 // 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.
11569                 subdomains: 'abc',
11570
11571                 // @option errorTileUrl: String = ''
11572                 // URL to the tile image to show in place of the tile that failed to load.
11573                 errorTileUrl: '',
11574
11575                 // @option zoomOffset: Number = 0
11576                 // The zoom number used in tile URLs will be offset with this value.
11577                 zoomOffset: 0,
11578
11579                 // @option tms: Boolean = false
11580                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11581                 tms: false,
11582
11583                 // @option zoomReverse: Boolean = false
11584                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11585                 zoomReverse: false,
11586
11587                 // @option detectRetina: Boolean = false
11588                 // 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.
11589                 detectRetina: false,
11590
11591                 // @option crossOrigin: Boolean|String = false
11592                 // Whether the crossOrigin attribute will be added to the tiles.
11593                 // 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.
11594                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11595                 crossOrigin: false
11596         },
11597
11598         initialize: function (url, options) {
11599
11600                 this._url = url;
11601
11602                 options = setOptions(this, options);
11603
11604                 // detecting retina displays, adjusting tileSize and zoom levels
11605                 if (options.detectRetina && retina && options.maxZoom > 0) {
11606
11607                         options.tileSize = Math.floor(options.tileSize / 2);
11608
11609                         if (!options.zoomReverse) {
11610                                 options.zoomOffset++;
11611                                 options.maxZoom--;
11612                         } else {
11613                                 options.zoomOffset--;
11614                                 options.minZoom++;
11615                         }
11616
11617                         options.minZoom = Math.max(0, options.minZoom);
11618                 }
11619
11620                 if (typeof options.subdomains === 'string') {
11621                         options.subdomains = options.subdomains.split('');
11622                 }
11623
11624                 // for https://github.com/Leaflet/Leaflet/issues/137
11625                 if (!android) {
11626                         this.on('tileunload', this._onTileRemove);
11627                 }
11628         },
11629
11630         // @method setUrl(url: String, noRedraw?: Boolean): this
11631         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11632         // If the URL does not change, the layer will not be redrawn unless
11633         // the noRedraw parameter is set to false.
11634         setUrl: function (url, noRedraw) {
11635                 if (this._url === url && noRedraw === undefined) {
11636                         noRedraw = true;
11637                 }
11638
11639                 this._url = url;
11640
11641                 if (!noRedraw) {
11642                         this.redraw();
11643                 }
11644                 return this;
11645         },
11646
11647         // @method createTile(coords: Object, done?: Function): HTMLElement
11648         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11649         // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11650         // callback is called when the tile has been loaded.
11651         createTile: function (coords, done) {
11652                 var tile = document.createElement('img');
11653
11654                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11655                 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11656
11657                 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11658                         tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11659                 }
11660
11661                 /*
11662                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11663                  http://www.w3.org/TR/WCAG20-TECHS/H67
11664                 */
11665                 tile.alt = '';
11666
11667                 /*
11668                  Set role="presentation" to force screen readers to ignore this
11669                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11670                 */
11671                 tile.setAttribute('role', 'presentation');
11672
11673                 tile.src = this.getTileUrl(coords);
11674
11675                 return tile;
11676         },
11677
11678         // @section Extension methods
11679         // @uninheritable
11680         // Layers extending `TileLayer` might reimplement the following method.
11681         // @method getTileUrl(coords: Object): String
11682         // Called only internally, returns the URL for a tile given its coordinates.
11683         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11684         getTileUrl: function (coords) {
11685                 var data = {
11686                         r: retina ? '@2x' : '',
11687                         s: this._getSubdomain(coords),
11688                         x: coords.x,
11689                         y: coords.y,
11690                         z: this._getZoomForUrl()
11691                 };
11692                 if (this._map && !this._map.options.crs.infinite) {
11693                         var invertedY = this._globalTileRange.max.y - coords.y;
11694                         if (this.options.tms) {
11695                                 data['y'] = invertedY;
11696                         }
11697                         data['-y'] = invertedY;
11698                 }
11699
11700                 return template(this._url, extend(data, this.options));
11701         },
11702
11703         _tileOnLoad: function (done, tile) {
11704                 // For https://github.com/Leaflet/Leaflet/issues/3332
11705                 if (ielt9) {
11706                         setTimeout(bind(done, this, null, tile), 0);
11707                 } else {
11708                         done(null, tile);
11709                 }
11710         },
11711
11712         _tileOnError: function (done, tile, e) {
11713                 var errorUrl = this.options.errorTileUrl;
11714                 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11715                         tile.src = errorUrl;
11716                 }
11717                 done(e, tile);
11718         },
11719
11720         _onTileRemove: function (e) {
11721                 e.tile.onload = null;
11722         },
11723
11724         _getZoomForUrl: function () {
11725                 var zoom = this._tileZoom,
11726                 maxZoom = this.options.maxZoom,
11727                 zoomReverse = this.options.zoomReverse,
11728                 zoomOffset = this.options.zoomOffset;
11729
11730                 if (zoomReverse) {
11731                         zoom = maxZoom - zoom;
11732                 }
11733
11734                 return zoom + zoomOffset;
11735         },
11736
11737         _getSubdomain: function (tilePoint) {
11738                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11739                 return this.options.subdomains[index];
11740         },
11741
11742         // stops loading all tiles in the background layer
11743         _abortLoading: function () {
11744                 var i, tile;
11745                 for (i in this._tiles) {
11746                         if (this._tiles[i].coords.z !== this._tileZoom) {
11747                                 tile = this._tiles[i].el;
11748
11749                                 tile.onload = falseFn;
11750                                 tile.onerror = falseFn;
11751
11752                                 if (!tile.complete) {
11753                                         tile.src = emptyImageUrl;
11754                                         remove(tile);
11755                                         delete this._tiles[i];
11756                                 }
11757                         }
11758                 }
11759         },
11760
11761         _removeTile: function (key) {
11762                 var tile = this._tiles[key];
11763                 if (!tile) { return; }
11764
11765                 // Cancels any pending http requests associated with the tile
11766                 // unless we're on Android's stock browser,
11767                 // see https://github.com/Leaflet/Leaflet/issues/137
11768                 if (!androidStock) {
11769                         tile.el.setAttribute('src', emptyImageUrl);
11770                 }
11771
11772                 return GridLayer.prototype._removeTile.call(this, key);
11773         },
11774
11775         _tileReady: function (coords, err, tile) {
11776                 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11777                         return;
11778                 }
11779
11780                 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11781         }
11782 });
11783
11784
11785 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11786 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11787
11788 function tileLayer(url, options) {
11789         return new TileLayer(url, options);
11790 }
11791
11792 /*
11793  * @class TileLayer.WMS
11794  * @inherits TileLayer
11795  * @aka L.TileLayer.WMS
11796  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11797  *
11798  * @example
11799  *
11800  * ```js
11801  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11802  *      layers: 'nexrad-n0r-900913',
11803  *      format: 'image/png',
11804  *      transparent: true,
11805  *      attribution: "Weather data © 2012 IEM Nexrad"
11806  * });
11807  * ```
11808  */
11809
11810 var TileLayerWMS = TileLayer.extend({
11811
11812         // @section
11813         // @aka TileLayer.WMS options
11814         // If any custom options not documented here are used, they will be sent to the
11815         // WMS server as extra parameters in each request URL. This can be useful for
11816         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11817         defaultWmsParams: {
11818                 service: 'WMS',
11819                 request: 'GetMap',
11820
11821                 // @option layers: String = ''
11822                 // **(required)** Comma-separated list of WMS layers to show.
11823                 layers: '',
11824
11825                 // @option styles: String = ''
11826                 // Comma-separated list of WMS styles.
11827                 styles: '',
11828
11829                 // @option format: String = 'image/jpeg'
11830                 // WMS image format (use `'image/png'` for layers with transparency).
11831                 format: 'image/jpeg',
11832
11833                 // @option transparent: Boolean = false
11834                 // If `true`, the WMS service will return images with transparency.
11835                 transparent: false,
11836
11837                 // @option version: String = '1.1.1'
11838                 // Version of the WMS service to use
11839                 version: '1.1.1'
11840         },
11841
11842         options: {
11843                 // @option crs: CRS = null
11844                 // Coordinate Reference System to use for the WMS requests, defaults to
11845                 // map CRS. Don't change this if you're not sure what it means.
11846                 crs: null,
11847
11848                 // @option uppercase: Boolean = false
11849                 // If `true`, WMS request parameter keys will be uppercase.
11850                 uppercase: false
11851         },
11852
11853         initialize: function (url, options) {
11854
11855                 this._url = url;
11856
11857                 var wmsParams = extend({}, this.defaultWmsParams);
11858
11859                 // all keys that are not TileLayer options go to WMS params
11860                 for (var i in options) {
11861                         if (!(i in this.options)) {
11862                                 wmsParams[i] = options[i];
11863                         }
11864                 }
11865
11866                 options = setOptions(this, options);
11867
11868                 var realRetina = options.detectRetina && retina ? 2 : 1;
11869                 var tileSize = this.getTileSize();
11870                 wmsParams.width = tileSize.x * realRetina;
11871                 wmsParams.height = tileSize.y * realRetina;
11872
11873                 this.wmsParams = wmsParams;
11874         },
11875
11876         onAdd: function (map) {
11877
11878                 this._crs = this.options.crs || map.options.crs;
11879                 this._wmsVersion = parseFloat(this.wmsParams.version);
11880
11881                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11882                 this.wmsParams[projectionKey] = this._crs.code;
11883
11884                 TileLayer.prototype.onAdd.call(this, map);
11885         },
11886
11887         getTileUrl: function (coords) {
11888
11889                 var tileBounds = this._tileCoordsToNwSe(coords),
11890                     crs = this._crs,
11891                     bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11892                     min = bounds.min,
11893                     max = bounds.max,
11894                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11895                     [min.y, min.x, max.y, max.x] :
11896                     [min.x, min.y, max.x, max.y]).join(','),
11897                     url = TileLayer.prototype.getTileUrl.call(this, coords);
11898                 return url +
11899                         getParamString(this.wmsParams, url, this.options.uppercase) +
11900                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11901         },
11902
11903         // @method setParams(params: Object, noRedraw?: Boolean): this
11904         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11905         setParams: function (params, noRedraw) {
11906
11907                 extend(this.wmsParams, params);
11908
11909                 if (!noRedraw) {
11910                         this.redraw();
11911                 }
11912
11913                 return this;
11914         }
11915 });
11916
11917
11918 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11919 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11920 function tileLayerWMS(url, options) {
11921         return new TileLayerWMS(url, options);
11922 }
11923
11924 TileLayer.WMS = TileLayerWMS;
11925 tileLayer.wms = tileLayerWMS;
11926
11927 /*
11928  * @class Renderer
11929  * @inherits Layer
11930  * @aka L.Renderer
11931  *
11932  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11933  * DOM container of the renderer, its bounds, and its zoom animation.
11934  *
11935  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11936  * itself can be added or removed to the map. All paths use a renderer, which can
11937  * be implicit (the map will decide the type of renderer and use it automatically)
11938  * or explicit (using the [`renderer`](#path-renderer) option of the path).
11939  *
11940  * Do not use this class directly, use `SVG` and `Canvas` instead.
11941  *
11942  * @event update: Event
11943  * Fired when the renderer updates its bounds, center and zoom, for example when
11944  * its map has moved
11945  */
11946
11947 var Renderer = Layer.extend({
11948
11949         // @section
11950         // @aka Renderer options
11951         options: {
11952                 // @option padding: Number = 0.1
11953                 // How much to extend the clip area around the map view (relative to its size)
11954                 // e.g. 0.1 would be 10% of map view in each direction
11955                 padding: 0.1,
11956
11957                 // @option tolerance: Number = 0
11958                 // How much to extend click tolerance round a path/object on the map
11959                 tolerance : 0
11960         },
11961
11962         initialize: function (options) {
11963                 setOptions(this, options);
11964                 stamp(this);
11965                 this._layers = this._layers || {};
11966         },
11967
11968         onAdd: function () {
11969                 if (!this._container) {
11970                         this._initContainer(); // defined by renderer implementations
11971
11972                         if (this._zoomAnimated) {
11973                                 addClass(this._container, 'leaflet-zoom-animated');
11974                         }
11975                 }
11976
11977                 this.getPane().appendChild(this._container);
11978                 this._update();
11979                 this.on('update', this._updatePaths, this);
11980         },
11981
11982         onRemove: function () {
11983                 this.off('update', this._updatePaths, this);
11984                 this._destroyContainer();
11985         },
11986
11987         getEvents: function () {
11988                 var events = {
11989                         viewreset: this._reset,
11990                         zoom: this._onZoom,
11991                         moveend: this._update,
11992                         zoomend: this._onZoomEnd
11993                 };
11994                 if (this._zoomAnimated) {
11995                         events.zoomanim = this._onAnimZoom;
11996                 }
11997                 return events;
11998         },
11999
12000         _onAnimZoom: function (ev) {
12001                 this._updateTransform(ev.center, ev.zoom);
12002         },
12003
12004         _onZoom: function () {
12005                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
12006         },
12007
12008         _updateTransform: function (center, zoom) {
12009                 var scale = this._map.getZoomScale(zoom, this._zoom),
12010                     position = getPosition(this._container),
12011                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
12012                     currentCenterPoint = this._map.project(this._center, zoom),
12013                     destCenterPoint = this._map.project(center, zoom),
12014                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
12015
12016                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
12017
12018                 if (any3d) {
12019                         setTransform(this._container, topLeftOffset, scale);
12020                 } else {
12021                         setPosition(this._container, topLeftOffset);
12022                 }
12023         },
12024
12025         _reset: function () {
12026                 this._update();
12027                 this._updateTransform(this._center, this._zoom);
12028
12029                 for (var id in this._layers) {
12030                         this._layers[id]._reset();
12031                 }
12032         },
12033
12034         _onZoomEnd: function () {
12035                 for (var id in this._layers) {
12036                         this._layers[id]._project();
12037                 }
12038         },
12039
12040         _updatePaths: function () {
12041                 for (var id in this._layers) {
12042                         this._layers[id]._update();
12043                 }
12044         },
12045
12046         _update: function () {
12047                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
12048                 // Subclasses are responsible of firing the 'update' event.
12049                 var p = this.options.padding,
12050                     size = this._map.getSize(),
12051                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
12052
12053                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
12054
12055                 this._center = this._map.getCenter();
12056                 this._zoom = this._map.getZoom();
12057         }
12058 });
12059
12060 /*
12061  * @class Canvas
12062  * @inherits Renderer
12063  * @aka L.Canvas
12064  *
12065  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
12066  * Inherits `Renderer`.
12067  *
12068  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
12069  * available in all web browsers, notably IE8, and overlapping geometries might
12070  * not display properly in some edge cases.
12071  *
12072  * @example
12073  *
12074  * Use Canvas by default for all paths in the map:
12075  *
12076  * ```js
12077  * var map = L.map('map', {
12078  *      renderer: L.canvas()
12079  * });
12080  * ```
12081  *
12082  * Use a Canvas renderer with extra padding for specific vector geometries:
12083  *
12084  * ```js
12085  * var map = L.map('map');
12086  * var myRenderer = L.canvas({ padding: 0.5 });
12087  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12088  * var circle = L.circle( center, { renderer: myRenderer } );
12089  * ```
12090  */
12091
12092 var Canvas = Renderer.extend({
12093         getEvents: function () {
12094                 var events = Renderer.prototype.getEvents.call(this);
12095                 events.viewprereset = this._onViewPreReset;
12096                 return events;
12097         },
12098
12099         _onViewPreReset: function () {
12100                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
12101                 this._postponeUpdatePaths = true;
12102         },
12103
12104         onAdd: function () {
12105                 Renderer.prototype.onAdd.call(this);
12106
12107                 // Redraw vectors since canvas is cleared upon removal,
12108                 // in case of removing the renderer itself from the map.
12109                 this._draw();
12110         },
12111
12112         _initContainer: function () {
12113                 var container = this._container = document.createElement('canvas');
12114
12115                 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
12116                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
12117                 on(container, 'mouseout', this._handleMouseOut, this);
12118
12119                 this._ctx = container.getContext('2d');
12120         },
12121
12122         _destroyContainer: function () {
12123                 cancelAnimFrame(this._redrawRequest);
12124                 delete this._ctx;
12125                 remove(this._container);
12126                 off(this._container);
12127                 delete this._container;
12128         },
12129
12130         _updatePaths: function () {
12131                 if (this._postponeUpdatePaths) { return; }
12132
12133                 var layer;
12134                 this._redrawBounds = null;
12135                 for (var id in this._layers) {
12136                         layer = this._layers[id];
12137                         layer._update();
12138                 }
12139                 this._redraw();
12140         },
12141
12142         _update: function () {
12143                 if (this._map._animatingZoom && this._bounds) { return; }
12144
12145                 Renderer.prototype._update.call(this);
12146
12147                 var b = this._bounds,
12148                     container = this._container,
12149                     size = b.getSize(),
12150                     m = retina ? 2 : 1;
12151
12152                 setPosition(container, b.min);
12153
12154                 // set canvas size (also clearing it); use double size on retina
12155                 container.width = m * size.x;
12156                 container.height = m * size.y;
12157                 container.style.width = size.x + 'px';
12158                 container.style.height = size.y + 'px';
12159
12160                 if (retina) {
12161                         this._ctx.scale(2, 2);
12162                 }
12163
12164                 // translate so we use the same path coordinates after canvas element moves
12165                 this._ctx.translate(-b.min.x, -b.min.y);
12166
12167                 // Tell paths to redraw themselves
12168                 this.fire('update');
12169         },
12170
12171         _reset: function () {
12172                 Renderer.prototype._reset.call(this);
12173
12174                 if (this._postponeUpdatePaths) {
12175                         this._postponeUpdatePaths = false;
12176                         this._updatePaths();
12177                 }
12178         },
12179
12180         _initPath: function (layer) {
12181                 this._updateDashArray(layer);
12182                 this._layers[stamp(layer)] = layer;
12183
12184                 var order = layer._order = {
12185                         layer: layer,
12186                         prev: this._drawLast,
12187                         next: null
12188                 };
12189                 if (this._drawLast) { this._drawLast.next = order; }
12190                 this._drawLast = order;
12191                 this._drawFirst = this._drawFirst || this._drawLast;
12192         },
12193
12194         _addPath: function (layer) {
12195                 this._requestRedraw(layer);
12196         },
12197
12198         _removePath: function (layer) {
12199                 var order = layer._order;
12200                 var next = order.next;
12201                 var prev = order.prev;
12202
12203                 if (next) {
12204                         next.prev = prev;
12205                 } else {
12206                         this._drawLast = prev;
12207                 }
12208                 if (prev) {
12209                         prev.next = next;
12210                 } else {
12211                         this._drawFirst = next;
12212                 }
12213
12214                 delete layer._order;
12215
12216                 delete this._layers[stamp(layer)];
12217
12218                 this._requestRedraw(layer);
12219         },
12220
12221         _updatePath: function (layer) {
12222                 // Redraw the union of the layer's old pixel
12223                 // bounds and the new pixel bounds.
12224                 this._extendRedrawBounds(layer);
12225                 layer._project();
12226                 layer._update();
12227                 // The redraw will extend the redraw bounds
12228                 // with the new pixel bounds.
12229                 this._requestRedraw(layer);
12230         },
12231
12232         _updateStyle: function (layer) {
12233                 this._updateDashArray(layer);
12234                 this._requestRedraw(layer);
12235         },
12236
12237         _updateDashArray: function (layer) {
12238                 if (typeof layer.options.dashArray === 'string') {
12239                         var parts = layer.options.dashArray.split(/[, ]+/),
12240                             dashArray = [],
12241                             dashValue,
12242                             i;
12243                         for (i = 0; i < parts.length; i++) {
12244                                 dashValue = Number(parts[i]);
12245                                 // Ignore dash array containing invalid lengths
12246                                 if (isNaN(dashValue)) { return; }
12247                                 dashArray.push(dashValue);
12248                         }
12249                         layer.options._dashArray = dashArray;
12250                 } else {
12251                         layer.options._dashArray = layer.options.dashArray;
12252                 }
12253         },
12254
12255         _requestRedraw: function (layer) {
12256                 if (!this._map) { return; }
12257
12258                 this._extendRedrawBounds(layer);
12259                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12260         },
12261
12262         _extendRedrawBounds: function (layer) {
12263                 if (layer._pxBounds) {
12264                         var padding = (layer.options.weight || 0) + 1;
12265                         this._redrawBounds = this._redrawBounds || new Bounds();
12266                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12267                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12268                 }
12269         },
12270
12271         _redraw: function () {
12272                 this._redrawRequest = null;
12273
12274                 if (this._redrawBounds) {
12275                         this._redrawBounds.min._floor();
12276                         this._redrawBounds.max._ceil();
12277                 }
12278
12279                 this._clear(); // clear layers in redraw bounds
12280                 this._draw(); // draw layers
12281
12282                 this._redrawBounds = null;
12283         },
12284
12285         _clear: function () {
12286                 var bounds = this._redrawBounds;
12287                 if (bounds) {
12288                         var size = bounds.getSize();
12289                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12290                 } else {
12291                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12292                 }
12293         },
12294
12295         _draw: function () {
12296                 var layer, bounds = this._redrawBounds;
12297                 this._ctx.save();
12298                 if (bounds) {
12299                         var size = bounds.getSize();
12300                         this._ctx.beginPath();
12301                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12302                         this._ctx.clip();
12303                 }
12304
12305                 this._drawing = true;
12306
12307                 for (var order = this._drawFirst; order; order = order.next) {
12308                         layer = order.layer;
12309                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12310                                 layer._updatePath();
12311                         }
12312                 }
12313
12314                 this._drawing = false;
12315
12316                 this._ctx.restore();  // Restore state before clipping.
12317         },
12318
12319         _updatePoly: function (layer, closed) {
12320                 if (!this._drawing) { return; }
12321
12322                 var i, j, len2, p,
12323                     parts = layer._parts,
12324                     len = parts.length,
12325                     ctx = this._ctx;
12326
12327                 if (!len) { return; }
12328
12329                 ctx.beginPath();
12330
12331                 for (i = 0; i < len; i++) {
12332                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
12333                                 p = parts[i][j];
12334                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12335                         }
12336                         if (closed) {
12337                                 ctx.closePath();
12338                         }
12339                 }
12340
12341                 this._fillStroke(ctx, layer);
12342
12343                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12344         },
12345
12346         _updateCircle: function (layer) {
12347
12348                 if (!this._drawing || layer._empty()) { return; }
12349
12350                 var p = layer._point,
12351                     ctx = this._ctx,
12352                     r = Math.max(Math.round(layer._radius), 1),
12353                     s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12354
12355                 if (s !== 1) {
12356                         ctx.save();
12357                         ctx.scale(1, s);
12358                 }
12359
12360                 ctx.beginPath();
12361                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12362
12363                 if (s !== 1) {
12364                         ctx.restore();
12365                 }
12366
12367                 this._fillStroke(ctx, layer);
12368         },
12369
12370         _fillStroke: function (ctx, layer) {
12371                 var options = layer.options;
12372
12373                 if (options.fill) {
12374                         ctx.globalAlpha = options.fillOpacity;
12375                         ctx.fillStyle = options.fillColor || options.color;
12376                         ctx.fill(options.fillRule || 'evenodd');
12377                 }
12378
12379                 if (options.stroke && options.weight !== 0) {
12380                         if (ctx.setLineDash) {
12381                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12382                         }
12383                         ctx.globalAlpha = options.opacity;
12384                         ctx.lineWidth = options.weight;
12385                         ctx.strokeStyle = options.color;
12386                         ctx.lineCap = options.lineCap;
12387                         ctx.lineJoin = options.lineJoin;
12388                         ctx.stroke();
12389                 }
12390         },
12391
12392         // Canvas obviously doesn't have mouse events for individual drawn objects,
12393         // so we emulate that by calculating what's under the mouse on mousemove/click manually
12394
12395         _onClick: function (e) {
12396                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12397
12398                 for (var order = this._drawFirst; order; order = order.next) {
12399                         layer = order.layer;
12400                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12401                                 clickedLayer = layer;
12402                         }
12403                 }
12404                 if (clickedLayer)  {
12405                         fakeStop(e);
12406                         this._fireEvent([clickedLayer], e);
12407                 }
12408         },
12409
12410         _onMouseMove: function (e) {
12411                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12412
12413                 var point = this._map.mouseEventToLayerPoint(e);
12414                 this._handleMouseHover(e, point);
12415         },
12416
12417
12418         _handleMouseOut: function (e) {
12419                 var layer = this._hoveredLayer;
12420                 if (layer) {
12421                         // if we're leaving the layer, fire mouseout
12422                         removeClass(this._container, 'leaflet-interactive');
12423                         this._fireEvent([layer], e, 'mouseout');
12424                         this._hoveredLayer = null;
12425                 }
12426         },
12427
12428         _handleMouseHover: function (e, point) {
12429                 var layer, candidateHoveredLayer;
12430
12431                 for (var order = this._drawFirst; order; order = order.next) {
12432                         layer = order.layer;
12433                         if (layer.options.interactive && layer._containsPoint(point)) {
12434                                 candidateHoveredLayer = layer;
12435                         }
12436                 }
12437
12438                 if (candidateHoveredLayer !== this._hoveredLayer) {
12439                         this._handleMouseOut(e);
12440
12441                         if (candidateHoveredLayer) {
12442                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12443                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12444                                 this._hoveredLayer = candidateHoveredLayer;
12445                         }
12446                 }
12447
12448                 if (this._hoveredLayer) {
12449                         this._fireEvent([this._hoveredLayer], e);
12450                 }
12451         },
12452
12453         _fireEvent: function (layers, e, type) {
12454                 this._map._fireDOMEvent(e, type || e.type, layers);
12455         },
12456
12457         _bringToFront: function (layer) {
12458                 var order = layer._order;
12459
12460                 if (!order) { return; }
12461
12462                 var next = order.next;
12463                 var prev = order.prev;
12464
12465                 if (next) {
12466                         next.prev = prev;
12467                 } else {
12468                         // Already last
12469                         return;
12470                 }
12471                 if (prev) {
12472                         prev.next = next;
12473                 } else if (next) {
12474                         // Update first entry unless this is the
12475                         // single entry
12476                         this._drawFirst = next;
12477                 }
12478
12479                 order.prev = this._drawLast;
12480                 this._drawLast.next = order;
12481
12482                 order.next = null;
12483                 this._drawLast = order;
12484
12485                 this._requestRedraw(layer);
12486         },
12487
12488         _bringToBack: function (layer) {
12489                 var order = layer._order;
12490
12491                 if (!order) { return; }
12492
12493                 var next = order.next;
12494                 var prev = order.prev;
12495
12496                 if (prev) {
12497                         prev.next = next;
12498                 } else {
12499                         // Already first
12500                         return;
12501                 }
12502                 if (next) {
12503                         next.prev = prev;
12504                 } else if (prev) {
12505                         // Update last entry unless this is the
12506                         // single entry
12507                         this._drawLast = prev;
12508                 }
12509
12510                 order.prev = null;
12511
12512                 order.next = this._drawFirst;
12513                 this._drawFirst.prev = order;
12514                 this._drawFirst = order;
12515
12516                 this._requestRedraw(layer);
12517         }
12518 });
12519
12520 // @factory L.canvas(options?: Renderer options)
12521 // Creates a Canvas renderer with the given options.
12522 function canvas$1(options) {
12523         return canvas ? new Canvas(options) : null;
12524 }
12525
12526 /*
12527  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12528  */
12529
12530
12531 var vmlCreate = (function () {
12532         try {
12533                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12534                 return function (name) {
12535                         return document.createElement('<lvml:' + name + ' class="lvml">');
12536                 };
12537         } catch (e) {
12538                 return function (name) {
12539                         return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12540                 };
12541         }
12542 })();
12543
12544
12545 /*
12546  * @class SVG
12547  *
12548  *
12549  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12550  * with old versions of Internet Explorer.
12551  */
12552
12553 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12554 var vmlMixin = {
12555
12556         _initContainer: function () {
12557                 this._container = create$1('div', 'leaflet-vml-container');
12558         },
12559
12560         _update: function () {
12561                 if (this._map._animatingZoom) { return; }
12562                 Renderer.prototype._update.call(this);
12563                 this.fire('update');
12564         },
12565
12566         _initPath: function (layer) {
12567                 var container = layer._container = vmlCreate('shape');
12568
12569                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12570
12571                 container.coordsize = '1 1';
12572
12573                 layer._path = vmlCreate('path');
12574                 container.appendChild(layer._path);
12575
12576                 this._updateStyle(layer);
12577                 this._layers[stamp(layer)] = layer;
12578         },
12579
12580         _addPath: function (layer) {
12581                 var container = layer._container;
12582                 this._container.appendChild(container);
12583
12584                 if (layer.options.interactive) {
12585                         layer.addInteractiveTarget(container);
12586                 }
12587         },
12588
12589         _removePath: function (layer) {
12590                 var container = layer._container;
12591                 remove(container);
12592                 layer.removeInteractiveTarget(container);
12593                 delete this._layers[stamp(layer)];
12594         },
12595
12596         _updateStyle: function (layer) {
12597                 var stroke = layer._stroke,
12598                     fill = layer._fill,
12599                     options = layer.options,
12600                     container = layer._container;
12601
12602                 container.stroked = !!options.stroke;
12603                 container.filled = !!options.fill;
12604
12605                 if (options.stroke) {
12606                         if (!stroke) {
12607                                 stroke = layer._stroke = vmlCreate('stroke');
12608                         }
12609                         container.appendChild(stroke);
12610                         stroke.weight = options.weight + 'px';
12611                         stroke.color = options.color;
12612                         stroke.opacity = options.opacity;
12613
12614                         if (options.dashArray) {
12615                                 stroke.dashStyle = isArray(options.dashArray) ?
12616                                     options.dashArray.join(' ') :
12617                                     options.dashArray.replace(/( *, *)/g, ' ');
12618                         } else {
12619                                 stroke.dashStyle = '';
12620                         }
12621                         stroke.endcap = options.lineCap.replace('butt', 'flat');
12622                         stroke.joinstyle = options.lineJoin;
12623
12624                 } else if (stroke) {
12625                         container.removeChild(stroke);
12626                         layer._stroke = null;
12627                 }
12628
12629                 if (options.fill) {
12630                         if (!fill) {
12631                                 fill = layer._fill = vmlCreate('fill');
12632                         }
12633                         container.appendChild(fill);
12634                         fill.color = options.fillColor || options.color;
12635                         fill.opacity = options.fillOpacity;
12636
12637                 } else if (fill) {
12638                         container.removeChild(fill);
12639                         layer._fill = null;
12640                 }
12641         },
12642
12643         _updateCircle: function (layer) {
12644                 var p = layer._point.round(),
12645                     r = Math.round(layer._radius),
12646                     r2 = Math.round(layer._radiusY || r);
12647
12648                 this._setPath(layer, layer._empty() ? 'M0 0' :
12649                         'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12650         },
12651
12652         _setPath: function (layer, path) {
12653                 layer._path.v = path;
12654         },
12655
12656         _bringToFront: function (layer) {
12657                 toFront(layer._container);
12658         },
12659
12660         _bringToBack: function (layer) {
12661                 toBack(layer._container);
12662         }
12663 };
12664
12665 var create$2 = vml ? vmlCreate : svgCreate;
12666
12667 /*
12668  * @class SVG
12669  * @inherits Renderer
12670  * @aka L.SVG
12671  *
12672  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12673  * Inherits `Renderer`.
12674  *
12675  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12676  * available in all web browsers, notably Android 2.x and 3.x.
12677  *
12678  * Although SVG is not available on IE7 and IE8, these browsers support
12679  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12680  * (a now deprecated technology), and the SVG renderer will fall back to VML in
12681  * this case.
12682  *
12683  * @example
12684  *
12685  * Use SVG by default for all paths in the map:
12686  *
12687  * ```js
12688  * var map = L.map('map', {
12689  *      renderer: L.svg()
12690  * });
12691  * ```
12692  *
12693  * Use a SVG renderer with extra padding for specific vector geometries:
12694  *
12695  * ```js
12696  * var map = L.map('map');
12697  * var myRenderer = L.svg({ padding: 0.5 });
12698  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12699  * var circle = L.circle( center, { renderer: myRenderer } );
12700  * ```
12701  */
12702
12703 var SVG = Renderer.extend({
12704
12705         getEvents: function () {
12706                 var events = Renderer.prototype.getEvents.call(this);
12707                 events.zoomstart = this._onZoomStart;
12708                 return events;
12709         },
12710
12711         _initContainer: function () {
12712                 this._container = create$2('svg');
12713
12714                 // makes it possible to click through svg root; we'll reset it back in individual paths
12715                 this._container.setAttribute('pointer-events', 'none');
12716
12717                 this._rootGroup = create$2('g');
12718                 this._container.appendChild(this._rootGroup);
12719         },
12720
12721         _destroyContainer: function () {
12722                 remove(this._container);
12723                 off(this._container);
12724                 delete this._container;
12725                 delete this._rootGroup;
12726                 delete this._svgSize;
12727         },
12728
12729         _onZoomStart: function () {
12730                 // Drag-then-pinch interactions might mess up the center and zoom.
12731                 // In this case, the easiest way to prevent this is re-do the renderer
12732                 //   bounds and padding when the zooming starts.
12733                 this._update();
12734         },
12735
12736         _update: function () {
12737                 if (this._map._animatingZoom && this._bounds) { return; }
12738
12739                 Renderer.prototype._update.call(this);
12740
12741                 var b = this._bounds,
12742                     size = b.getSize(),
12743                     container = this._container;
12744
12745                 // set size of svg-container if changed
12746                 if (!this._svgSize || !this._svgSize.equals(size)) {
12747                         this._svgSize = size;
12748                         container.setAttribute('width', size.x);
12749                         container.setAttribute('height', size.y);
12750                 }
12751
12752                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12753                 setPosition(container, b.min);
12754                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12755
12756                 this.fire('update');
12757         },
12758
12759         // methods below are called by vector layers implementations
12760
12761         _initPath: function (layer) {
12762                 var path = layer._path = create$2('path');
12763
12764                 // @namespace Path
12765                 // @option className: String = null
12766                 // Custom class name set on an element. Only for SVG renderer.
12767                 if (layer.options.className) {
12768                         addClass(path, layer.options.className);
12769                 }
12770
12771                 if (layer.options.interactive) {
12772                         addClass(path, 'leaflet-interactive');
12773                 }
12774
12775                 this._updateStyle(layer);
12776                 this._layers[stamp(layer)] = layer;
12777         },
12778
12779         _addPath: function (layer) {
12780                 if (!this._rootGroup) { this._initContainer(); }
12781                 this._rootGroup.appendChild(layer._path);
12782                 layer.addInteractiveTarget(layer._path);
12783         },
12784
12785         _removePath: function (layer) {
12786                 remove(layer._path);
12787                 layer.removeInteractiveTarget(layer._path);
12788                 delete this._layers[stamp(layer)];
12789         },
12790
12791         _updatePath: function (layer) {
12792                 layer._project();
12793                 layer._update();
12794         },
12795
12796         _updateStyle: function (layer) {
12797                 var path = layer._path,
12798                     options = layer.options;
12799
12800                 if (!path) { return; }
12801
12802                 if (options.stroke) {
12803                         path.setAttribute('stroke', options.color);
12804                         path.setAttribute('stroke-opacity', options.opacity);
12805                         path.setAttribute('stroke-width', options.weight);
12806                         path.setAttribute('stroke-linecap', options.lineCap);
12807                         path.setAttribute('stroke-linejoin', options.lineJoin);
12808
12809                         if (options.dashArray) {
12810                                 path.setAttribute('stroke-dasharray', options.dashArray);
12811                         } else {
12812                                 path.removeAttribute('stroke-dasharray');
12813                         }
12814
12815                         if (options.dashOffset) {
12816                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
12817                         } else {
12818                                 path.removeAttribute('stroke-dashoffset');
12819                         }
12820                 } else {
12821                         path.setAttribute('stroke', 'none');
12822                 }
12823
12824                 if (options.fill) {
12825                         path.setAttribute('fill', options.fillColor || options.color);
12826                         path.setAttribute('fill-opacity', options.fillOpacity);
12827                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12828                 } else {
12829                         path.setAttribute('fill', 'none');
12830                 }
12831         },
12832
12833         _updatePoly: function (layer, closed) {
12834                 this._setPath(layer, pointsToPath(layer._parts, closed));
12835         },
12836
12837         _updateCircle: function (layer) {
12838                 var p = layer._point,
12839                     r = Math.max(Math.round(layer._radius), 1),
12840                     r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12841                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12842
12843                 // drawing a circle with two half-arcs
12844                 var d = layer._empty() ? 'M0 0' :
12845                         'M' + (p.x - r) + ',' + p.y +
12846                         arc + (r * 2) + ',0 ' +
12847                         arc + (-r * 2) + ',0 ';
12848
12849                 this._setPath(layer, d);
12850         },
12851
12852         _setPath: function (layer, path) {
12853                 layer._path.setAttribute('d', path);
12854         },
12855
12856         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12857         _bringToFront: function (layer) {
12858                 toFront(layer._path);
12859         },
12860
12861         _bringToBack: function (layer) {
12862                 toBack(layer._path);
12863         }
12864 });
12865
12866 if (vml) {
12867         SVG.include(vmlMixin);
12868 }
12869
12870 // @namespace SVG
12871 // @factory L.svg(options?: Renderer options)
12872 // Creates a SVG renderer with the given options.
12873 function svg$1(options) {
12874         return svg || vml ? new SVG(options) : null;
12875 }
12876
12877 Map.include({
12878         // @namespace Map; @method getRenderer(layer: Path): Renderer
12879         // Returns the instance of `Renderer` that should be used to render the given
12880         // `Path`. It will ensure that the `renderer` options of the map and paths
12881         // are respected, and that the renderers do exist on the map.
12882         getRenderer: function (layer) {
12883                 // @namespace Path; @option renderer: Renderer
12884                 // Use this specific instance of `Renderer` for this path. Takes
12885                 // precedence over the map's [default renderer](#map-renderer).
12886                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12887
12888                 if (!renderer) {
12889                         renderer = this._renderer = this._createRenderer();
12890                 }
12891
12892                 if (!this.hasLayer(renderer)) {
12893                         this.addLayer(renderer);
12894                 }
12895                 return renderer;
12896         },
12897
12898         _getPaneRenderer: function (name) {
12899                 if (name === 'overlayPane' || name === undefined) {
12900                         return false;
12901                 }
12902
12903                 var renderer = this._paneRenderers[name];
12904                 if (renderer === undefined) {
12905                         renderer = this._createRenderer({pane: name});
12906                         this._paneRenderers[name] = renderer;
12907                 }
12908                 return renderer;
12909         },
12910
12911         _createRenderer: function (options) {
12912                 // @namespace Map; @option preferCanvas: Boolean = false
12913                 // Whether `Path`s should be rendered on a `Canvas` renderer.
12914                 // By default, all `Path`s are rendered in a `SVG` renderer.
12915                 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12916         }
12917 });
12918
12919 /*
12920  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12921  */
12922
12923 /*
12924  * @class Rectangle
12925  * @aka L.Rectangle
12926  * @inherits Polygon
12927  *
12928  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12929  *
12930  * @example
12931  *
12932  * ```js
12933  * // define rectangle geographical bounds
12934  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12935  *
12936  * // create an orange rectangle
12937  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12938  *
12939  * // zoom the map to the rectangle bounds
12940  * map.fitBounds(bounds);
12941  * ```
12942  *
12943  */
12944
12945
12946 var Rectangle = Polygon.extend({
12947         initialize: function (latLngBounds, options) {
12948                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12949         },
12950
12951         // @method setBounds(latLngBounds: LatLngBounds): this
12952         // Redraws the rectangle with the passed bounds.
12953         setBounds: function (latLngBounds) {
12954                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12955         },
12956
12957         _boundsToLatLngs: function (latLngBounds) {
12958                 latLngBounds = toLatLngBounds(latLngBounds);
12959                 return [
12960                         latLngBounds.getSouthWest(),
12961                         latLngBounds.getNorthWest(),
12962                         latLngBounds.getNorthEast(),
12963                         latLngBounds.getSouthEast()
12964                 ];
12965         }
12966 });
12967
12968
12969 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12970 function rectangle(latLngBounds, options) {
12971         return new Rectangle(latLngBounds, options);
12972 }
12973
12974 SVG.create = create$2;
12975 SVG.pointsToPath = pointsToPath;
12976
12977 GeoJSON.geometryToLayer = geometryToLayer;
12978 GeoJSON.coordsToLatLng = coordsToLatLng;
12979 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12980 GeoJSON.latLngToCoords = latLngToCoords;
12981 GeoJSON.latLngsToCoords = latLngsToCoords;
12982 GeoJSON.getFeature = getFeature;
12983 GeoJSON.asFeature = asFeature;
12984
12985 /*
12986  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12987  * (zoom to a selected bounding box), enabled by default.
12988  */
12989
12990 // @namespace Map
12991 // @section Interaction Options
12992 Map.mergeOptions({
12993         // @option boxZoom: Boolean = true
12994         // Whether the map can be zoomed to a rectangular area specified by
12995         // dragging the mouse while pressing the shift key.
12996         boxZoom: true
12997 });
12998
12999 var BoxZoom = Handler.extend({
13000         initialize: function (map) {
13001                 this._map = map;
13002                 this._container = map._container;
13003                 this._pane = map._panes.overlayPane;
13004                 this._resetStateTimeout = 0;
13005                 map.on('unload', this._destroy, this);
13006         },
13007
13008         addHooks: function () {
13009                 on(this._container, 'mousedown', this._onMouseDown, this);
13010         },
13011
13012         removeHooks: function () {
13013                 off(this._container, 'mousedown', this._onMouseDown, this);
13014         },
13015
13016         moved: function () {
13017                 return this._moved;
13018         },
13019
13020         _destroy: function () {
13021                 remove(this._pane);
13022                 delete this._pane;
13023         },
13024
13025         _resetState: function () {
13026                 this._resetStateTimeout = 0;
13027                 this._moved = false;
13028         },
13029
13030         _clearDeferredResetState: function () {
13031                 if (this._resetStateTimeout !== 0) {
13032                         clearTimeout(this._resetStateTimeout);
13033                         this._resetStateTimeout = 0;
13034                 }
13035         },
13036
13037         _onMouseDown: function (e) {
13038                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
13039
13040                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
13041                 // will interrupt the interaction and orphan a box element in the container.
13042                 this._clearDeferredResetState();
13043                 this._resetState();
13044
13045                 disableTextSelection();
13046                 disableImageDrag();
13047
13048                 this._startPoint = this._map.mouseEventToContainerPoint(e);
13049
13050                 on(document, {
13051                         contextmenu: stop,
13052                         mousemove: this._onMouseMove,
13053                         mouseup: this._onMouseUp,
13054                         keydown: this._onKeyDown
13055                 }, this);
13056         },
13057
13058         _onMouseMove: function (e) {
13059                 if (!this._moved) {
13060                         this._moved = true;
13061
13062                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
13063                         addClass(this._container, 'leaflet-crosshair');
13064
13065                         this._map.fire('boxzoomstart');
13066                 }
13067
13068                 this._point = this._map.mouseEventToContainerPoint(e);
13069
13070                 var bounds = new Bounds(this._point, this._startPoint),
13071                     size = bounds.getSize();
13072
13073                 setPosition(this._box, bounds.min);
13074
13075                 this._box.style.width  = size.x + 'px';
13076                 this._box.style.height = size.y + 'px';
13077         },
13078
13079         _finish: function () {
13080                 if (this._moved) {
13081                         remove(this._box);
13082                         removeClass(this._container, 'leaflet-crosshair');
13083                 }
13084
13085                 enableTextSelection();
13086                 enableImageDrag();
13087
13088                 off(document, {
13089                         contextmenu: stop,
13090                         mousemove: this._onMouseMove,
13091                         mouseup: this._onMouseUp,
13092                         keydown: this._onKeyDown
13093                 }, this);
13094         },
13095
13096         _onMouseUp: function (e) {
13097                 if ((e.which !== 1) && (e.button !== 1)) { return; }
13098
13099                 this._finish();
13100
13101                 if (!this._moved) { return; }
13102                 // Postpone to next JS tick so internal click event handling
13103                 // still see it as "moved".
13104                 this._clearDeferredResetState();
13105                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
13106
13107                 var bounds = new LatLngBounds(
13108                         this._map.containerPointToLatLng(this._startPoint),
13109                         this._map.containerPointToLatLng(this._point));
13110
13111                 this._map
13112                         .fitBounds(bounds)
13113                         .fire('boxzoomend', {boxZoomBounds: bounds});
13114         },
13115
13116         _onKeyDown: function (e) {
13117                 if (e.keyCode === 27) {
13118                         this._finish();
13119                 }
13120         }
13121 });
13122
13123 // @section Handlers
13124 // @property boxZoom: Handler
13125 // Box (shift-drag with mouse) zoom handler.
13126 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
13127
13128 /*
13129  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
13130  */
13131
13132 // @namespace Map
13133 // @section Interaction Options
13134
13135 Map.mergeOptions({
13136         // @option doubleClickZoom: Boolean|String = true
13137         // Whether the map can be zoomed in by double clicking on it and
13138         // zoomed out by double clicking while holding shift. If passed
13139         // `'center'`, double-click zoom will zoom to the center of the
13140         //  view regardless of where the mouse was.
13141         doubleClickZoom: true
13142 });
13143
13144 var DoubleClickZoom = Handler.extend({
13145         addHooks: function () {
13146                 this._map.on('dblclick', this._onDoubleClick, this);
13147         },
13148
13149         removeHooks: function () {
13150                 this._map.off('dblclick', this._onDoubleClick, this);
13151         },
13152
13153         _onDoubleClick: function (e) {
13154                 var map = this._map,
13155                     oldZoom = map.getZoom(),
13156                     delta = map.options.zoomDelta,
13157                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13158
13159                 if (map.options.doubleClickZoom === 'center') {
13160                         map.setZoom(zoom);
13161                 } else {
13162                         map.setZoomAround(e.containerPoint, zoom);
13163                 }
13164         }
13165 });
13166
13167 // @section Handlers
13168 //
13169 // Map properties include interaction handlers that allow you to control
13170 // interaction behavior in runtime, enabling or disabling certain features such
13171 // as dragging or touch zoom (see `Handler` methods). For example:
13172 //
13173 // ```js
13174 // map.doubleClickZoom.disable();
13175 // ```
13176 //
13177 // @property doubleClickZoom: Handler
13178 // Double click zoom handler.
13179 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13180
13181 /*
13182  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13183  */
13184
13185 // @namespace Map
13186 // @section Interaction Options
13187 Map.mergeOptions({
13188         // @option dragging: Boolean = true
13189         // Whether the map be draggable with mouse/touch or not.
13190         dragging: true,
13191
13192         // @section Panning Inertia Options
13193         // @option inertia: Boolean = *
13194         // If enabled, panning of the map will have an inertia effect where
13195         // the map builds momentum while dragging and continues moving in
13196         // the same direction for some time. Feels especially nice on touch
13197         // devices. Enabled by default unless running on old Android devices.
13198         inertia: !android23,
13199
13200         // @option inertiaDeceleration: Number = 3000
13201         // The rate with which the inertial movement slows down, in pixels/second².
13202         inertiaDeceleration: 3400, // px/s^2
13203
13204         // @option inertiaMaxSpeed: Number = Infinity
13205         // Max speed of the inertial movement, in pixels/second.
13206         inertiaMaxSpeed: Infinity, // px/s
13207
13208         // @option easeLinearity: Number = 0.2
13209         easeLinearity: 0.2,
13210
13211         // TODO refactor, move to CRS
13212         // @option worldCopyJump: Boolean = false
13213         // With this option enabled, the map tracks when you pan to another "copy"
13214         // of the world and seamlessly jumps to the original one so that all overlays
13215         // like markers and vector layers are still visible.
13216         worldCopyJump: false,
13217
13218         // @option maxBoundsViscosity: Number = 0.0
13219         // If `maxBounds` is set, this option will control how solid the bounds
13220         // are when dragging the map around. The default value of `0.0` allows the
13221         // user to drag outside the bounds at normal speed, higher values will
13222         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13223         // solid, preventing the user from dragging outside the bounds.
13224         maxBoundsViscosity: 0.0
13225 });
13226
13227 var Drag = Handler.extend({
13228         addHooks: function () {
13229                 if (!this._draggable) {
13230                         var map = this._map;
13231
13232                         this._draggable = new Draggable(map._mapPane, map._container);
13233
13234                         this._draggable.on({
13235                                 dragstart: this._onDragStart,
13236                                 drag: this._onDrag,
13237                                 dragend: this._onDragEnd
13238                         }, this);
13239
13240                         this._draggable.on('predrag', this._onPreDragLimit, this);
13241                         if (map.options.worldCopyJump) {
13242                                 this._draggable.on('predrag', this._onPreDragWrap, this);
13243                                 map.on('zoomend', this._onZoomEnd, this);
13244
13245                                 map.whenReady(this._onZoomEnd, this);
13246                         }
13247                 }
13248                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13249                 this._draggable.enable();
13250                 this._positions = [];
13251                 this._times = [];
13252         },
13253
13254         removeHooks: function () {
13255                 removeClass(this._map._container, 'leaflet-grab');
13256                 removeClass(this._map._container, 'leaflet-touch-drag');
13257                 this._draggable.disable();
13258         },
13259
13260         moved: function () {
13261                 return this._draggable && this._draggable._moved;
13262         },
13263
13264         moving: function () {
13265                 return this._draggable && this._draggable._moving;
13266         },
13267
13268         _onDragStart: function () {
13269                 var map = this._map;
13270
13271                 map._stop();
13272                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13273                         var bounds = toLatLngBounds(this._map.options.maxBounds);
13274
13275                         this._offsetLimit = toBounds(
13276                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13277                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13278                                         .add(this._map.getSize()));
13279
13280                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13281                 } else {
13282                         this._offsetLimit = null;
13283                 }
13284
13285                 map
13286                     .fire('movestart')
13287                     .fire('dragstart');
13288
13289                 if (map.options.inertia) {
13290                         this._positions = [];
13291                         this._times = [];
13292                 }
13293         },
13294
13295         _onDrag: function (e) {
13296                 if (this._map.options.inertia) {
13297                         var time = this._lastTime = +new Date(),
13298                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13299
13300                         this._positions.push(pos);
13301                         this._times.push(time);
13302
13303                         this._prunePositions(time);
13304                 }
13305
13306                 this._map
13307                     .fire('move', e)
13308                     .fire('drag', e);
13309         },
13310
13311         _prunePositions: function (time) {
13312                 while (this._positions.length > 1 && time - this._times[0] > 50) {
13313                         this._positions.shift();
13314                         this._times.shift();
13315                 }
13316         },
13317
13318         _onZoomEnd: function () {
13319                 var pxCenter = this._map.getSize().divideBy(2),
13320                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13321
13322                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13323                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13324         },
13325
13326         _viscousLimit: function (value, threshold) {
13327                 return value - (value - threshold) * this._viscosity;
13328         },
13329
13330         _onPreDragLimit: function () {
13331                 if (!this._viscosity || !this._offsetLimit) { return; }
13332
13333                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13334
13335                 var limit = this._offsetLimit;
13336                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13337                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13338                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13339                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13340
13341                 this._draggable._newPos = this._draggable._startPos.add(offset);
13342         },
13343
13344         _onPreDragWrap: function () {
13345                 // TODO refactor to be able to adjust map pane position after zoom
13346                 var worldWidth = this._worldWidth,
13347                     halfWidth = Math.round(worldWidth / 2),
13348                     dx = this._initialWorldOffset,
13349                     x = this._draggable._newPos.x,
13350                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13351                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13352                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13353
13354                 this._draggable._absPos = this._draggable._newPos.clone();
13355                 this._draggable._newPos.x = newX;
13356         },
13357
13358         _onDragEnd: function (e) {
13359                 var map = this._map,
13360                     options = map.options,
13361
13362                     noInertia = !options.inertia || this._times.length < 2;
13363
13364                 map.fire('dragend', e);
13365
13366                 if (noInertia) {
13367                         map.fire('moveend');
13368
13369                 } else {
13370                         this._prunePositions(+new Date());
13371
13372                         var direction = this._lastPos.subtract(this._positions[0]),
13373                             duration = (this._lastTime - this._times[0]) / 1000,
13374                             ease = options.easeLinearity,
13375
13376                             speedVector = direction.multiplyBy(ease / duration),
13377                             speed = speedVector.distanceTo([0, 0]),
13378
13379                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13380                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13381
13382                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13383                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13384
13385                         if (!offset.x && !offset.y) {
13386                                 map.fire('moveend');
13387
13388                         } else {
13389                                 offset = map._limitOffset(offset, map.options.maxBounds);
13390
13391                                 requestAnimFrame(function () {
13392                                         map.panBy(offset, {
13393                                                 duration: decelerationDuration,
13394                                                 easeLinearity: ease,
13395                                                 noMoveStart: true,
13396                                                 animate: true
13397                                         });
13398                                 });
13399                         }
13400                 }
13401         }
13402 });
13403
13404 // @section Handlers
13405 // @property dragging: Handler
13406 // Map dragging handler (by both mouse and touch).
13407 Map.addInitHook('addHandler', 'dragging', Drag);
13408
13409 /*
13410  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13411  */
13412
13413 // @namespace Map
13414 // @section Keyboard Navigation Options
13415 Map.mergeOptions({
13416         // @option keyboard: Boolean = true
13417         // Makes the map focusable and allows users to navigate the map with keyboard
13418         // arrows and `+`/`-` keys.
13419         keyboard: true,
13420
13421         // @option keyboardPanDelta: Number = 80
13422         // Amount of pixels to pan when pressing an arrow key.
13423         keyboardPanDelta: 80
13424 });
13425
13426 var Keyboard = Handler.extend({
13427
13428         keyCodes: {
13429                 left:    [37],
13430                 right:   [39],
13431                 down:    [40],
13432                 up:      [38],
13433                 zoomIn:  [187, 107, 61, 171],
13434                 zoomOut: [189, 109, 54, 173]
13435         },
13436
13437         initialize: function (map) {
13438                 this._map = map;
13439
13440                 this._setPanDelta(map.options.keyboardPanDelta);
13441                 this._setZoomDelta(map.options.zoomDelta);
13442         },
13443
13444         addHooks: function () {
13445                 var container = this._map._container;
13446
13447                 // make the container focusable by tabbing
13448                 if (container.tabIndex <= 0) {
13449                         container.tabIndex = '0';
13450                 }
13451
13452                 on(container, {
13453                         focus: this._onFocus,
13454                         blur: this._onBlur,
13455                         mousedown: this._onMouseDown
13456                 }, this);
13457
13458                 this._map.on({
13459                         focus: this._addHooks,
13460                         blur: this._removeHooks
13461                 }, this);
13462         },
13463
13464         removeHooks: function () {
13465                 this._removeHooks();
13466
13467                 off(this._map._container, {
13468                         focus: this._onFocus,
13469                         blur: this._onBlur,
13470                         mousedown: this._onMouseDown
13471                 }, this);
13472
13473                 this._map.off({
13474                         focus: this._addHooks,
13475                         blur: this._removeHooks
13476                 }, this);
13477         },
13478
13479         _onMouseDown: function () {
13480                 if (this._focused) { return; }
13481
13482                 var body = document.body,
13483                     docEl = document.documentElement,
13484                     top = body.scrollTop || docEl.scrollTop,
13485                     left = body.scrollLeft || docEl.scrollLeft;
13486
13487                 this._map._container.focus();
13488
13489                 window.scrollTo(left, top);
13490         },
13491
13492         _onFocus: function () {
13493                 this._focused = true;
13494                 this._map.fire('focus');
13495         },
13496
13497         _onBlur: function () {
13498                 this._focused = false;
13499                 this._map.fire('blur');
13500         },
13501
13502         _setPanDelta: function (panDelta) {
13503                 var keys = this._panKeys = {},
13504                     codes = this.keyCodes,
13505                     i, len;
13506
13507                 for (i = 0, len = codes.left.length; i < len; i++) {
13508                         keys[codes.left[i]] = [-1 * panDelta, 0];
13509                 }
13510                 for (i = 0, len = codes.right.length; i < len; i++) {
13511                         keys[codes.right[i]] = [panDelta, 0];
13512                 }
13513                 for (i = 0, len = codes.down.length; i < len; i++) {
13514                         keys[codes.down[i]] = [0, panDelta];
13515                 }
13516                 for (i = 0, len = codes.up.length; i < len; i++) {
13517                         keys[codes.up[i]] = [0, -1 * panDelta];
13518                 }
13519         },
13520
13521         _setZoomDelta: function (zoomDelta) {
13522                 var keys = this._zoomKeys = {},
13523                     codes = this.keyCodes,
13524                     i, len;
13525
13526                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13527                         keys[codes.zoomIn[i]] = zoomDelta;
13528                 }
13529                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13530                         keys[codes.zoomOut[i]] = -zoomDelta;
13531                 }
13532         },
13533
13534         _addHooks: function () {
13535                 on(document, 'keydown', this._onKeyDown, this);
13536         },
13537
13538         _removeHooks: function () {
13539                 off(document, 'keydown', this._onKeyDown, this);
13540         },
13541
13542         _onKeyDown: function (e) {
13543                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13544
13545                 var key = e.keyCode,
13546                     map = this._map,
13547                     offset;
13548
13549                 if (key in this._panKeys) {
13550                         if (!map._panAnim || !map._panAnim._inProgress) {
13551                                 offset = this._panKeys[key];
13552                                 if (e.shiftKey) {
13553                                         offset = toPoint(offset).multiplyBy(3);
13554                                 }
13555
13556                                 map.panBy(offset);
13557
13558                                 if (map.options.maxBounds) {
13559                                         map.panInsideBounds(map.options.maxBounds);
13560                                 }
13561                         }
13562                 } else if (key in this._zoomKeys) {
13563                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13564
13565                 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13566                         map.closePopup();
13567
13568                 } else {
13569                         return;
13570                 }
13571
13572                 stop(e);
13573         }
13574 });
13575
13576 // @section Handlers
13577 // @section Handlers
13578 // @property keyboard: Handler
13579 // Keyboard navigation handler.
13580 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13581
13582 /*
13583  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13584  */
13585
13586 // @namespace Map
13587 // @section Interaction Options
13588 Map.mergeOptions({
13589         // @section Mousewheel options
13590         // @option scrollWheelZoom: Boolean|String = true
13591         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13592         // it will zoom to the center of the view regardless of where the mouse was.
13593         scrollWheelZoom: true,
13594
13595         // @option wheelDebounceTime: Number = 40
13596         // Limits the rate at which a wheel can fire (in milliseconds). By default
13597         // user can't zoom via wheel more often than once per 40 ms.
13598         wheelDebounceTime: 40,
13599
13600         // @option wheelPxPerZoomLevel: Number = 60
13601         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13602         // mean a change of one full zoom level. Smaller values will make wheel-zooming
13603         // faster (and vice versa).
13604         wheelPxPerZoomLevel: 60
13605 });
13606
13607 var ScrollWheelZoom = Handler.extend({
13608         addHooks: function () {
13609                 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13610
13611                 this._delta = 0;
13612         },
13613
13614         removeHooks: function () {
13615                 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13616         },
13617
13618         _onWheelScroll: function (e) {
13619                 var delta = getWheelDelta(e);
13620
13621                 var debounce = this._map.options.wheelDebounceTime;
13622
13623                 this._delta += delta;
13624                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13625
13626                 if (!this._startTime) {
13627                         this._startTime = +new Date();
13628                 }
13629
13630                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13631
13632                 clearTimeout(this._timer);
13633                 this._timer = setTimeout(bind(this._performZoom, this), left);
13634
13635                 stop(e);
13636         },
13637
13638         _performZoom: function () {
13639                 var map = this._map,
13640                     zoom = map.getZoom(),
13641                     snap = this._map.options.zoomSnap || 0;
13642
13643                 map._stop(); // stop panning and fly animations if any
13644
13645                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13646                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13647                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13648                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13649                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13650
13651                 this._delta = 0;
13652                 this._startTime = null;
13653
13654                 if (!delta) { return; }
13655
13656                 if (map.options.scrollWheelZoom === 'center') {
13657                         map.setZoom(zoom + delta);
13658                 } else {
13659                         map.setZoomAround(this._lastMousePos, zoom + delta);
13660                 }
13661         }
13662 });
13663
13664 // @section Handlers
13665 // @property scrollWheelZoom: Handler
13666 // Scroll wheel zoom handler.
13667 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13668
13669 /*
13670  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13671  */
13672
13673 // @namespace Map
13674 // @section Interaction Options
13675 Map.mergeOptions({
13676         // @section Touch interaction options
13677         // @option tap: Boolean = true
13678         // Enables mobile hacks for supporting instant taps (fixing 200ms click
13679         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13680         tap: true,
13681
13682         // @option tapTolerance: Number = 15
13683         // The max number of pixels a user can shift his finger during touch
13684         // for it to be considered a valid tap.
13685         tapTolerance: 15
13686 });
13687
13688 var Tap = Handler.extend({
13689         addHooks: function () {
13690                 on(this._map._container, 'touchstart', this._onDown, this);
13691         },
13692
13693         removeHooks: function () {
13694                 off(this._map._container, 'touchstart', this._onDown, this);
13695         },
13696
13697         _onDown: function (e) {
13698                 if (!e.touches) { return; }
13699
13700                 preventDefault(e);
13701
13702                 this._fireClick = true;
13703
13704                 // don't simulate click or track longpress if more than 1 touch
13705                 if (e.touches.length > 1) {
13706                         this._fireClick = false;
13707                         clearTimeout(this._holdTimeout);
13708                         return;
13709                 }
13710
13711                 var first = e.touches[0],
13712                     el = first.target;
13713
13714                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13715
13716                 // if touching a link, highlight it
13717                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13718                         addClass(el, 'leaflet-active');
13719                 }
13720
13721                 // simulate long hold but setting a timeout
13722                 this._holdTimeout = setTimeout(bind(function () {
13723                         if (this._isTapValid()) {
13724                                 this._fireClick = false;
13725                                 this._onUp();
13726                                 this._simulateEvent('contextmenu', first);
13727                         }
13728                 }, this), 1000);
13729
13730                 this._simulateEvent('mousedown', first);
13731
13732                 on(document, {
13733                         touchmove: this._onMove,
13734                         touchend: this._onUp
13735                 }, this);
13736         },
13737
13738         _onUp: function (e) {
13739                 clearTimeout(this._holdTimeout);
13740
13741                 off(document, {
13742                         touchmove: this._onMove,
13743                         touchend: this._onUp
13744                 }, this);
13745
13746                 if (this._fireClick && e && e.changedTouches) {
13747
13748                         var first = e.changedTouches[0],
13749                             el = first.target;
13750
13751                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13752                                 removeClass(el, 'leaflet-active');
13753                         }
13754
13755                         this._simulateEvent('mouseup', first);
13756
13757                         // simulate click if the touch didn't move too much
13758                         if (this._isTapValid()) {
13759                                 this._simulateEvent('click', first);
13760                         }
13761                 }
13762         },
13763
13764         _isTapValid: function () {
13765                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13766         },
13767
13768         _onMove: function (e) {
13769                 var first = e.touches[0];
13770                 this._newPos = new Point(first.clientX, first.clientY);
13771                 this._simulateEvent('mousemove', first);
13772         },
13773
13774         _simulateEvent: function (type, e) {
13775                 var simulatedEvent = document.createEvent('MouseEvents');
13776
13777                 simulatedEvent._simulated = true;
13778                 e.target._simulatedClick = true;
13779
13780                 simulatedEvent.initMouseEvent(
13781                         type, true, true, window, 1,
13782                         e.screenX, e.screenY,
13783                         e.clientX, e.clientY,
13784                         false, false, false, false, 0, null);
13785
13786                 e.target.dispatchEvent(simulatedEvent);
13787         }
13788 });
13789
13790 // @section Handlers
13791 // @property tap: Handler
13792 // Mobile touch hacks (quick tap and touch hold) handler.
13793 if (touch && !pointer) {
13794         Map.addInitHook('addHandler', 'tap', Tap);
13795 }
13796
13797 /*
13798  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13799  */
13800
13801 // @namespace Map
13802 // @section Interaction Options
13803 Map.mergeOptions({
13804         // @section Touch interaction options
13805         // @option touchZoom: Boolean|String = *
13806         // Whether the map can be zoomed by touch-dragging with two fingers. If
13807         // passed `'center'`, it will zoom to the center of the view regardless of
13808         // where the touch events (fingers) were. Enabled for touch-capable web
13809         // browsers except for old Androids.
13810         touchZoom: touch && !android23,
13811
13812         // @option bounceAtZoomLimits: Boolean = true
13813         // Set it to false if you don't want the map to zoom beyond min/max zoom
13814         // and then bounce back when pinch-zooming.
13815         bounceAtZoomLimits: true
13816 });
13817
13818 var TouchZoom = Handler.extend({
13819         addHooks: function () {
13820                 addClass(this._map._container, 'leaflet-touch-zoom');
13821                 on(this._map._container, 'touchstart', this._onTouchStart, this);
13822         },
13823
13824         removeHooks: function () {
13825                 removeClass(this._map._container, 'leaflet-touch-zoom');
13826                 off(this._map._container, 'touchstart', this._onTouchStart, this);
13827         },
13828
13829         _onTouchStart: function (e) {
13830                 var map = this._map;
13831                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13832
13833                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13834                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
13835
13836                 this._centerPoint = map.getSize()._divideBy(2);
13837                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13838                 if (map.options.touchZoom !== 'center') {
13839                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13840                 }
13841
13842                 this._startDist = p1.distanceTo(p2);
13843                 this._startZoom = map.getZoom();
13844
13845                 this._moved = false;
13846                 this._zooming = true;
13847
13848                 map._stop();
13849
13850                 on(document, 'touchmove', this._onTouchMove, this);
13851                 on(document, 'touchend', this._onTouchEnd, this);
13852
13853                 preventDefault(e);
13854         },
13855
13856         _onTouchMove: function (e) {
13857                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13858
13859                 var map = this._map,
13860                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
13861                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
13862                     scale = p1.distanceTo(p2) / this._startDist;
13863
13864                 this._zoom = map.getScaleZoom(scale, this._startZoom);
13865
13866                 if (!map.options.bounceAtZoomLimits && (
13867                         (this._zoom < map.getMinZoom() && scale < 1) ||
13868                         (this._zoom > map.getMaxZoom() && scale > 1))) {
13869                         this._zoom = map._limitZoom(this._zoom);
13870                 }
13871
13872                 if (map.options.touchZoom === 'center') {
13873                         this._center = this._startLatLng;
13874                         if (scale === 1) { return; }
13875                 } else {
13876                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13877                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13878                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13879                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13880                 }
13881
13882                 if (!this._moved) {
13883                         map._moveStart(true, false);
13884                         this._moved = true;
13885                 }
13886
13887                 cancelAnimFrame(this._animRequest);
13888
13889                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13890                 this._animRequest = requestAnimFrame(moveFn, this, true);
13891
13892                 preventDefault(e);
13893         },
13894
13895         _onTouchEnd: function () {
13896                 if (!this._moved || !this._zooming) {
13897                         this._zooming = false;
13898                         return;
13899                 }
13900
13901                 this._zooming = false;
13902                 cancelAnimFrame(this._animRequest);
13903
13904                 off(document, 'touchmove', this._onTouchMove);
13905                 off(document, 'touchend', this._onTouchEnd);
13906
13907                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13908                 if (this._map.options.zoomAnimation) {
13909                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13910                 } else {
13911                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13912                 }
13913         }
13914 });
13915
13916 // @section Handlers
13917 // @property touchZoom: Handler
13918 // Touch zoom handler.
13919 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13920
13921 Map.BoxZoom = BoxZoom;
13922 Map.DoubleClickZoom = DoubleClickZoom;
13923 Map.Drag = Drag;
13924 Map.Keyboard = Keyboard;
13925 Map.ScrollWheelZoom = ScrollWheelZoom;
13926 Map.Tap = Tap;
13927 Map.TouchZoom = TouchZoom;
13928
13929 Object.freeze = freeze;
13930
13931 exports.version = version;
13932 exports.Control = Control;
13933 exports.control = control;
13934 exports.Browser = Browser;
13935 exports.Evented = Evented;
13936 exports.Mixin = Mixin;
13937 exports.Util = Util;
13938 exports.Class = Class;
13939 exports.Handler = Handler;
13940 exports.extend = extend;
13941 exports.bind = bind;
13942 exports.stamp = stamp;
13943 exports.setOptions = setOptions;
13944 exports.DomEvent = DomEvent;
13945 exports.DomUtil = DomUtil;
13946 exports.PosAnimation = PosAnimation;
13947 exports.Draggable = Draggable;
13948 exports.LineUtil = LineUtil;
13949 exports.PolyUtil = PolyUtil;
13950 exports.Point = Point;
13951 exports.point = toPoint;
13952 exports.Bounds = Bounds;
13953 exports.bounds = toBounds;
13954 exports.Transformation = Transformation;
13955 exports.transformation = toTransformation;
13956 exports.Projection = index;
13957 exports.LatLng = LatLng;
13958 exports.latLng = toLatLng;
13959 exports.LatLngBounds = LatLngBounds;
13960 exports.latLngBounds = toLatLngBounds;
13961 exports.CRS = CRS;
13962 exports.GeoJSON = GeoJSON;
13963 exports.geoJSON = geoJSON;
13964 exports.geoJson = geoJson;
13965 exports.Layer = Layer;
13966 exports.LayerGroup = LayerGroup;
13967 exports.layerGroup = layerGroup;
13968 exports.FeatureGroup = FeatureGroup;
13969 exports.featureGroup = featureGroup;
13970 exports.ImageOverlay = ImageOverlay;
13971 exports.imageOverlay = imageOverlay;
13972 exports.VideoOverlay = VideoOverlay;
13973 exports.videoOverlay = videoOverlay;
13974 exports.SVGOverlay = SVGOverlay;
13975 exports.svgOverlay = svgOverlay;
13976 exports.DivOverlay = DivOverlay;
13977 exports.Popup = Popup;
13978 exports.popup = popup;
13979 exports.Tooltip = Tooltip;
13980 exports.tooltip = tooltip;
13981 exports.Icon = Icon;
13982 exports.icon = icon;
13983 exports.DivIcon = DivIcon;
13984 exports.divIcon = divIcon;
13985 exports.Marker = Marker;
13986 exports.marker = marker;
13987 exports.TileLayer = TileLayer;
13988 exports.tileLayer = tileLayer;
13989 exports.GridLayer = GridLayer;
13990 exports.gridLayer = gridLayer;
13991 exports.SVG = SVG;
13992 exports.svg = svg$1;
13993 exports.Renderer = Renderer;
13994 exports.Canvas = Canvas;
13995 exports.canvas = canvas$1;
13996 exports.Path = Path;
13997 exports.CircleMarker = CircleMarker;
13998 exports.circleMarker = circleMarker;
13999 exports.Circle = Circle;
14000 exports.circle = circle;
14001 exports.Polyline = Polyline;
14002 exports.polyline = polyline;
14003 exports.Polygon = Polygon;
14004 exports.polygon = polygon;
14005 exports.Rectangle = Rectangle;
14006 exports.rectangle = rectangle;
14007 exports.Map = Map;
14008 exports.map = createMap;
14009
14010 var oldL = window.L;
14011 exports.noConflict = function() {
14012         window.L = oldL;
14013         return this;
14014 }
14015
14016 // Always export us to window global (see #2364)
14017 window.L = exports;
14018
14019 })));
14020 //# sourceMappingURL=leaflet-src.js.map