]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Merge branch 'master' into notes-search
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /* @preserve
2  * Leaflet 1.3.4, 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.3.4";
13
14 /*
15  * @namespace Util
16  *
17  * Various utility functions, used by Leaflet internally.
18  */
19
20 var freeze = Object.freeze;
21 Object.freeze = function (obj) { return obj; };
22
23 // @function extend(dest: Object, src?: Object): Object
24 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
25 function extend(dest) {
26         var i, j, len, src;
27
28         for (j = 1, len = arguments.length; j < len; j++) {
29                 src = arguments[j];
30                 for (i in src) {
31                         dest[i] = src[i];
32                 }
33         }
34         return dest;
35 }
36
37 // @function create(proto: Object, properties?: Object): Object
38 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
39 var create = Object.create || (function () {
40         function F() {}
41         return function (proto) {
42                 F.prototype = proto;
43                 return new F();
44         };
45 })();
46
47 // @function bind(fn: Function, …): Function
48 // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
49 // Has a `L.bind()` shortcut.
50 function bind(fn, obj) {
51         var slice = Array.prototype.slice;
52
53         if (fn.bind) {
54                 return fn.bind.apply(fn, slice.call(arguments, 1));
55         }
56
57         var args = slice.call(arguments, 2);
58
59         return function () {
60                 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
61         };
62 }
63
64 // @property lastId: Number
65 // Last unique ID used by [`stamp()`](#util-stamp)
66 var lastId = 0;
67
68 // @function stamp(obj: Object): Number
69 // Returns the unique ID of an object, assigning it one if it doesn't have it.
70 function stamp(obj) {
71         /*eslint-disable */
72         obj._leaflet_id = obj._leaflet_id || ++lastId;
73         return obj._leaflet_id;
74         /* eslint-enable */
75 }
76
77 // @function throttle(fn: Function, time: Number, context: Object): Function
78 // Returns a function which executes function `fn` with the given scope `context`
79 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
80 // `fn` will be called no more than one time per given amount of `time`. The arguments
81 // received by the bound function will be any arguments passed when binding the
82 // function, followed by any arguments passed when invoking the bound function.
83 // Has an `L.throttle` shortcut.
84 function throttle(fn, time, context) {
85         var lock, args, wrapperFn, later;
86
87         later = function () {
88                 // reset lock and call if queued
89                 lock = false;
90                 if (args) {
91                         wrapperFn.apply(context, args);
92                         args = false;
93                 }
94         };
95
96         wrapperFn = function () {
97                 if (lock) {
98                         // called too soon, queue to call later
99                         args = arguments;
100
101                 } else {
102                         // call and lock until later
103                         fn.apply(context, arguments);
104                         setTimeout(later, time);
105                         lock = true;
106                 }
107         };
108
109         return wrapperFn;
110 }
111
112 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
113 // Returns the number `num` modulo `range` in such a way so it lies within
114 // `range[0]` and `range[1]`. The returned value will be always smaller than
115 // `range[1]` unless `includeMax` is set to `true`.
116 function wrapNum(x, range, includeMax) {
117         var max = range[1],
118             min = range[0],
119             d = max - min;
120         return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
121 }
122
123 // @function falseFn(): Function
124 // Returns a function which always returns `false`.
125 function falseFn() { return false; }
126
127 // @function formatNum(num: Number, digits?: Number): Number
128 // Returns the number `num` rounded to `digits` decimals, or to 6 decimals by default.
129 function formatNum(num, digits) {
130         var pow = Math.pow(10, (digits === undefined ? 6 : digits));
131         return Math.round(num * pow) / pow;
132 }
133
134 // @function trim(str: String): String
135 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
136 function trim(str) {
137         return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
138 }
139
140 // @function splitWords(str: String): String[]
141 // Trims and splits the string on whitespace and returns the array of parts.
142 function splitWords(str) {
143         return trim(str).split(/\s+/);
144 }
145
146 // @function setOptions(obj: Object, options: Object): Object
147 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
148 function setOptions(obj, options) {
149         if (!obj.hasOwnProperty('options')) {
150                 obj.options = obj.options ? create(obj.options) : {};
151         }
152         for (var i in options) {
153                 obj.options[i] = options[i];
154         }
155         return obj.options;
156 }
157
158 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
159 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
160 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
161 // be appended at the end. If `uppercase` is `true`, the parameter names will
162 // be uppercased (e.g. `'?A=foo&B=bar'`)
163 function getParamString(obj, existingUrl, uppercase) {
164         var params = [];
165         for (var i in obj) {
166                 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
167         }
168         return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
169 }
170
171 var templateRe = /\{ *([\w_-]+) *\}/g;
172
173 // @function template(str: String, data: Object): String
174 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
175 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
176 // `('Hello foo, bar')`. You can also specify functions instead of strings for
177 // data values — they will be evaluated passing `data` as an argument.
178 function template(str, data) {
179         return str.replace(templateRe, function (str, key) {
180                 var value = data[key];
181
182                 if (value === undefined) {
183                         throw new Error('No value provided for variable ' + str);
184
185                 } else if (typeof value === 'function') {
186                         value = value(data);
187                 }
188                 return value;
189         });
190 }
191
192 // @function isArray(obj): Boolean
193 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
194 var isArray = Array.isArray || function (obj) {
195         return (Object.prototype.toString.call(obj) === '[object Array]');
196 };
197
198 // @function indexOf(array: Array, el: Object): Number
199 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
200 function indexOf(array, el) {
201         for (var i = 0; i < array.length; i++) {
202                 if (array[i] === el) { return i; }
203         }
204         return -1;
205 }
206
207 // @property emptyImageUrl: String
208 // Data URI string containing a base64-encoded empty GIF image.
209 // Used as a hack to free memory from unused images on WebKit-powered
210 // mobile devices (by setting image `src` to this string).
211 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
212
213 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
214
215 function getPrefixed(name) {
216         return window['webkit' + name] || window['moz' + name] || window['ms' + name];
217 }
218
219 var lastTime = 0;
220
221 // fallback for IE 7-8
222 function timeoutDefer(fn) {
223         var time = +new Date(),
224             timeToCall = Math.max(0, 16 - (time - lastTime));
225
226         lastTime = time + timeToCall;
227         return window.setTimeout(fn, timeToCall);
228 }
229
230 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
231 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
232                 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
233
234 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
235 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
236 // `context` if given. When `immediate` is set, `fn` is called immediately if
237 // the browser doesn't have native support for
238 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
239 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
240 function requestAnimFrame(fn, context, immediate) {
241         if (immediate && requestFn === timeoutDefer) {
242                 fn.call(context);
243         } else {
244                 return requestFn.call(window, bind(fn, context));
245         }
246 }
247
248 // @function cancelAnimFrame(id: Number): undefined
249 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
250 function cancelAnimFrame(id) {
251         if (id) {
252                 cancelFn.call(window, id);
253         }
254 }
255
256
257 var Util = (Object.freeze || Object)({
258         freeze: freeze,
259         extend: extend,
260         create: create,
261         bind: bind,
262         lastId: lastId,
263         stamp: stamp,
264         throttle: throttle,
265         wrapNum: wrapNum,
266         falseFn: falseFn,
267         formatNum: formatNum,
268         trim: trim,
269         splitWords: splitWords,
270         setOptions: setOptions,
271         getParamString: getParamString,
272         template: template,
273         isArray: isArray,
274         indexOf: indexOf,
275         emptyImageUrl: emptyImageUrl,
276         requestFn: requestFn,
277         cancelFn: cancelFn,
278         requestAnimFrame: requestAnimFrame,
279         cancelAnimFrame: cancelAnimFrame
280 });
281
282 // @class Class
283 // @aka L.Class
284
285 // @section
286 // @uninheritable
287
288 // Thanks to John Resig and Dean Edwards for inspiration!
289
290 function Class() {}
291
292 Class.extend = function (props) {
293
294         // @function extend(props: Object): Function
295         // [Extends the current class](#class-inheritance) given the properties to be included.
296         // Returns a Javascript function that is a class constructor (to be called with `new`).
297         var NewClass = function () {
298
299                 // call the constructor
300                 if (this.initialize) {
301                         this.initialize.apply(this, arguments);
302                 }
303
304                 // call all constructor hooks
305                 this.callInitHooks();
306         };
307
308         var parentProto = NewClass.__super__ = this.prototype;
309
310         var proto = create(parentProto);
311         proto.constructor = NewClass;
312
313         NewClass.prototype = proto;
314
315         // inherit parent's statics
316         for (var i in this) {
317                 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
318                         NewClass[i] = this[i];
319                 }
320         }
321
322         // mix static properties into the class
323         if (props.statics) {
324                 extend(NewClass, props.statics);
325                 delete props.statics;
326         }
327
328         // mix includes into the prototype
329         if (props.includes) {
330                 checkDeprecatedMixinEvents(props.includes);
331                 extend.apply(null, [proto].concat(props.includes));
332                 delete props.includes;
333         }
334
335         // merge options
336         if (proto.options) {
337                 props.options = extend(create(proto.options), props.options);
338         }
339
340         // mix given properties into the prototype
341         extend(proto, props);
342
343         proto._initHooks = [];
344
345         // add method for calling all hooks
346         proto.callInitHooks = function () {
347
348                 if (this._initHooksCalled) { return; }
349
350                 if (parentProto.callInitHooks) {
351                         parentProto.callInitHooks.call(this);
352                 }
353
354                 this._initHooksCalled = true;
355
356                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
357                         proto._initHooks[i].call(this);
358                 }
359         };
360
361         return NewClass;
362 };
363
364
365 // @function include(properties: Object): this
366 // [Includes a mixin](#class-includes) into the current class.
367 Class.include = function (props) {
368         extend(this.prototype, props);
369         return this;
370 };
371
372 // @function mergeOptions(options: Object): this
373 // [Merges `options`](#class-options) into the defaults of the class.
374 Class.mergeOptions = function (options) {
375         extend(this.prototype.options, options);
376         return this;
377 };
378
379 // @function addInitHook(fn: Function): this
380 // Adds a [constructor hook](#class-constructor-hooks) to the class.
381 Class.addInitHook = function (fn) { // (Function) || (String, args...)
382         var args = Array.prototype.slice.call(arguments, 1);
383
384         var init = typeof fn === 'function' ? fn : function () {
385                 this[fn].apply(this, args);
386         };
387
388         this.prototype._initHooks = this.prototype._initHooks || [];
389         this.prototype._initHooks.push(init);
390         return this;
391 };
392
393 function checkDeprecatedMixinEvents(includes) {
394         if (typeof L === 'undefined' || !L || !L.Mixin) { return; }
395
396         includes = isArray(includes) ? includes : [includes];
397
398         for (var i = 0; i < includes.length; i++) {
399                 if (includes[i] === L.Mixin.Events) {
400                         console.warn('Deprecated include of L.Mixin.Events: ' +
401                                 'this property will be removed in future releases, ' +
402                                 'please inherit from L.Evented instead.', new Error().stack);
403                 }
404         }
405 }
406
407 /*
408  * @class Evented
409  * @aka L.Evented
410  * @inherits Class
411  *
412  * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
413  *
414  * @example
415  *
416  * ```js
417  * map.on('click', function(e) {
418  *      alert(e.latlng);
419  * } );
420  * ```
421  *
422  * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
423  *
424  * ```js
425  * function onClick(e) { ... }
426  *
427  * map.on('click', onClick);
428  * map.off('click', onClick);
429  * ```
430  */
431
432 var Events = {
433         /* @method on(type: String, fn: Function, context?: Object): this
434          * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
435          *
436          * @alternative
437          * @method on(eventMap: Object): this
438          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
439          */
440         on: function (types, fn, context) {
441
442                 // types can be a map of types/handlers
443                 if (typeof types === 'object') {
444                         for (var type in types) {
445                                 // we don't process space-separated events here for performance;
446                                 // it's a hot path since Layer uses the on(obj) syntax
447                                 this._on(type, types[type], fn);
448                         }
449
450                 } else {
451                         // types can be a string of space-separated words
452                         types = splitWords(types);
453
454                         for (var i = 0, len = types.length; i < len; i++) {
455                                 this._on(types[i], fn, context);
456                         }
457                 }
458
459                 return this;
460         },
461
462         /* @method off(type: String, fn?: Function, context?: Object): this
463          * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
464          *
465          * @alternative
466          * @method off(eventMap: Object): this
467          * Removes a set of type/listener pairs.
468          *
469          * @alternative
470          * @method off: this
471          * Removes all listeners to all events on the object.
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 SphericalMercator = {
1652
1653         R: 6378137,
1654         MAX_LATITUDE: 85.0511287798,
1655
1656         project: function (latlng) {
1657                 var d = Math.PI / 180,
1658                     max = this.MAX_LATITUDE,
1659                     lat = Math.max(Math.min(max, latlng.lat), -max),
1660                     sin = Math.sin(lat * d);
1661
1662                 return new Point(
1663                         this.R * latlng.lng * d,
1664                         this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1665         },
1666
1667         unproject: function (point) {
1668                 var d = 180 / Math.PI;
1669
1670                 return new LatLng(
1671                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1672                         point.x * d / this.R);
1673         },
1674
1675         bounds: (function () {
1676                 var d = 6378137 * Math.PI;
1677                 return new Bounds([-d, -d], [d, d]);
1678         })()
1679 };
1680
1681 /*
1682  * @class Transformation
1683  * @aka L.Transformation
1684  *
1685  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1686  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1687  * the reverse. Used by Leaflet in its projections code.
1688  *
1689  * @example
1690  *
1691  * ```js
1692  * var transformation = L.transformation(2, 5, -1, 10),
1693  *      p = L.point(1, 2),
1694  *      p2 = transformation.transform(p), //  L.point(7, 8)
1695  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1696  * ```
1697  */
1698
1699
1700 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1701 // Creates a `Transformation` object with the given coefficients.
1702 function Transformation(a, b, c, d) {
1703         if (isArray(a)) {
1704                 // use array properties
1705                 this._a = a[0];
1706                 this._b = a[1];
1707                 this._c = a[2];
1708                 this._d = a[3];
1709                 return;
1710         }
1711         this._a = a;
1712         this._b = b;
1713         this._c = c;
1714         this._d = d;
1715 }
1716
1717 Transformation.prototype = {
1718         // @method transform(point: Point, scale?: Number): Point
1719         // Returns a transformed point, optionally multiplied by the given scale.
1720         // Only accepts actual `L.Point` instances, not arrays.
1721         transform: function (point, scale) { // (Point, Number) -> Point
1722                 return this._transform(point.clone(), scale);
1723         },
1724
1725         // destructive transform (faster)
1726         _transform: function (point, scale) {
1727                 scale = scale || 1;
1728                 point.x = scale * (this._a * point.x + this._b);
1729                 point.y = scale * (this._c * point.y + this._d);
1730                 return point;
1731         },
1732
1733         // @method untransform(point: Point, scale?: Number): Point
1734         // Returns the reverse transformation of the given point, optionally divided
1735         // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1736         untransform: function (point, scale) {
1737                 scale = scale || 1;
1738                 return new Point(
1739                         (point.x / scale - this._b) / this._a,
1740                         (point.y / scale - this._d) / this._c);
1741         }
1742 };
1743
1744 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1745
1746 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1747 // Instantiates a Transformation object with the given coefficients.
1748
1749 // @alternative
1750 // @factory L.transformation(coefficients: Array): Transformation
1751 // Expects an coefficients array of the form
1752 // `[a: Number, b: Number, c: Number, d: Number]`.
1753
1754 function toTransformation(a, b, c, d) {
1755         return new Transformation(a, b, c, d);
1756 }
1757
1758 /*
1759  * @namespace CRS
1760  * @crs L.CRS.EPSG3857
1761  *
1762  * The most common CRS for online maps, used by almost all free and commercial
1763  * tile providers. Uses Spherical Mercator projection. Set in by default in
1764  * Map's `crs` option.
1765  */
1766
1767 var EPSG3857 = extend({}, Earth, {
1768         code: 'EPSG:3857',
1769         projection: SphericalMercator,
1770
1771         transformation: (function () {
1772                 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1773                 return toTransformation(scale, 0.5, -scale, 0.5);
1774         }())
1775 });
1776
1777 var EPSG900913 = extend({}, EPSG3857, {
1778         code: 'EPSG:900913'
1779 });
1780
1781 // @namespace SVG; @section
1782 // There are several static functions which can be called without instantiating L.SVG:
1783
1784 // @function create(name: String): SVGElement
1785 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1786 // corresponding to the class name passed. For example, using 'line' will return
1787 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1788 function svgCreate(name) {
1789         return document.createElementNS('http://www.w3.org/2000/svg', name);
1790 }
1791
1792 // @function pointsToPath(rings: Point[], closed: Boolean): String
1793 // Generates a SVG path string for multiple rings, with each ring turning
1794 // into "M..L..L.." instructions
1795 function pointsToPath(rings, closed) {
1796         var str = '',
1797         i, j, len, len2, points, p;
1798
1799         for (i = 0, len = rings.length; i < len; i++) {
1800                 points = rings[i];
1801
1802                 for (j = 0, len2 = points.length; j < len2; j++) {
1803                         p = points[j];
1804                         str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1805                 }
1806
1807                 // closes the ring for polygons; "x" is VML syntax
1808                 str += closed ? (svg ? 'z' : 'x') : '';
1809         }
1810
1811         // SVG complains about empty path strings
1812         return str || 'M0 0';
1813 }
1814
1815 /*
1816  * @namespace Browser
1817  * @aka L.Browser
1818  *
1819  * A namespace with static properties for browser/feature detection used by Leaflet internally.
1820  *
1821  * @example
1822  *
1823  * ```js
1824  * if (L.Browser.ielt9) {
1825  *   alert('Upgrade your browser, dude!');
1826  * }
1827  * ```
1828  */
1829
1830 var style$1 = document.documentElement.style;
1831
1832 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1833 var ie = 'ActiveXObject' in window;
1834
1835 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1836 var ielt9 = ie && !document.addEventListener;
1837
1838 // @property edge: Boolean; `true` for the Edge web browser.
1839 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1840
1841 // @property webkit: Boolean;
1842 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1843 var webkit = userAgentContains('webkit');
1844
1845 // @property android: Boolean
1846 // `true` for any browser running on an Android platform.
1847 var android = userAgentContains('android');
1848
1849 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1850 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1851
1852 /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
1853 var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
1854 // @property androidStock: Boolean; `true` for the Android stock browser (i.e. not Chrome)
1855 var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);
1856
1857 // @property opera: Boolean; `true` for the Opera browser
1858 var opera = !!window.opera;
1859
1860 // @property chrome: Boolean; `true` for the Chrome browser.
1861 var chrome = userAgentContains('chrome');
1862
1863 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1864 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1865
1866 // @property safari: Boolean; `true` for the Safari browser.
1867 var safari = !chrome && userAgentContains('safari');
1868
1869 var phantom = userAgentContains('phantom');
1870
1871 // @property opera12: Boolean
1872 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1873 var opera12 = 'OTransition' in style$1;
1874
1875 // @property win: Boolean; `true` when the browser is running in a Windows platform
1876 var win = navigator.platform.indexOf('Win') === 0;
1877
1878 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1879 var ie3d = ie && ('transition' in style$1);
1880
1881 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1882 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1883
1884 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1885 var gecko3d = 'MozPerspective' in style$1;
1886
1887 // @property any3d: Boolean
1888 // `true` for all browsers supporting CSS transforms.
1889 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1890
1891 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1892 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1893
1894 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1895 var mobileWebkit = mobile && webkit;
1896
1897 // @property mobileWebkit3d: Boolean
1898 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1899 var mobileWebkit3d = mobile && webkit3d;
1900
1901 // @property msPointer: Boolean
1902 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1903 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1904
1905 // @property pointer: Boolean
1906 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1907 var pointer = !!(window.PointerEvent || msPointer);
1908
1909 // @property touch: Boolean
1910 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1911 // This does not necessarily mean that the browser is running in a computer with
1912 // a touchscreen, it only means that the browser is capable of understanding
1913 // touch events.
1914 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1915                 (window.DocumentTouch && document instanceof window.DocumentTouch));
1916
1917 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1918 var mobileOpera = mobile && opera;
1919
1920 // @property mobileGecko: Boolean
1921 // `true` for gecko-based browsers running in a mobile device.
1922 var mobileGecko = mobile && gecko;
1923
1924 // @property retina: Boolean
1925 // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
1926 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1927
1928
1929 // @property canvas: Boolean
1930 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1931 var canvas = (function () {
1932         return !!document.createElement('canvas').getContext;
1933 }());
1934
1935 // @property svg: Boolean
1936 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1937 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1938
1939 // @property vml: Boolean
1940 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1941 var vml = !svg && (function () {
1942         try {
1943                 var div = document.createElement('div');
1944                 div.innerHTML = '<v:shape adj="1"/>';
1945
1946                 var shape = div.firstChild;
1947                 shape.style.behavior = 'url(#default#VML)';
1948
1949                 return shape && (typeof shape.adj === 'object');
1950
1951         } catch (e) {
1952                 return false;
1953         }
1954 }());
1955
1956
1957 function userAgentContains(str) {
1958         return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1959 }
1960
1961
1962 var Browser = (Object.freeze || Object)({
1963         ie: ie,
1964         ielt9: ielt9,
1965         edge: edge,
1966         webkit: webkit,
1967         android: android,
1968         android23: android23,
1969         androidStock: androidStock,
1970         opera: opera,
1971         chrome: chrome,
1972         gecko: gecko,
1973         safari: safari,
1974         phantom: phantom,
1975         opera12: opera12,
1976         win: win,
1977         ie3d: ie3d,
1978         webkit3d: webkit3d,
1979         gecko3d: gecko3d,
1980         any3d: any3d,
1981         mobile: mobile,
1982         mobileWebkit: mobileWebkit,
1983         mobileWebkit3d: mobileWebkit3d,
1984         msPointer: msPointer,
1985         pointer: pointer,
1986         touch: touch,
1987         mobileOpera: mobileOpera,
1988         mobileGecko: mobileGecko,
1989         retina: retina,
1990         canvas: canvas,
1991         svg: svg,
1992         vml: vml
1993 });
1994
1995 /*
1996  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1997  */
1998
1999
2000 var POINTER_DOWN =   msPointer ? 'MSPointerDown'   : 'pointerdown';
2001 var POINTER_MOVE =   msPointer ? 'MSPointerMove'   : 'pointermove';
2002 var POINTER_UP =     msPointer ? 'MSPointerUp'     : 'pointerup';
2003 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
2004 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
2005
2006 var _pointers = {};
2007 var _pointerDocListener = false;
2008
2009 // DomEvent.DoubleTap needs to know about this
2010 var _pointersCount = 0;
2011
2012 // Provides a touch events wrapper for (ms)pointer events.
2013 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
2014
2015 function addPointerListener(obj, type, handler, id) {
2016         if (type === 'touchstart') {
2017                 _addPointerStart(obj, handler, id);
2018
2019         } else if (type === 'touchmove') {
2020                 _addPointerMove(obj, handler, id);
2021
2022         } else if (type === 'touchend') {
2023                 _addPointerEnd(obj, handler, id);
2024         }
2025
2026         return this;
2027 }
2028
2029 function removePointerListener(obj, type, id) {
2030         var handler = obj['_leaflet_' + type + id];
2031
2032         if (type === 'touchstart') {
2033                 obj.removeEventListener(POINTER_DOWN, handler, false);
2034
2035         } else if (type === 'touchmove') {
2036                 obj.removeEventListener(POINTER_MOVE, handler, false);
2037
2038         } else if (type === 'touchend') {
2039                 obj.removeEventListener(POINTER_UP, handler, false);
2040                 obj.removeEventListener(POINTER_CANCEL, handler, false);
2041         }
2042
2043         return this;
2044 }
2045
2046 function _addPointerStart(obj, handler, id) {
2047         var onDown = bind(function (e) {
2048                 if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
2049                         // In IE11, some touch events needs to fire for form controls, or
2050                         // the controls will stop working. We keep a whitelist of tag names that
2051                         // need these events. For other target tags, we prevent default on the event.
2052                         if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
2053                                 preventDefault(e);
2054                         } else {
2055                                 return;
2056                         }
2057                 }
2058
2059                 _handlePointer(e, handler);
2060         });
2061
2062         obj['_leaflet_touchstart' + id] = onDown;
2063         obj.addEventListener(POINTER_DOWN, onDown, false);
2064
2065         // need to keep track of what pointers and how many are active to provide e.touches emulation
2066         if (!_pointerDocListener) {
2067                 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2068                 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2069                 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2070                 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2071                 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2072
2073                 _pointerDocListener = true;
2074         }
2075 }
2076
2077 function _globalPointerDown(e) {
2078         _pointers[e.pointerId] = e;
2079         _pointersCount++;
2080 }
2081
2082 function _globalPointerMove(e) {
2083         if (_pointers[e.pointerId]) {
2084                 _pointers[e.pointerId] = e;
2085         }
2086 }
2087
2088 function _globalPointerUp(e) {
2089         delete _pointers[e.pointerId];
2090         _pointersCount--;
2091 }
2092
2093 function _handlePointer(e, handler) {
2094         e.touches = [];
2095         for (var i in _pointers) {
2096                 e.touches.push(_pointers[i]);
2097         }
2098         e.changedTouches = [e];
2099
2100         handler(e);
2101 }
2102
2103 function _addPointerMove(obj, handler, id) {
2104         var onMove = function (e) {
2105                 // don't fire touch moves when mouse isn't down
2106                 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2107
2108                 _handlePointer(e, handler);
2109         };
2110
2111         obj['_leaflet_touchmove' + id] = onMove;
2112         obj.addEventListener(POINTER_MOVE, onMove, false);
2113 }
2114
2115 function _addPointerEnd(obj, handler, id) {
2116         var onUp = function (e) {
2117                 _handlePointer(e, handler);
2118         };
2119
2120         obj['_leaflet_touchend' + id] = onUp;
2121         obj.addEventListener(POINTER_UP, onUp, false);
2122         obj.addEventListener(POINTER_CANCEL, onUp, false);
2123 }
2124
2125 /*
2126  * Extends the event handling code with double tap support for mobile browsers.
2127  */
2128
2129 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2130 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2131 var _pre = '_leaflet_';
2132
2133 // inspired by Zepto touch code by Thomas Fuchs
2134 function addDoubleTapListener(obj, handler, id) {
2135         var last, touch$$1,
2136             doubleTap = false,
2137             delay = 250;
2138
2139         function onTouchStart(e) {
2140                 var count;
2141
2142                 if (pointer) {
2143                         if ((!edge) || e.pointerType === 'mouse') { return; }
2144                         count = _pointersCount;
2145                 } else {
2146                         count = e.touches.length;
2147                 }
2148
2149                 if (count > 1) { return; }
2150
2151                 var now = Date.now(),
2152                     delta = now - (last || now);
2153
2154                 touch$$1 = e.touches ? e.touches[0] : e;
2155                 doubleTap = (delta > 0 && delta <= delay);
2156                 last = now;
2157         }
2158
2159         function onTouchEnd(e) {
2160                 if (doubleTap && !touch$$1.cancelBubble) {
2161                         if (pointer) {
2162                                 if ((!edge) || e.pointerType === 'mouse') { return; }
2163                                 // work around .type being readonly with MSPointer* events
2164                                 var newTouch = {},
2165                                     prop, i;
2166
2167                                 for (i in touch$$1) {
2168                                         prop = touch$$1[i];
2169                                         newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2170                                 }
2171                                 touch$$1 = newTouch;
2172                         }
2173                         touch$$1.type = 'dblclick';
2174                         handler(touch$$1);
2175                         last = null;
2176                 }
2177         }
2178
2179         obj[_pre + _touchstart + id] = onTouchStart;
2180         obj[_pre + _touchend + id] = onTouchEnd;
2181         obj[_pre + 'dblclick' + id] = handler;
2182
2183         obj.addEventListener(_touchstart, onTouchStart, false);
2184         obj.addEventListener(_touchend, onTouchEnd, false);
2185
2186         // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2187         // the browser doesn't fire touchend/pointerup events but does fire
2188         // native dblclicks. See #4127.
2189         // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2190         obj.addEventListener('dblclick', handler, false);
2191
2192         return this;
2193 }
2194
2195 function removeDoubleTapListener(obj, id) {
2196         var touchstart = obj[_pre + _touchstart + id],
2197             touchend = obj[_pre + _touchend + id],
2198             dblclick = obj[_pre + 'dblclick' + id];
2199
2200         obj.removeEventListener(_touchstart, touchstart, false);
2201         obj.removeEventListener(_touchend, touchend, false);
2202         if (!edge) {
2203                 obj.removeEventListener('dblclick', dblclick, false);
2204         }
2205
2206         return this;
2207 }
2208
2209 /*
2210  * @namespace DomUtil
2211  *
2212  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2213  * tree, used by Leaflet internally.
2214  *
2215  * Most functions expecting or returning a `HTMLElement` also work for
2216  * SVG elements. The only difference is that classes refer to CSS classes
2217  * in HTML and SVG classes in SVG.
2218  */
2219
2220
2221 // @property TRANSFORM: String
2222 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2223 var TRANSFORM = testProp(
2224         ['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2225
2226 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2227 // the same for the transitionend event, in particular the Android 4.1 stock browser
2228
2229 // @property TRANSITION: String
2230 // Vendor-prefixed transition style name.
2231 var TRANSITION = testProp(
2232         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2233
2234 // @property TRANSITION_END: String
2235 // Vendor-prefixed transitionend event name.
2236 var TRANSITION_END =
2237         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2238
2239
2240 // @function get(id: String|HTMLElement): HTMLElement
2241 // Returns an element given its DOM id, or returns the element itself
2242 // if it was passed directly.
2243 function get(id) {
2244         return typeof id === 'string' ? document.getElementById(id) : id;
2245 }
2246
2247 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2248 // Returns the value for a certain style attribute on an element,
2249 // including computed values or values set through CSS.
2250 function getStyle(el, style) {
2251         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2252
2253         if ((!value || value === 'auto') && document.defaultView) {
2254                 var css = document.defaultView.getComputedStyle(el, null);
2255                 value = css ? css[style] : null;
2256         }
2257         return value === 'auto' ? null : value;
2258 }
2259
2260 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2261 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2262 function create$1(tagName, className, container) {
2263         var el = document.createElement(tagName);
2264         el.className = className || '';
2265
2266         if (container) {
2267                 container.appendChild(el);
2268         }
2269         return el;
2270 }
2271
2272 // @function remove(el: HTMLElement)
2273 // Removes `el` from its parent element
2274 function remove(el) {
2275         var parent = el.parentNode;
2276         if (parent) {
2277                 parent.removeChild(el);
2278         }
2279 }
2280
2281 // @function empty(el: HTMLElement)
2282 // Removes all of `el`'s children elements from `el`
2283 function empty(el) {
2284         while (el.firstChild) {
2285                 el.removeChild(el.firstChild);
2286         }
2287 }
2288
2289 // @function toFront(el: HTMLElement)
2290 // Makes `el` the last child of its parent, so it renders in front of the other children.
2291 function toFront(el) {
2292         var parent = el.parentNode;
2293         if (parent.lastChild !== el) {
2294                 parent.appendChild(el);
2295         }
2296 }
2297
2298 // @function toBack(el: HTMLElement)
2299 // Makes `el` the first child of its parent, so it renders behind the other children.
2300 function toBack(el) {
2301         var parent = el.parentNode;
2302         if (parent.firstChild !== el) {
2303                 parent.insertBefore(el, parent.firstChild);
2304         }
2305 }
2306
2307 // @function hasClass(el: HTMLElement, name: String): Boolean
2308 // Returns `true` if the element's class attribute contains `name`.
2309 function hasClass(el, name) {
2310         if (el.classList !== undefined) {
2311                 return el.classList.contains(name);
2312         }
2313         var className = getClass(el);
2314         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2315 }
2316
2317 // @function addClass(el: HTMLElement, name: String)
2318 // Adds `name` to the element's class attribute.
2319 function addClass(el, name) {
2320         if (el.classList !== undefined) {
2321                 var classes = splitWords(name);
2322                 for (var i = 0, len = classes.length; i < len; i++) {
2323                         el.classList.add(classes[i]);
2324                 }
2325         } else if (!hasClass(el, name)) {
2326                 var className = getClass(el);
2327                 setClass(el, (className ? className + ' ' : '') + name);
2328         }
2329 }
2330
2331 // @function removeClass(el: HTMLElement, name: String)
2332 // Removes `name` from the element's class attribute.
2333 function removeClass(el, name) {
2334         if (el.classList !== undefined) {
2335                 el.classList.remove(name);
2336         } else {
2337                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2338         }
2339 }
2340
2341 // @function setClass(el: HTMLElement, name: String)
2342 // Sets the element's class.
2343 function setClass(el, name) {
2344         if (el.className.baseVal === undefined) {
2345                 el.className = name;
2346         } else {
2347                 // in case of SVG element
2348                 el.className.baseVal = name;
2349         }
2350 }
2351
2352 // @function getClass(el: HTMLElement): String
2353 // Returns the element's class.
2354 function getClass(el) {
2355         return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2356 }
2357
2358 // @function setOpacity(el: HTMLElement, opacity: Number)
2359 // Set the opacity of an element (including old IE support).
2360 // `opacity` must be a number from `0` to `1`.
2361 function setOpacity(el, value) {
2362         if ('opacity' in el.style) {
2363                 el.style.opacity = value;
2364         } else if ('filter' in el.style) {
2365                 _setOpacityIE(el, value);
2366         }
2367 }
2368
2369 function _setOpacityIE(el, value) {
2370         var filter = false,
2371             filterName = 'DXImageTransform.Microsoft.Alpha';
2372
2373         // filters collection throws an error if we try to retrieve a filter that doesn't exist
2374         try {
2375                 filter = el.filters.item(filterName);
2376         } catch (e) {
2377                 // don't set opacity to 1 if we haven't already set an opacity,
2378                 // it isn't needed and breaks transparent pngs.
2379                 if (value === 1) { return; }
2380         }
2381
2382         value = Math.round(value * 100);
2383
2384         if (filter) {
2385                 filter.Enabled = (value !== 100);
2386                 filter.Opacity = value;
2387         } else {
2388                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2389         }
2390 }
2391
2392 // @function testProp(props: String[]): String|false
2393 // Goes through the array of style names and returns the first name
2394 // that is a valid style name for an element. If no such name is found,
2395 // it returns false. Useful for vendor-prefixed styles like `transform`.
2396 function testProp(props) {
2397         var style = document.documentElement.style;
2398
2399         for (var i = 0; i < props.length; i++) {
2400                 if (props[i] in style) {
2401                         return props[i];
2402                 }
2403         }
2404         return false;
2405 }
2406
2407 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2408 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2409 // and optionally scaled by `scale`. Does not have an effect if the
2410 // browser doesn't support 3D CSS transforms.
2411 function setTransform(el, offset, scale) {
2412         var pos = offset || new Point(0, 0);
2413
2414         el.style[TRANSFORM] =
2415                 (ie3d ?
2416                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2417                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2418                 (scale ? ' scale(' + scale + ')' : '');
2419 }
2420
2421 // @function setPosition(el: HTMLElement, position: Point)
2422 // Sets the position of `el` to coordinates specified by `position`,
2423 // using CSS translate or top/left positioning depending on the browser
2424 // (used by Leaflet internally to position its layers).
2425 function setPosition(el, point) {
2426
2427         /*eslint-disable */
2428         el._leaflet_pos = point;
2429         /* eslint-enable */
2430
2431         if (any3d) {
2432                 setTransform(el, point);
2433         } else {
2434                 el.style.left = point.x + 'px';
2435                 el.style.top = point.y + 'px';
2436         }
2437 }
2438
2439 // @function getPosition(el: HTMLElement): Point
2440 // Returns the coordinates of an element previously positioned with setPosition.
2441 function getPosition(el) {
2442         // this method is only used for elements previously positioned using setPosition,
2443         // so it's safe to cache the position for performance
2444
2445         return el._leaflet_pos || new Point(0, 0);
2446 }
2447
2448 // @function disableTextSelection()
2449 // Prevents the user from generating `selectstart` DOM events, usually generated
2450 // when the user drags the mouse through a page with text. Used internally
2451 // by Leaflet to override the behaviour of any click-and-drag interaction on
2452 // the map. Affects drag interactions on the whole document.
2453
2454 // @function enableTextSelection()
2455 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2456 var disableTextSelection;
2457 var enableTextSelection;
2458 var _userSelect;
2459 if ('onselectstart' in document) {
2460         disableTextSelection = function () {
2461                 on(window, 'selectstart', preventDefault);
2462         };
2463         enableTextSelection = function () {
2464                 off(window, 'selectstart', preventDefault);
2465         };
2466 } else {
2467         var userSelectProperty = testProp(
2468                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2469
2470         disableTextSelection = function () {
2471                 if (userSelectProperty) {
2472                         var style = document.documentElement.style;
2473                         _userSelect = style[userSelectProperty];
2474                         style[userSelectProperty] = 'none';
2475                 }
2476         };
2477         enableTextSelection = function () {
2478                 if (userSelectProperty) {
2479                         document.documentElement.style[userSelectProperty] = _userSelect;
2480                         _userSelect = undefined;
2481                 }
2482         };
2483 }
2484
2485 // @function disableImageDrag()
2486 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2487 // for `dragstart` DOM events, usually generated when the user drags an image.
2488 function disableImageDrag() {
2489         on(window, 'dragstart', preventDefault);
2490 }
2491
2492 // @function enableImageDrag()
2493 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2494 function enableImageDrag() {
2495         off(window, 'dragstart', preventDefault);
2496 }
2497
2498 var _outlineElement;
2499 var _outlineStyle;
2500 // @function preventOutline(el: HTMLElement)
2501 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2502 // of the element `el` invisible. Used internally by Leaflet to prevent
2503 // focusable elements from displaying an outline when the user performs a
2504 // drag interaction on them.
2505 function preventOutline(element) {
2506         while (element.tabIndex === -1) {
2507                 element = element.parentNode;
2508         }
2509         if (!element.style) { return; }
2510         restoreOutline();
2511         _outlineElement = element;
2512         _outlineStyle = element.style.outline;
2513         element.style.outline = 'none';
2514         on(window, 'keydown', restoreOutline);
2515 }
2516
2517 // @function restoreOutline()
2518 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2519 function restoreOutline() {
2520         if (!_outlineElement) { return; }
2521         _outlineElement.style.outline = _outlineStyle;
2522         _outlineElement = undefined;
2523         _outlineStyle = undefined;
2524         off(window, 'keydown', restoreOutline);
2525 }
2526
2527 // @function getSizedParentNode(el: HTMLElement): HTMLElement
2528 // Finds the closest parent node which size (width and height) is not null.
2529 function getSizedParentNode(element) {
2530         do {
2531                 element = element.parentNode;
2532         } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
2533         return element;
2534 }
2535
2536 // @function getScale(el: HTMLElement): Object
2537 // Computes the CSS scale currently applied on the element.
2538 // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
2539 // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
2540 function getScale(element) {
2541         var rect = element.getBoundingClientRect(); // Read-only in old browsers.
2542
2543         return {
2544                 x: rect.width / element.offsetWidth || 1,
2545                 y: rect.height / element.offsetHeight || 1,
2546                 boundingClientRect: rect
2547         };
2548 }
2549
2550
2551 var DomUtil = (Object.freeze || Object)({
2552         TRANSFORM: TRANSFORM,
2553         TRANSITION: TRANSITION,
2554         TRANSITION_END: TRANSITION_END,
2555         get: get,
2556         getStyle: getStyle,
2557         create: create$1,
2558         remove: remove,
2559         empty: empty,
2560         toFront: toFront,
2561         toBack: toBack,
2562         hasClass: hasClass,
2563         addClass: addClass,
2564         removeClass: removeClass,
2565         setClass: setClass,
2566         getClass: getClass,
2567         setOpacity: setOpacity,
2568         testProp: testProp,
2569         setTransform: setTransform,
2570         setPosition: setPosition,
2571         getPosition: getPosition,
2572         disableTextSelection: disableTextSelection,
2573         enableTextSelection: enableTextSelection,
2574         disableImageDrag: disableImageDrag,
2575         enableImageDrag: enableImageDrag,
2576         preventOutline: preventOutline,
2577         restoreOutline: restoreOutline,
2578         getSizedParentNode: getSizedParentNode,
2579         getScale: getScale
2580 });
2581
2582 /*
2583  * @namespace DomEvent
2584  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2585  */
2586
2587 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2588
2589 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2590 // Adds a listener function (`fn`) to a particular DOM event type of the
2591 // element `el`. You can optionally specify the context of the listener
2592 // (object the `this` keyword will point to). You can also pass several
2593 // space-separated types (e.g. `'click dblclick'`).
2594
2595 // @alternative
2596 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2597 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2598 function on(obj, types, fn, context) {
2599
2600         if (typeof types === 'object') {
2601                 for (var type in types) {
2602                         addOne(obj, type, types[type], fn);
2603                 }
2604         } else {
2605                 types = splitWords(types);
2606
2607                 for (var i = 0, len = types.length; i < len; i++) {
2608                         addOne(obj, types[i], fn, context);
2609                 }
2610         }
2611
2612         return this;
2613 }
2614
2615 var eventsKey = '_leaflet_events';
2616
2617 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2618 // Removes a previously added listener function.
2619 // Note that if you passed a custom context to on, you must pass the same
2620 // context to `off` in order to remove the listener.
2621
2622 // @alternative
2623 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2624 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2625 function off(obj, types, fn, context) {
2626
2627         if (typeof types === 'object') {
2628                 for (var type in types) {
2629                         removeOne(obj, type, types[type], fn);
2630                 }
2631         } else if (types) {
2632                 types = splitWords(types);
2633
2634                 for (var i = 0, len = types.length; i < len; i++) {
2635                         removeOne(obj, types[i], fn, context);
2636                 }
2637         } else {
2638                 for (var j in obj[eventsKey]) {
2639                         removeOne(obj, j, obj[eventsKey][j]);
2640                 }
2641                 delete obj[eventsKey];
2642         }
2643
2644         return this;
2645 }
2646
2647 function addOne(obj, type, fn, context) {
2648         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2649
2650         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2651
2652         var handler = function (e) {
2653                 return fn.call(context || obj, e || window.event);
2654         };
2655
2656         var originalHandler = handler;
2657
2658         if (pointer && type.indexOf('touch') === 0) {
2659                 // Needs DomEvent.Pointer.js
2660                 addPointerListener(obj, type, handler, id);
2661
2662         } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2663                    !(pointer && chrome)) {
2664                 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2665                 // See #5180
2666                 addDoubleTapListener(obj, handler, id);
2667
2668         } else if ('addEventListener' in obj) {
2669
2670                 if (type === 'mousewheel') {
2671                         obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2672
2673                 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2674                         handler = function (e) {
2675                                 e = e || window.event;
2676                                 if (isExternalTarget(obj, e)) {
2677                                         originalHandler(e);
2678                                 }
2679                         };
2680                         obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2681
2682                 } else {
2683                         if (type === 'click' && android) {
2684                                 handler = function (e) {
2685                                         filterClick(e, originalHandler);
2686                                 };
2687                         }
2688                         obj.addEventListener(type, handler, false);
2689                 }
2690
2691         } else if ('attachEvent' in obj) {
2692                 obj.attachEvent('on' + type, handler);
2693         }
2694
2695         obj[eventsKey] = obj[eventsKey] || {};
2696         obj[eventsKey][id] = handler;
2697 }
2698
2699 function removeOne(obj, type, fn, context) {
2700
2701         var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2702             handler = obj[eventsKey] && obj[eventsKey][id];
2703
2704         if (!handler) { return this; }
2705
2706         if (pointer && type.indexOf('touch') === 0) {
2707                 removePointerListener(obj, type, id);
2708
2709         } else if (touch && (type === 'dblclick') && removeDoubleTapListener &&
2710                    !(pointer && chrome)) {
2711                 removeDoubleTapListener(obj, id);
2712
2713         } else if ('removeEventListener' in obj) {
2714
2715                 if (type === 'mousewheel') {
2716                         obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2717
2718                 } else {
2719                         obj.removeEventListener(
2720                                 type === 'mouseenter' ? 'mouseover' :
2721                                 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2722                 }
2723
2724         } else if ('detachEvent' in obj) {
2725                 obj.detachEvent('on' + type, handler);
2726         }
2727
2728         obj[eventsKey][id] = null;
2729 }
2730
2731 // @function stopPropagation(ev: DOMEvent): this
2732 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2733 // ```js
2734 // L.DomEvent.on(div, 'click', function (ev) {
2735 //      L.DomEvent.stopPropagation(ev);
2736 // });
2737 // ```
2738 function stopPropagation(e) {
2739
2740         if (e.stopPropagation) {
2741                 e.stopPropagation();
2742         } else if (e.originalEvent) {  // In case of Leaflet event.
2743                 e.originalEvent._stopped = true;
2744         } else {
2745                 e.cancelBubble = true;
2746         }
2747         skipped(e);
2748
2749         return this;
2750 }
2751
2752 // @function disableScrollPropagation(el: HTMLElement): this
2753 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2754 function disableScrollPropagation(el) {
2755         addOne(el, 'mousewheel', stopPropagation);
2756         return this;
2757 }
2758
2759 // @function disableClickPropagation(el: HTMLElement): this
2760 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2761 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2762 function disableClickPropagation(el) {
2763         on(el, 'mousedown touchstart dblclick', stopPropagation);
2764         addOne(el, 'click', fakeStop);
2765         return this;
2766 }
2767
2768 // @function preventDefault(ev: DOMEvent): this
2769 // Prevents the default action of the DOM Event `ev` from happening (such as
2770 // following a link in the href of the a element, or doing a POST request
2771 // with page reload when a `<form>` is submitted).
2772 // Use it inside listener functions.
2773 function preventDefault(e) {
2774         if (e.preventDefault) {
2775                 e.preventDefault();
2776         } else {
2777                 e.returnValue = false;
2778         }
2779         return this;
2780 }
2781
2782 // @function stop(ev: DOMEvent): this
2783 // Does `stopPropagation` and `preventDefault` at the same time.
2784 function stop(e) {
2785         preventDefault(e);
2786         stopPropagation(e);
2787         return this;
2788 }
2789
2790 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2791 // Gets normalized mouse position from a DOM event relative to the
2792 // `container` (border excluded) or to the whole page if not specified.
2793 function getMousePosition(e, container) {
2794         if (!container) {
2795                 return new Point(e.clientX, e.clientY);
2796         }
2797
2798         var scale = getScale(container),
2799             offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)
2800
2801         return new Point(
2802                 // offset.left/top values are in page scale (like clientX/Y),
2803                 // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
2804                 (e.clientX - offset.left) / scale.x - container.clientLeft,
2805                 (e.clientY - offset.top) / scale.y - container.clientTop
2806         );
2807 }
2808
2809 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2810 // and Firefox scrolls device pixels, not CSS pixels
2811 var wheelPxFactor =
2812         (win && chrome) ? 2 * window.devicePixelRatio :
2813         gecko ? window.devicePixelRatio : 1;
2814
2815 // @function getWheelDelta(ev: DOMEvent): Number
2816 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2817 // pixels scrolled (negative if scrolling down).
2818 // Events from pointing devices without precise scrolling are mapped to
2819 // a best guess of 60 pixels.
2820 function getWheelDelta(e) {
2821         return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2822                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2823                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2824                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2825                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
2826                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2827                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2828                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2829                0;
2830 }
2831
2832 var skipEvents = {};
2833
2834 function fakeStop(e) {
2835         // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2836         skipEvents[e.type] = true;
2837 }
2838
2839 function skipped(e) {
2840         var events = skipEvents[e.type];
2841         // reset when checking, as it's only used in map container and propagates outside of the map
2842         skipEvents[e.type] = false;
2843         return events;
2844 }
2845
2846 // check if element really left/entered the event target (for mouseenter/mouseleave)
2847 function isExternalTarget(el, e) {
2848
2849         var related = e.relatedTarget;
2850
2851         if (!related) { return true; }
2852
2853         try {
2854                 while (related && (related !== el)) {
2855                         related = related.parentNode;
2856                 }
2857         } catch (err) {
2858                 return false;
2859         }
2860         return (related !== el);
2861 }
2862
2863 var lastClick;
2864
2865 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2866 function filterClick(e, handler) {
2867         var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2868             elapsed = lastClick && (timeStamp - lastClick);
2869
2870         // are they closer together than 500ms yet more than 100ms?
2871         // Android typically triggers them ~300ms apart while multiple listeners
2872         // on the same event should be triggered far faster;
2873         // or check if click is simulated on the element, and if it is, reject any non-simulated events
2874
2875         if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2876                 stop(e);
2877                 return;
2878         }
2879         lastClick = timeStamp;
2880
2881         handler(e);
2882 }
2883
2884
2885
2886
2887 var DomEvent = (Object.freeze || Object)({
2888         on: on,
2889         off: off,
2890         stopPropagation: stopPropagation,
2891         disableScrollPropagation: disableScrollPropagation,
2892         disableClickPropagation: disableClickPropagation,
2893         preventDefault: preventDefault,
2894         stop: stop,
2895         getMousePosition: getMousePosition,
2896         getWheelDelta: getWheelDelta,
2897         fakeStop: fakeStop,
2898         skipped: skipped,
2899         isExternalTarget: isExternalTarget,
2900         addListener: on,
2901         removeListener: off
2902 });
2903
2904 /*
2905  * @class PosAnimation
2906  * @aka L.PosAnimation
2907  * @inherits Evented
2908  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2909  *
2910  * @example
2911  * ```js
2912  * var fx = new L.PosAnimation();
2913  * fx.run(el, [300, 500], 0.5);
2914  * ```
2915  *
2916  * @constructor L.PosAnimation()
2917  * Creates a `PosAnimation` object.
2918  *
2919  */
2920
2921 var PosAnimation = Evented.extend({
2922
2923         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2924         // Run an animation of a given element to a new position, optionally setting
2925         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2926         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2927         // `0.5` by default).
2928         run: function (el, newPos, duration, easeLinearity) {
2929                 this.stop();
2930
2931                 this._el = el;
2932                 this._inProgress = true;
2933                 this._duration = duration || 0.25;
2934                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2935
2936                 this._startPos = getPosition(el);
2937                 this._offset = newPos.subtract(this._startPos);
2938                 this._startTime = +new Date();
2939
2940                 // @event start: Event
2941                 // Fired when the animation starts
2942                 this.fire('start');
2943
2944                 this._animate();
2945         },
2946
2947         // @method stop()
2948         // Stops the animation (if currently running).
2949         stop: function () {
2950                 if (!this._inProgress) { return; }
2951
2952                 this._step(true);
2953                 this._complete();
2954         },
2955
2956         _animate: function () {
2957                 // animation loop
2958                 this._animId = requestAnimFrame(this._animate, this);
2959                 this._step();
2960         },
2961
2962         _step: function (round) {
2963                 var elapsed = (+new Date()) - this._startTime,
2964                     duration = this._duration * 1000;
2965
2966                 if (elapsed < duration) {
2967                         this._runFrame(this._easeOut(elapsed / duration), round);
2968                 } else {
2969                         this._runFrame(1);
2970                         this._complete();
2971                 }
2972         },
2973
2974         _runFrame: function (progress, round) {
2975                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2976                 if (round) {
2977                         pos._round();
2978                 }
2979                 setPosition(this._el, pos);
2980
2981                 // @event step: Event
2982                 // Fired continuously during the animation.
2983                 this.fire('step');
2984         },
2985
2986         _complete: function () {
2987                 cancelAnimFrame(this._animId);
2988
2989                 this._inProgress = false;
2990                 // @event end: Event
2991                 // Fired when the animation ends.
2992                 this.fire('end');
2993         },
2994
2995         _easeOut: function (t) {
2996                 return 1 - Math.pow(1 - t, this._easeOutPower);
2997         }
2998 });
2999
3000 /*
3001  * @class Map
3002  * @aka L.Map
3003  * @inherits Evented
3004  *
3005  * The central class of the API — it is used to create a map on a page and manipulate it.
3006  *
3007  * @example
3008  *
3009  * ```js
3010  * // initialize the map on the "map" div with a given center and zoom
3011  * var map = L.map('map', {
3012  *      center: [51.505, -0.09],
3013  *      zoom: 13
3014  * });
3015  * ```
3016  *
3017  */
3018
3019 var Map = Evented.extend({
3020
3021         options: {
3022                 // @section Map State Options
3023                 // @option crs: CRS = L.CRS.EPSG3857
3024                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
3025                 // sure what it means.
3026                 crs: EPSG3857,
3027
3028                 // @option center: LatLng = undefined
3029                 // Initial geographic center of the map
3030                 center: undefined,
3031
3032                 // @option zoom: Number = undefined
3033                 // Initial map zoom level
3034                 zoom: undefined,
3035
3036                 // @option minZoom: Number = *
3037                 // Minimum zoom level of the map.
3038                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3039                 // the lowest of their `minZoom` options will be used instead.
3040                 minZoom: undefined,
3041
3042                 // @option maxZoom: Number = *
3043                 // Maximum zoom level of the map.
3044                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
3045                 // the highest of their `maxZoom` options will be used instead.
3046                 maxZoom: undefined,
3047
3048                 // @option layers: Layer[] = []
3049                 // Array of layers that will be added to the map initially
3050                 layers: [],
3051
3052                 // @option maxBounds: LatLngBounds = null
3053                 // When this option is set, the map restricts the view to the given
3054                 // geographical bounds, bouncing the user back if the user tries to pan
3055                 // outside the view. To set the restriction dynamically, use
3056                 // [`setMaxBounds`](#map-setmaxbounds) method.
3057                 maxBounds: undefined,
3058
3059                 // @option renderer: Renderer = *
3060                 // The default method for drawing vector layers on the map. `L.SVG`
3061                 // or `L.Canvas` by default depending on browser support.
3062                 renderer: undefined,
3063
3064
3065                 // @section Animation Options
3066                 // @option zoomAnimation: Boolean = true
3067                 // Whether the map zoom animation is enabled. By default it's enabled
3068                 // in all browsers that support CSS3 Transitions except Android.
3069                 zoomAnimation: true,
3070
3071                 // @option zoomAnimationThreshold: Number = 4
3072                 // Won't animate zoom if the zoom difference exceeds this value.
3073                 zoomAnimationThreshold: 4,
3074
3075                 // @option fadeAnimation: Boolean = true
3076                 // Whether the tile fade animation is enabled. By default it's enabled
3077                 // in all browsers that support CSS3 Transitions except Android.
3078                 fadeAnimation: true,
3079
3080                 // @option markerZoomAnimation: Boolean = true
3081                 // Whether markers animate their zoom with the zoom animation, if disabled
3082                 // they will disappear for the length of the animation. By default it's
3083                 // enabled in all browsers that support CSS3 Transitions except Android.
3084                 markerZoomAnimation: true,
3085
3086                 // @option transform3DLimit: Number = 2^23
3087                 // Defines the maximum size of a CSS translation transform. The default
3088                 // value should not be changed unless a web browser positions layers in
3089                 // the wrong place after doing a large `panBy`.
3090                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3091
3092                 // @section Interaction Options
3093                 // @option zoomSnap: Number = 1
3094                 // Forces the map's zoom level to always be a multiple of this, particularly
3095                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3096                 // By default, the zoom level snaps to the nearest integer; lower values
3097                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3098                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3099                 zoomSnap: 1,
3100
3101                 // @option zoomDelta: Number = 1
3102                 // Controls how much the map's zoom level will change after a
3103                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3104                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3105                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3106                 zoomDelta: 1,
3107
3108                 // @option trackResize: Boolean = true
3109                 // Whether the map automatically handles browser window resize to update itself.
3110                 trackResize: true
3111         },
3112
3113         initialize: function (id, options) { // (HTMLElement or String, Object)
3114                 options = setOptions(this, options);
3115
3116                 this._initContainer(id);
3117                 this._initLayout();
3118
3119                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3120                 this._onResize = bind(this._onResize, this);
3121
3122                 this._initEvents();
3123
3124                 if (options.maxBounds) {
3125                         this.setMaxBounds(options.maxBounds);
3126                 }
3127
3128                 if (options.zoom !== undefined) {
3129                         this._zoom = this._limitZoom(options.zoom);
3130                 }
3131
3132                 if (options.center && options.zoom !== undefined) {
3133                         this.setView(toLatLng(options.center), options.zoom, {reset: true});
3134                 }
3135
3136                 this._handlers = [];
3137                 this._layers = {};
3138                 this._zoomBoundLayers = {};
3139                 this._sizeChanged = true;
3140
3141                 this.callInitHooks();
3142
3143                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3144                 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3145                                 this.options.zoomAnimation;
3146
3147                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3148                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3149                 if (this._zoomAnimated) {
3150                         this._createAnimProxy();
3151                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3152                 }
3153
3154                 this._addLayers(this.options.layers);
3155         },
3156
3157
3158         // @section Methods for modifying map state
3159
3160         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3161         // Sets the view of the map (geographical center and zoom) with the given
3162         // animation options.
3163         setView: function (center, zoom, options) {
3164
3165                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3166                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3167                 options = options || {};
3168
3169                 this._stop();
3170
3171                 if (this._loaded && !options.reset && options !== true) {
3172
3173                         if (options.animate !== undefined) {
3174                                 options.zoom = extend({animate: options.animate}, options.zoom);
3175                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3176                         }
3177
3178                         // try animating pan or zoom
3179                         var moved = (this._zoom !== zoom) ?
3180                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3181                                 this._tryAnimatedPan(center, options.pan);
3182
3183                         if (moved) {
3184                                 // prevent resize handler call, the view will refresh after animation anyway
3185                                 clearTimeout(this._sizeTimer);
3186                                 return this;
3187                         }
3188                 }
3189
3190                 // animation didn't start, just reset the map view
3191                 this._resetView(center, zoom);
3192
3193                 return this;
3194         },
3195
3196         // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3197         // Sets the zoom of the map.
3198         setZoom: function (zoom, options) {
3199                 if (!this._loaded) {
3200                         this._zoom = zoom;
3201                         return this;
3202                 }
3203                 return this.setView(this.getCenter(), zoom, {zoom: options});
3204         },
3205
3206         // @method zoomIn(delta?: Number, options?: Zoom options): this
3207         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3208         zoomIn: function (delta, options) {
3209                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3210                 return this.setZoom(this._zoom + delta, options);
3211         },
3212
3213         // @method zoomOut(delta?: Number, options?: Zoom options): this
3214         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3215         zoomOut: function (delta, options) {
3216                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3217                 return this.setZoom(this._zoom - delta, options);
3218         },
3219
3220         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3221         // Zooms the map while keeping a specified geographical point on the map
3222         // stationary (e.g. used internally for scroll zoom and double-click zoom).
3223         // @alternative
3224         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3225         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3226         setZoomAround: function (latlng, zoom, options) {
3227                 var scale = this.getZoomScale(zoom),
3228                     viewHalf = this.getSize().divideBy(2),
3229                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3230
3231                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3232                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3233
3234                 return this.setView(newCenter, zoom, {zoom: options});
3235         },
3236
3237         _getBoundsCenterZoom: function (bounds, options) {
3238
3239                 options = options || {};
3240                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3241
3242                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3243                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3244
3245                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3246
3247                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3248
3249                 if (zoom === Infinity) {
3250                         return {
3251                                 center: bounds.getCenter(),
3252                                 zoom: zoom
3253                         };
3254                 }
3255
3256                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3257
3258                     swPoint = this.project(bounds.getSouthWest(), zoom),
3259                     nePoint = this.project(bounds.getNorthEast(), zoom),
3260                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3261
3262                 return {
3263                         center: center,
3264                         zoom: zoom
3265                 };
3266         },
3267
3268         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3269         // Sets a map view that contains the given geographical bounds with the
3270         // maximum zoom level possible.
3271         fitBounds: function (bounds, options) {
3272
3273                 bounds = toLatLngBounds(bounds);
3274
3275                 if (!bounds.isValid()) {
3276                         throw new Error('Bounds are not valid.');
3277                 }
3278
3279                 var target = this._getBoundsCenterZoom(bounds, options);
3280                 return this.setView(target.center, target.zoom, options);
3281         },
3282
3283         // @method fitWorld(options?: fitBounds options): this
3284         // Sets a map view that mostly contains the whole world with the maximum
3285         // zoom level possible.
3286         fitWorld: function (options) {
3287                 return this.fitBounds([[-90, -180], [90, 180]], options);
3288         },
3289
3290         // @method panTo(latlng: LatLng, options?: Pan options): this
3291         // Pans the map to a given center.
3292         panTo: function (center, options) { // (LatLng)
3293                 return this.setView(center, this._zoom, {pan: options});
3294         },
3295
3296         // @method panBy(offset: Point, options?: Pan options): this
3297         // Pans the map by a given number of pixels (animated).
3298         panBy: function (offset, options) {
3299                 offset = toPoint(offset).round();
3300                 options = options || {};
3301
3302                 if (!offset.x && !offset.y) {
3303                         return this.fire('moveend');
3304                 }
3305                 // If we pan too far, Chrome gets issues with tiles
3306                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3307                 if (options.animate !== true && !this.getSize().contains(offset)) {
3308                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3309                         return this;
3310                 }
3311
3312                 if (!this._panAnim) {
3313                         this._panAnim = new PosAnimation();
3314
3315                         this._panAnim.on({
3316                                 'step': this._onPanTransitionStep,
3317                                 'end': this._onPanTransitionEnd
3318                         }, this);
3319                 }
3320
3321                 // don't fire movestart if animating inertia
3322                 if (!options.noMoveStart) {
3323                         this.fire('movestart');
3324                 }
3325
3326                 // animate pan unless animate: false specified
3327                 if (options.animate !== false) {
3328                         addClass(this._mapPane, 'leaflet-pan-anim');
3329
3330                         var newPos = this._getMapPanePos().subtract(offset).round();
3331                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3332                 } else {
3333                         this._rawPanBy(offset);
3334                         this.fire('move').fire('moveend');
3335                 }
3336
3337                 return this;
3338         },
3339
3340         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3341         // Sets the view of the map (geographical center and zoom) performing a smooth
3342         // pan-zoom animation.
3343         flyTo: function (targetCenter, targetZoom, options) {
3344
3345                 options = options || {};
3346                 if (options.animate === false || !any3d) {
3347                         return this.setView(targetCenter, targetZoom, options);
3348                 }
3349
3350                 this._stop();
3351
3352                 var from = this.project(this.getCenter()),
3353                     to = this.project(targetCenter),
3354                     size = this.getSize(),
3355                     startZoom = this._zoom;
3356
3357                 targetCenter = toLatLng(targetCenter);
3358                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3359
3360                 var w0 = Math.max(size.x, size.y),
3361                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3362                     u1 = (to.distanceTo(from)) || 1,
3363                     rho = 1.42,
3364                     rho2 = rho * rho;
3365
3366                 function r(i) {
3367                         var s1 = i ? -1 : 1,
3368                             s2 = i ? w1 : w0,
3369                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3370                             b1 = 2 * s2 * rho2 * u1,
3371                             b = t1 / b1,
3372                             sq = Math.sqrt(b * b + 1) - b;
3373
3374                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
3375                             // thus triggering an infinite loop in flyTo
3376                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
3377
3378                         return log;
3379                 }
3380
3381                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3382                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3383                 function tanh(n) { return sinh(n) / cosh(n); }
3384
3385                 var r0 = r(0);
3386
3387                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3388                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3389
3390                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3391
3392                 var start = Date.now(),
3393                     S = (r(1) - r0) / rho,
3394                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3395
3396                 function frame() {
3397                         var t = (Date.now() - start) / duration,
3398                             s = easeOut(t) * S;
3399
3400                         if (t <= 1) {
3401                                 this._flyToFrame = requestAnimFrame(frame, this);
3402
3403                                 this._move(
3404                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3405                                         this.getScaleZoom(w0 / w(s), startZoom),
3406                                         {flyTo: true});
3407
3408                         } else {
3409                                 this
3410                                         ._move(targetCenter, targetZoom)
3411                                         ._moveEnd(true);
3412                         }
3413                 }
3414
3415                 this._moveStart(true, options.noMoveStart);
3416
3417                 frame.call(this);
3418                 return this;
3419         },
3420
3421         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3422         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3423         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3424         flyToBounds: function (bounds, options) {
3425                 var target = this._getBoundsCenterZoom(bounds, options);
3426                 return this.flyTo(target.center, target.zoom, options);
3427         },
3428
3429         // @method setMaxBounds(bounds: Bounds): this
3430         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3431         setMaxBounds: function (bounds) {
3432                 bounds = toLatLngBounds(bounds);
3433
3434                 if (!bounds.isValid()) {
3435                         this.options.maxBounds = null;
3436                         return this.off('moveend', this._panInsideMaxBounds);
3437                 } else if (this.options.maxBounds) {
3438                         this.off('moveend', this._panInsideMaxBounds);
3439                 }
3440
3441                 this.options.maxBounds = bounds;
3442
3443                 if (this._loaded) {
3444                         this._panInsideMaxBounds();
3445                 }
3446
3447                 return this.on('moveend', this._panInsideMaxBounds);
3448         },
3449
3450         // @method setMinZoom(zoom: Number): this
3451         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3452         setMinZoom: function (zoom) {
3453                 var oldZoom = this.options.minZoom;
3454                 this.options.minZoom = zoom;
3455
3456                 if (this._loaded && oldZoom !== zoom) {
3457                         this.fire('zoomlevelschange');
3458
3459                         if (this.getZoom() < this.options.minZoom) {
3460                                 return this.setZoom(zoom);
3461                         }
3462                 }
3463
3464                 return this;
3465         },
3466
3467         // @method setMaxZoom(zoom: Number): this
3468         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3469         setMaxZoom: function (zoom) {
3470                 var oldZoom = this.options.maxZoom;
3471                 this.options.maxZoom = zoom;
3472
3473                 if (this._loaded && oldZoom !== zoom) {
3474                         this.fire('zoomlevelschange');
3475
3476                         if (this.getZoom() > this.options.maxZoom) {
3477                                 return this.setZoom(zoom);
3478                         }
3479                 }
3480
3481                 return this;
3482         },
3483
3484         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3485         // 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.
3486         panInsideBounds: function (bounds, options) {
3487                 this._enforcingBounds = true;
3488                 var center = this.getCenter(),
3489                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3490
3491                 if (!center.equals(newCenter)) {
3492                         this.panTo(newCenter, options);
3493                 }
3494
3495                 this._enforcingBounds = false;
3496                 return this;
3497         },
3498
3499         // @method invalidateSize(options: Zoom/pan options): this
3500         // Checks if the map container size changed and updates the map if so —
3501         // call it after you've changed the map size dynamically, also animating
3502         // pan by default. If `options.pan` is `false`, panning will not occur.
3503         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3504         // that it doesn't happen often even if the method is called many
3505         // times in a row.
3506
3507         // @alternative
3508         // @method invalidateSize(animate: Boolean): this
3509         // Checks if the map container size changed and updates the map if so —
3510         // call it after you've changed the map size dynamically, also animating
3511         // pan by default.
3512         invalidateSize: function (options) {
3513                 if (!this._loaded) { return this; }
3514
3515                 options = extend({
3516                         animate: false,
3517                         pan: true
3518                 }, options === true ? {animate: true} : options);
3519
3520                 var oldSize = this.getSize();
3521                 this._sizeChanged = true;
3522                 this._lastCenter = null;
3523
3524                 var newSize = this.getSize(),
3525                     oldCenter = oldSize.divideBy(2).round(),
3526                     newCenter = newSize.divideBy(2).round(),
3527                     offset = oldCenter.subtract(newCenter);
3528
3529                 if (!offset.x && !offset.y) { return this; }
3530
3531                 if (options.animate && options.pan) {
3532                         this.panBy(offset);
3533
3534                 } else {
3535                         if (options.pan) {
3536                                 this._rawPanBy(offset);
3537                         }
3538
3539                         this.fire('move');
3540
3541                         if (options.debounceMoveend) {
3542                                 clearTimeout(this._sizeTimer);
3543                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3544                         } else {
3545                                 this.fire('moveend');
3546                         }
3547                 }
3548
3549                 // @section Map state change events
3550                 // @event resize: ResizeEvent
3551                 // Fired when the map is resized.
3552                 return this.fire('resize', {
3553                         oldSize: oldSize,
3554                         newSize: newSize
3555                 });
3556         },
3557
3558         // @section Methods for modifying map state
3559         // @method stop(): this
3560         // Stops the currently running `panTo` or `flyTo` animation, if any.
3561         stop: function () {
3562                 this.setZoom(this._limitZoom(this._zoom));
3563                 if (!this.options.zoomSnap) {
3564                         this.fire('viewreset');
3565                 }
3566                 return this._stop();
3567         },
3568
3569         // @section Geolocation methods
3570         // @method locate(options?: Locate options): this
3571         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3572         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3573         // and optionally sets the map view to the user's location with respect to
3574         // detection accuracy (or to the world view if geolocation failed).
3575         // Note that, if your page doesn't use HTTPS, this method will fail in
3576         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3577         // See `Locate options` for more details.
3578         locate: function (options) {
3579
3580                 options = this._locateOptions = extend({
3581                         timeout: 10000,
3582                         watch: false
3583                         // setView: false
3584                         // maxZoom: <Number>
3585                         // maximumAge: 0
3586                         // enableHighAccuracy: false
3587                 }, options);
3588
3589                 if (!('geolocation' in navigator)) {
3590                         this._handleGeolocationError({
3591                                 code: 0,
3592                                 message: 'Geolocation not supported.'
3593                         });
3594                         return this;
3595                 }
3596
3597                 var onResponse = bind(this._handleGeolocationResponse, this),
3598                     onError = bind(this._handleGeolocationError, this);
3599
3600                 if (options.watch) {
3601                         this._locationWatchId =
3602                                 navigator.geolocation.watchPosition(onResponse, onError, options);
3603                 } else {
3604                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3605                 }
3606                 return this;
3607         },
3608
3609         // @method stopLocate(): this
3610         // Stops watching location previously initiated by `map.locate({watch: true})`
3611         // and aborts resetting the map view if map.locate was called with
3612         // `{setView: true}`.
3613         stopLocate: function () {
3614                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3615                         navigator.geolocation.clearWatch(this._locationWatchId);
3616                 }
3617                 if (this._locateOptions) {
3618                         this._locateOptions.setView = false;
3619                 }
3620                 return this;
3621         },
3622
3623         _handleGeolocationError: function (error) {
3624                 var c = error.code,
3625                     message = error.message ||
3626                             (c === 1 ? 'permission denied' :
3627                             (c === 2 ? 'position unavailable' : 'timeout'));
3628
3629                 if (this._locateOptions.setView && !this._loaded) {
3630                         this.fitWorld();
3631                 }
3632
3633                 // @section Location events
3634                 // @event locationerror: ErrorEvent
3635                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3636                 this.fire('locationerror', {
3637                         code: c,
3638                         message: 'Geolocation error: ' + message + '.'
3639                 });
3640         },
3641
3642         _handleGeolocationResponse: function (pos) {
3643                 var lat = pos.coords.latitude,
3644                     lng = pos.coords.longitude,
3645                     latlng = new LatLng(lat, lng),
3646                     bounds = latlng.toBounds(pos.coords.accuracy * 2),
3647                     options = this._locateOptions;
3648
3649                 if (options.setView) {
3650                         var zoom = this.getBoundsZoom(bounds);
3651                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3652                 }
3653
3654                 var data = {
3655                         latlng: latlng,
3656                         bounds: bounds,
3657                         timestamp: pos.timestamp
3658                 };
3659
3660                 for (var i in pos.coords) {
3661                         if (typeof pos.coords[i] === 'number') {
3662                                 data[i] = pos.coords[i];
3663                         }
3664                 }
3665
3666                 // @event locationfound: LocationEvent
3667                 // Fired when geolocation (using the [`locate`](#map-locate) method)
3668                 // went successfully.
3669                 this.fire('locationfound', data);
3670         },
3671
3672         // TODO Appropriate docs section?
3673         // @section Other Methods
3674         // @method addHandler(name: String, HandlerClass: Function): this
3675         // Adds a new `Handler` to the map, given its name and constructor function.
3676         addHandler: function (name, HandlerClass) {
3677                 if (!HandlerClass) { return this; }
3678
3679                 var handler = this[name] = new HandlerClass(this);
3680
3681                 this._handlers.push(handler);
3682
3683                 if (this.options[name]) {
3684                         handler.enable();
3685                 }
3686
3687                 return this;
3688         },
3689
3690         // @method remove(): this
3691         // Destroys the map and clears all related event listeners.
3692         remove: function () {
3693
3694                 this._initEvents(true);
3695
3696                 if (this._containerId !== this._container._leaflet_id) {
3697                         throw new Error('Map container is being reused by another instance');
3698                 }
3699
3700                 try {
3701                         // throws error in IE6-8
3702                         delete this._container._leaflet_id;
3703                         delete this._containerId;
3704                 } catch (e) {
3705                         /*eslint-disable */
3706                         this._container._leaflet_id = undefined;
3707                         /* eslint-enable */
3708                         this._containerId = undefined;
3709                 }
3710
3711                 if (this._locationWatchId !== undefined) {
3712                         this.stopLocate();
3713                 }
3714
3715                 this._stop();
3716
3717                 remove(this._mapPane);
3718
3719                 if (this._clearControlPos) {
3720                         this._clearControlPos();
3721                 }
3722                 if (this._resizeRequest) {
3723                         cancelAnimFrame(this._resizeRequest);
3724                         this._resizeRequest = null;
3725                 }
3726
3727                 this._clearHandlers();
3728
3729                 if (this._loaded) {
3730                         // @section Map state change events
3731                         // @event unload: Event
3732                         // Fired when the map is destroyed with [remove](#map-remove) method.
3733                         this.fire('unload');
3734                 }
3735
3736                 var i;
3737                 for (i in this._layers) {
3738                         this._layers[i].remove();
3739                 }
3740                 for (i in this._panes) {
3741                         remove(this._panes[i]);
3742                 }
3743
3744                 this._layers = [];
3745                 this._panes = [];
3746                 delete this._mapPane;
3747                 delete this._renderer;
3748
3749                 return this;
3750         },
3751
3752         // @section Other Methods
3753         // @method createPane(name: String, container?: HTMLElement): HTMLElement
3754         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3755         // then returns it. The pane is created as a child of `container`, or
3756         // as a child of the main map pane if not set.
3757         createPane: function (name, container) {
3758                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3759                     pane = create$1('div', className, container || this._mapPane);
3760
3761                 if (name) {
3762                         this._panes[name] = pane;
3763                 }
3764                 return pane;
3765         },
3766
3767         // @section Methods for Getting Map State
3768
3769         // @method getCenter(): LatLng
3770         // Returns the geographical center of the map view
3771         getCenter: function () {
3772                 this._checkIfLoaded();
3773
3774                 if (this._lastCenter && !this._moved()) {
3775                         return this._lastCenter;
3776                 }
3777                 return this.layerPointToLatLng(this._getCenterLayerPoint());
3778         },
3779
3780         // @method getZoom(): Number
3781         // Returns the current zoom level of the map view
3782         getZoom: function () {
3783                 return this._zoom;
3784         },
3785
3786         // @method getBounds(): LatLngBounds
3787         // Returns the geographical bounds visible in the current map view
3788         getBounds: function () {
3789                 var bounds = this.getPixelBounds(),
3790                     sw = this.unproject(bounds.getBottomLeft()),
3791                     ne = this.unproject(bounds.getTopRight());
3792
3793                 return new LatLngBounds(sw, ne);
3794         },
3795
3796         // @method getMinZoom(): Number
3797         // 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.
3798         getMinZoom: function () {
3799                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3800         },
3801
3802         // @method getMaxZoom(): Number
3803         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3804         getMaxZoom: function () {
3805                 return this.options.maxZoom === undefined ?
3806                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3807                         this.options.maxZoom;
3808         },
3809
3810         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
3811         // Returns the maximum zoom level on which the given bounds fit to the map
3812         // view in its entirety. If `inside` (optional) is set to `true`, the method
3813         // instead returns the minimum zoom level on which the map view fits into
3814         // the given bounds in its entirety.
3815         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3816                 bounds = toLatLngBounds(bounds);
3817                 padding = toPoint(padding || [0, 0]);
3818
3819                 var zoom = this.getZoom() || 0,
3820                     min = this.getMinZoom(),
3821                     max = this.getMaxZoom(),
3822                     nw = bounds.getNorthWest(),
3823                     se = bounds.getSouthEast(),
3824                     size = this.getSize().subtract(padding),
3825                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3826                     snap = any3d ? this.options.zoomSnap : 1,
3827                     scalex = size.x / boundsSize.x,
3828                     scaley = size.y / boundsSize.y,
3829                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3830
3831                 zoom = this.getScaleZoom(scale, zoom);
3832
3833                 if (snap) {
3834                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3835                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3836                 }
3837
3838                 return Math.max(min, Math.min(max, zoom));
3839         },
3840
3841         // @method getSize(): Point
3842         // Returns the current size of the map container (in pixels).
3843         getSize: function () {
3844                 if (!this._size || this._sizeChanged) {
3845                         this._size = new Point(
3846                                 this._container.clientWidth || 0,
3847                                 this._container.clientHeight || 0);
3848
3849                         this._sizeChanged = false;
3850                 }
3851                 return this._size.clone();
3852         },
3853
3854         // @method getPixelBounds(): Bounds
3855         // Returns the bounds of the current map view in projected pixel
3856         // coordinates (sometimes useful in layer and overlay implementations).
3857         getPixelBounds: function (center, zoom) {
3858                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3859                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3860         },
3861
3862         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3863         // the map pane? "left point of the map layer" can be confusing, specially
3864         // since there can be negative offsets.
3865         // @method getPixelOrigin(): Point
3866         // Returns the projected pixel coordinates of the top left point of
3867         // the map layer (useful in custom layer and overlay implementations).
3868         getPixelOrigin: function () {
3869                 this._checkIfLoaded();
3870                 return this._pixelOrigin;
3871         },
3872
3873         // @method getPixelWorldBounds(zoom?: Number): Bounds
3874         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3875         // If `zoom` is omitted, the map's current zoom level is used.
3876         getPixelWorldBounds: function (zoom) {
3877                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3878         },
3879
3880         // @section Other Methods
3881
3882         // @method getPane(pane: String|HTMLElement): HTMLElement
3883         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3884         getPane: function (pane) {
3885                 return typeof pane === 'string' ? this._panes[pane] : pane;
3886         },
3887
3888         // @method getPanes(): Object
3889         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3890         // the panes as values.
3891         getPanes: function () {
3892                 return this._panes;
3893         },
3894
3895         // @method getContainer: HTMLElement
3896         // Returns the HTML element that contains the map.
3897         getContainer: function () {
3898                 return this._container;
3899         },
3900
3901
3902         // @section Conversion Methods
3903
3904         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3905         // Returns the scale factor to be applied to a map transition from zoom level
3906         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3907         getZoomScale: function (toZoom, fromZoom) {
3908                 // TODO replace with universal implementation after refactoring projections
3909                 var crs = this.options.crs;
3910                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3911                 return crs.scale(toZoom) / crs.scale(fromZoom);
3912         },
3913
3914         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3915         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3916         // level and everything is scaled by a factor of `scale`. Inverse of
3917         // [`getZoomScale`](#map-getZoomScale).
3918         getScaleZoom: function (scale, fromZoom) {
3919                 var crs = this.options.crs;
3920                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3921                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3922                 return isNaN(zoom) ? Infinity : zoom;
3923         },
3924
3925         // @method project(latlng: LatLng, zoom: Number): Point
3926         // Projects a geographical coordinate `LatLng` according to the projection
3927         // of the map's CRS, then scales it according to `zoom` and the CRS's
3928         // `Transformation`. The result is pixel coordinate relative to
3929         // the CRS origin.
3930         project: function (latlng, zoom) {
3931                 zoom = zoom === undefined ? this._zoom : zoom;
3932                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3933         },
3934
3935         // @method unproject(point: Point, zoom: Number): LatLng
3936         // Inverse of [`project`](#map-project).
3937         unproject: function (point, zoom) {
3938                 zoom = zoom === undefined ? this._zoom : zoom;
3939                 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3940         },
3941
3942         // @method layerPointToLatLng(point: Point): LatLng
3943         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3944         // returns the corresponding geographical coordinate (for the current zoom level).
3945         layerPointToLatLng: function (point) {
3946                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3947                 return this.unproject(projectedPoint);
3948         },
3949
3950         // @method latLngToLayerPoint(latlng: LatLng): Point
3951         // Given a geographical coordinate, returns the corresponding pixel coordinate
3952         // relative to the [origin pixel](#map-getpixelorigin).
3953         latLngToLayerPoint: function (latlng) {
3954                 var projectedPoint = this.project(toLatLng(latlng))._round();
3955                 return projectedPoint._subtract(this.getPixelOrigin());
3956         },
3957
3958         // @method wrapLatLng(latlng: LatLng): LatLng
3959         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3960         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3961         // CRS's bounds.
3962         // By default this means longitude is wrapped around the dateline so its
3963         // value is between -180 and +180 degrees.
3964         wrapLatLng: function (latlng) {
3965                 return this.options.crs.wrapLatLng(toLatLng(latlng));
3966         },
3967
3968         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3969         // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3970         // its center is within the CRS's bounds.
3971         // By default this means the center longitude is wrapped around the dateline so its
3972         // value is between -180 and +180 degrees, and the majority of the bounds
3973         // overlaps the CRS's bounds.
3974         wrapLatLngBounds: function (latlng) {
3975                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3976         },
3977
3978         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3979         // Returns the distance between two geographical coordinates according to
3980         // the map's CRS. By default this measures distance in meters.
3981         distance: function (latlng1, latlng2) {
3982                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3983         },
3984
3985         // @method containerPointToLayerPoint(point: Point): Point
3986         // Given a pixel coordinate relative to the map container, returns the corresponding
3987         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3988         containerPointToLayerPoint: function (point) { // (Point)
3989                 return toPoint(point).subtract(this._getMapPanePos());
3990         },
3991
3992         // @method layerPointToContainerPoint(point: Point): Point
3993         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3994         // returns the corresponding pixel coordinate relative to the map container.
3995         layerPointToContainerPoint: function (point) { // (Point)
3996                 return toPoint(point).add(this._getMapPanePos());
3997         },
3998
3999         // @method containerPointToLatLng(point: Point): LatLng
4000         // Given a pixel coordinate relative to the map container, returns
4001         // the corresponding geographical coordinate (for the current zoom level).
4002         containerPointToLatLng: function (point) {
4003                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
4004                 return this.layerPointToLatLng(layerPoint);
4005         },
4006
4007         // @method latLngToContainerPoint(latlng: LatLng): Point
4008         // Given a geographical coordinate, returns the corresponding pixel coordinate
4009         // relative to the map container.
4010         latLngToContainerPoint: function (latlng) {
4011                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
4012         },
4013
4014         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
4015         // Given a MouseEvent object, returns the pixel coordinate relative to the
4016         // map container where the event took place.
4017         mouseEventToContainerPoint: function (e) {
4018                 return getMousePosition(e, this._container);
4019         },
4020
4021         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
4022         // Given a MouseEvent object, returns the pixel coordinate relative to
4023         // the [origin pixel](#map-getpixelorigin) where the event took place.
4024         mouseEventToLayerPoint: function (e) {
4025                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
4026         },
4027
4028         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
4029         // Given a MouseEvent object, returns geographical coordinate where the
4030         // event took place.
4031         mouseEventToLatLng: function (e) { // (MouseEvent)
4032                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
4033         },
4034
4035
4036         // map initialization methods
4037
4038         _initContainer: function (id) {
4039                 var container = this._container = get(id);
4040
4041                 if (!container) {
4042                         throw new Error('Map container not found.');
4043                 } else if (container._leaflet_id) {
4044                         throw new Error('Map container is already initialized.');
4045                 }
4046
4047                 on(container, 'scroll', this._onScroll, this);
4048                 this._containerId = stamp(container);
4049         },
4050
4051         _initLayout: function () {
4052                 var container = this._container;
4053
4054                 this._fadeAnimated = this.options.fadeAnimation && any3d;
4055
4056                 addClass(container, 'leaflet-container' +
4057                         (touch ? ' leaflet-touch' : '') +
4058                         (retina ? ' leaflet-retina' : '') +
4059                         (ielt9 ? ' leaflet-oldie' : '') +
4060                         (safari ? ' leaflet-safari' : '') +
4061                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
4062
4063                 var position = getStyle(container, 'position');
4064
4065                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
4066                         container.style.position = 'relative';
4067                 }
4068
4069                 this._initPanes();
4070
4071                 if (this._initControlPos) {
4072                         this._initControlPos();
4073                 }
4074         },
4075
4076         _initPanes: function () {
4077                 var panes = this._panes = {};
4078                 this._paneRenderers = {};
4079
4080                 // @section
4081                 //
4082                 // Panes are DOM elements used to control the ordering of layers on the map. You
4083                 // can access panes with [`map.getPane`](#map-getpane) or
4084                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
4085                 // [`map.createPane`](#map-createpane) method.
4086                 //
4087                 // Every map has the following default panes that differ only in zIndex.
4088                 //
4089                 // @pane mapPane: HTMLElement = 'auto'
4090                 // Pane that contains all other map panes
4091
4092                 this._mapPane = this.createPane('mapPane', this._container);
4093                 setPosition(this._mapPane, new Point(0, 0));
4094
4095                 // @pane tilePane: HTMLElement = 200
4096                 // Pane for `GridLayer`s and `TileLayer`s
4097                 this.createPane('tilePane');
4098                 // @pane overlayPane: HTMLElement = 400
4099                 // Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
4100                 this.createPane('shadowPane');
4101                 // @pane shadowPane: HTMLElement = 500
4102                 // Pane for overlay shadows (e.g. `Marker` shadows)
4103                 this.createPane('overlayPane');
4104                 // @pane markerPane: HTMLElement = 600
4105                 // Pane for `Icon`s of `Marker`s
4106                 this.createPane('markerPane');
4107                 // @pane tooltipPane: HTMLElement = 650
4108                 // Pane for `Tooltip`s.
4109                 this.createPane('tooltipPane');
4110                 // @pane popupPane: HTMLElement = 700
4111                 // Pane for `Popup`s.
4112                 this.createPane('popupPane');
4113
4114                 if (!this.options.markerZoomAnimation) {
4115                         addClass(panes.markerPane, 'leaflet-zoom-hide');
4116                         addClass(panes.shadowPane, 'leaflet-zoom-hide');
4117                 }
4118         },
4119
4120
4121         // private methods that modify map state
4122
4123         // @section Map state change events
4124         _resetView: function (center, zoom) {
4125                 setPosition(this._mapPane, new Point(0, 0));
4126
4127                 var loading = !this._loaded;
4128                 this._loaded = true;
4129                 zoom = this._limitZoom(zoom);
4130
4131                 this.fire('viewprereset');
4132
4133                 var zoomChanged = this._zoom !== zoom;
4134                 this
4135                         ._moveStart(zoomChanged, false)
4136                         ._move(center, zoom)
4137                         ._moveEnd(zoomChanged);
4138
4139                 // @event viewreset: Event
4140                 // Fired when the map needs to redraw its content (this usually happens
4141                 // on map zoom or load). Very useful for creating custom overlays.
4142                 this.fire('viewreset');
4143
4144                 // @event load: Event
4145                 // Fired when the map is initialized (when its center and zoom are set
4146                 // for the first time).
4147                 if (loading) {
4148                         this.fire('load');
4149                 }
4150         },
4151
4152         _moveStart: function (zoomChanged, noMoveStart) {
4153                 // @event zoomstart: Event
4154                 // Fired when the map zoom is about to change (e.g. before zoom animation).
4155                 // @event movestart: Event
4156                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4157                 if (zoomChanged) {
4158                         this.fire('zoomstart');
4159                 }
4160                 if (!noMoveStart) {
4161                         this.fire('movestart');
4162                 }
4163                 return this;
4164         },
4165
4166         _move: function (center, zoom, data) {
4167                 if (zoom === undefined) {
4168                         zoom = this._zoom;
4169                 }
4170                 var zoomChanged = this._zoom !== zoom;
4171
4172                 this._zoom = zoom;
4173                 this._lastCenter = center;
4174                 this._pixelOrigin = this._getNewPixelOrigin(center);
4175
4176                 // @event zoom: Event
4177                 // Fired repeatedly during any change in zoom level, including zoom
4178                 // and fly animations.
4179                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
4180                         this.fire('zoom', data);
4181                 }
4182
4183                 // @event move: Event
4184                 // Fired repeatedly during any movement of the map, including pan and
4185                 // fly animations.
4186                 return this.fire('move', data);
4187         },
4188
4189         _moveEnd: function (zoomChanged) {
4190                 // @event zoomend: Event
4191                 // Fired when the map has changed, after any animations.
4192                 if (zoomChanged) {
4193                         this.fire('zoomend');
4194                 }
4195
4196                 // @event moveend: Event
4197                 // Fired when the center of the map stops changing (e.g. user stopped
4198                 // dragging the map).
4199                 return this.fire('moveend');
4200         },
4201
4202         _stop: function () {
4203                 cancelAnimFrame(this._flyToFrame);
4204                 if (this._panAnim) {
4205                         this._panAnim.stop();
4206                 }
4207                 return this;
4208         },
4209
4210         _rawPanBy: function (offset) {
4211                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4212         },
4213
4214         _getZoomSpan: function () {
4215                 return this.getMaxZoom() - this.getMinZoom();
4216         },
4217
4218         _panInsideMaxBounds: function () {
4219                 if (!this._enforcingBounds) {
4220                         this.panInsideBounds(this.options.maxBounds);
4221                 }
4222         },
4223
4224         _checkIfLoaded: function () {
4225                 if (!this._loaded) {
4226                         throw new Error('Set map center and zoom first.');
4227                 }
4228         },
4229
4230         // DOM event handling
4231
4232         // @section Interaction events
4233         _initEvents: function (remove$$1) {
4234                 this._targets = {};
4235                 this._targets[stamp(this._container)] = this;
4236
4237                 var onOff = remove$$1 ? off : on;
4238
4239                 // @event click: MouseEvent
4240                 // Fired when the user clicks (or taps) the map.
4241                 // @event dblclick: MouseEvent
4242                 // Fired when the user double-clicks (or double-taps) the map.
4243                 // @event mousedown: MouseEvent
4244                 // Fired when the user pushes the mouse button on the map.
4245                 // @event mouseup: MouseEvent
4246                 // Fired when the user releases the mouse button on the map.
4247                 // @event mouseover: MouseEvent
4248                 // Fired when the mouse enters the map.
4249                 // @event mouseout: MouseEvent
4250                 // Fired when the mouse leaves the map.
4251                 // @event mousemove: MouseEvent
4252                 // Fired while the mouse moves over the map.
4253                 // @event contextmenu: MouseEvent
4254                 // Fired when the user pushes the right mouse button on the map, prevents
4255                 // default browser context menu from showing if there are listeners on
4256                 // this event. Also fired on mobile when the user holds a single touch
4257                 // for a second (also called long press).
4258                 // @event keypress: KeyboardEvent
4259                 // Fired when the user presses a key from the keyboard while the map is focused.
4260                 onOff(this._container, 'click dblclick mousedown mouseup ' +
4261                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4262
4263                 if (this.options.trackResize) {
4264                         onOff(window, 'resize', this._onResize, this);
4265                 }
4266
4267                 if (any3d && this.options.transform3DLimit) {
4268                         (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4269                 }
4270         },
4271
4272         _onResize: function () {
4273                 cancelAnimFrame(this._resizeRequest);
4274                 this._resizeRequest = requestAnimFrame(
4275                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
4276         },
4277
4278         _onScroll: function () {
4279                 this._container.scrollTop  = 0;
4280                 this._container.scrollLeft = 0;
4281         },
4282
4283         _onMoveEnd: function () {
4284                 var pos = this._getMapPanePos();
4285                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4286                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4287                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4288                         this._resetView(this.getCenter(), this.getZoom());
4289                 }
4290         },
4291
4292         _findEventTargets: function (e, type) {
4293                 var targets = [],
4294                     target,
4295                     isHover = type === 'mouseout' || type === 'mouseover',
4296                     src = e.target || e.srcElement,
4297                     dragging = false;
4298
4299                 while (src) {
4300                         target = this._targets[stamp(src)];
4301                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4302                                 // Prevent firing click after you just dragged an object.
4303                                 dragging = true;
4304                                 break;
4305                         }
4306                         if (target && target.listens(type, true)) {
4307                                 if (isHover && !isExternalTarget(src, e)) { break; }
4308                                 targets.push(target);
4309                                 if (isHover) { break; }
4310                         }
4311                         if (src === this._container) { break; }
4312                         src = src.parentNode;
4313                 }
4314                 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4315                         targets = [this];
4316                 }
4317                 return targets;
4318         },
4319
4320         _handleDOMEvent: function (e) {
4321                 if (!this._loaded || skipped(e)) { return; }
4322
4323                 var type = e.type;
4324
4325                 if (type === 'mousedown' || type === 'keypress') {
4326                         // prevents outline when clicking on keyboard-focusable element
4327                         preventOutline(e.target || e.srcElement);
4328                 }
4329
4330                 this._fireDOMEvent(e, type);
4331         },
4332
4333         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4334
4335         _fireDOMEvent: function (e, type, targets) {
4336
4337                 if (e.type === 'click') {
4338                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4339                         // @event preclick: MouseEvent
4340                         // Fired before mouse click on the map (sometimes useful when you
4341                         // want something to happen on click before any existing click
4342                         // handlers start running).
4343                         var synth = extend({}, e);
4344                         synth.type = 'preclick';
4345                         this._fireDOMEvent(synth, synth.type, targets);
4346                 }
4347
4348                 if (e._stopped) { return; }
4349
4350                 // Find the layer the event is propagating from and its parents.
4351                 targets = (targets || []).concat(this._findEventTargets(e, type));
4352
4353                 if (!targets.length) { return; }
4354
4355                 var target = targets[0];
4356                 if (type === 'contextmenu' && target.listens(type, true)) {
4357                         preventDefault(e);
4358                 }
4359
4360                 var data = {
4361                         originalEvent: e
4362                 };
4363
4364                 if (e.type !== 'keypress') {
4365                         var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
4366                         data.containerPoint = isMarker ?
4367                                 this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4368                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4369                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4370                 }
4371
4372                 for (var i = 0; i < targets.length; i++) {
4373                         targets[i].fire(type, data, true);
4374                         if (data.originalEvent._stopped ||
4375                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4376                 }
4377         },
4378
4379         _draggableMoved: function (obj) {
4380                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4381                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4382         },
4383
4384         _clearHandlers: function () {
4385                 for (var i = 0, len = this._handlers.length; i < len; i++) {
4386                         this._handlers[i].disable();
4387                 }
4388         },
4389
4390         // @section Other Methods
4391
4392         // @method whenReady(fn: Function, context?: Object): this
4393         // Runs the given function `fn` when the map gets initialized with
4394         // a view (center and zoom) and at least one layer, or immediately
4395         // if it's already initialized, optionally passing a function context.
4396         whenReady: function (callback, context) {
4397                 if (this._loaded) {
4398                         callback.call(context || this, {target: this});
4399                 } else {
4400                         this.on('load', callback, context);
4401                 }
4402                 return this;
4403         },
4404
4405
4406         // private methods for getting map state
4407
4408         _getMapPanePos: function () {
4409                 return getPosition(this._mapPane) || new Point(0, 0);
4410         },
4411
4412         _moved: function () {
4413                 var pos = this._getMapPanePos();
4414                 return pos && !pos.equals([0, 0]);
4415         },
4416
4417         _getTopLeftPoint: function (center, zoom) {
4418                 var pixelOrigin = center && zoom !== undefined ?
4419                         this._getNewPixelOrigin(center, zoom) :
4420                         this.getPixelOrigin();
4421                 return pixelOrigin.subtract(this._getMapPanePos());
4422         },
4423
4424         _getNewPixelOrigin: function (center, zoom) {
4425                 var viewHalf = this.getSize()._divideBy(2);
4426                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4427         },
4428
4429         _latLngToNewLayerPoint: function (latlng, zoom, center) {
4430                 var topLeft = this._getNewPixelOrigin(center, zoom);
4431                 return this.project(latlng, zoom)._subtract(topLeft);
4432         },
4433
4434         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4435                 var topLeft = this._getNewPixelOrigin(center, zoom);
4436                 return toBounds([
4437                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4438                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4439                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4440                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4441                 ]);
4442         },
4443
4444         // layer point of the current center
4445         _getCenterLayerPoint: function () {
4446                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4447         },
4448
4449         // offset of the specified place to the current center in pixels
4450         _getCenterOffset: function (latlng) {
4451                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4452         },
4453
4454         // adjust center for view to get inside bounds
4455         _limitCenter: function (center, zoom, bounds) {
4456
4457                 if (!bounds) { return center; }
4458
4459                 var centerPoint = this.project(center, zoom),
4460                     viewHalf = this.getSize().divideBy(2),
4461                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4462                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4463
4464                 // If offset is less than a pixel, ignore.
4465                 // This prevents unstable projections from getting into
4466                 // an infinite loop of tiny offsets.
4467                 if (offset.round().equals([0, 0])) {
4468                         return center;
4469                 }
4470
4471                 return this.unproject(centerPoint.add(offset), zoom);
4472         },
4473
4474         // adjust offset for view to get inside bounds
4475         _limitOffset: function (offset, bounds) {
4476                 if (!bounds) { return offset; }
4477
4478                 var viewBounds = this.getPixelBounds(),
4479                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4480
4481                 return offset.add(this._getBoundsOffset(newBounds, bounds));
4482         },
4483
4484         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4485         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4486                 var projectedMaxBounds = toBounds(
4487                         this.project(maxBounds.getNorthEast(), zoom),
4488                         this.project(maxBounds.getSouthWest(), zoom)
4489                     ),
4490                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4491                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4492
4493                     dx = this._rebound(minOffset.x, -maxOffset.x),
4494                     dy = this._rebound(minOffset.y, -maxOffset.y);
4495
4496                 return new Point(dx, dy);
4497         },
4498
4499         _rebound: function (left, right) {
4500                 return left + right > 0 ?
4501                         Math.round(left - right) / 2 :
4502                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4503         },
4504
4505         _limitZoom: function (zoom) {
4506                 var min = this.getMinZoom(),
4507                     max = this.getMaxZoom(),
4508                     snap = any3d ? this.options.zoomSnap : 1;
4509                 if (snap) {
4510                         zoom = Math.round(zoom / snap) * snap;
4511                 }
4512                 return Math.max(min, Math.min(max, zoom));
4513         },
4514
4515         _onPanTransitionStep: function () {
4516                 this.fire('move');
4517         },
4518
4519         _onPanTransitionEnd: function () {
4520                 removeClass(this._mapPane, 'leaflet-pan-anim');
4521                 this.fire('moveend');
4522         },
4523
4524         _tryAnimatedPan: function (center, options) {
4525                 // difference between the new and current centers in pixels
4526                 var offset = this._getCenterOffset(center)._trunc();
4527
4528                 // don't animate too far unless animate: true specified in options
4529                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4530
4531                 this.panBy(offset, options);
4532
4533                 return true;
4534         },
4535
4536         _createAnimProxy: function () {
4537
4538                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4539                 this._panes.mapPane.appendChild(proxy);
4540
4541                 this.on('zoomanim', function (e) {
4542                         var prop = TRANSFORM,
4543                             transform = this._proxy.style[prop];
4544
4545                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4546
4547                         // workaround for case when transform is the same and so transitionend event is not fired
4548                         if (transform === this._proxy.style[prop] && this._animatingZoom) {
4549                                 this._onZoomTransitionEnd();
4550                         }
4551                 }, this);
4552
4553                 this.on('load moveend', function () {
4554                         var c = this.getCenter(),
4555                             z = this.getZoom();
4556                         setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4557                 }, this);
4558
4559                 this._on('unload', this._destroyAnimProxy, this);
4560         },
4561
4562         _destroyAnimProxy: function () {
4563                 remove(this._proxy);
4564                 delete this._proxy;
4565         },
4566
4567         _catchTransitionEnd: function (e) {
4568                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4569                         this._onZoomTransitionEnd();
4570                 }
4571         },
4572
4573         _nothingToAnimate: function () {
4574                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4575         },
4576
4577         _tryAnimatedZoom: function (center, zoom, options) {
4578
4579                 if (this._animatingZoom) { return true; }
4580
4581                 options = options || {};
4582
4583                 // don't animate if disabled, not supported or zoom difference is too large
4584                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4585                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4586
4587                 // offset is the pixel coords of the zoom origin relative to the current center
4588                 var scale = this.getZoomScale(zoom),
4589                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4590
4591                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4592                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4593
4594                 requestAnimFrame(function () {
4595                         this
4596                             ._moveStart(true, false)
4597                             ._animateZoom(center, zoom, true);
4598                 }, this);
4599
4600                 return true;
4601         },
4602
4603         _animateZoom: function (center, zoom, startAnim, noUpdate) {
4604                 if (!this._mapPane) { return; }
4605
4606                 if (startAnim) {
4607                         this._animatingZoom = true;
4608
4609                         // remember what center/zoom to set after animation
4610                         this._animateToCenter = center;
4611                         this._animateToZoom = zoom;
4612
4613                         addClass(this._mapPane, 'leaflet-zoom-anim');
4614                 }
4615
4616                 // @event zoomanim: ZoomAnimEvent
4617                 // Fired on every frame of a zoom animation
4618                 this.fire('zoomanim', {
4619                         center: center,
4620                         zoom: zoom,
4621                         noUpdate: noUpdate
4622                 });
4623
4624                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4625                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4626         },
4627
4628         _onZoomTransitionEnd: function () {
4629                 if (!this._animatingZoom) { return; }
4630
4631                 if (this._mapPane) {
4632                         removeClass(this._mapPane, 'leaflet-zoom-anim');
4633                 }
4634
4635                 this._animatingZoom = false;
4636
4637                 this._move(this._animateToCenter, this._animateToZoom);
4638
4639                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4640                 requestAnimFrame(function () {
4641                         this._moveEnd(true);
4642                 }, this);
4643         }
4644 });
4645
4646 // @section
4647
4648 // @factory L.map(id: String, options?: Map options)
4649 // Instantiates a map object given the DOM ID of a `<div>` element
4650 // and optionally an object literal with `Map options`.
4651 //
4652 // @alternative
4653 // @factory L.map(el: HTMLElement, options?: Map options)
4654 // Instantiates a map object given an instance of a `<div>` HTML element
4655 // and optionally an object literal with `Map options`.
4656 function createMap(id, options) {
4657         return new Map(id, options);
4658 }
4659
4660 /*
4661  * @class Control
4662  * @aka L.Control
4663  * @inherits Class
4664  *
4665  * L.Control is a base class for implementing map controls. Handles positioning.
4666  * All other controls extend from this class.
4667  */
4668
4669 var Control = Class.extend({
4670         // @section
4671         // @aka Control options
4672         options: {
4673                 // @option position: String = 'topright'
4674                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4675                 // `'topright'`, `'bottomleft'` or `'bottomright'`
4676                 position: 'topright'
4677         },
4678
4679         initialize: function (options) {
4680                 setOptions(this, options);
4681         },
4682
4683         /* @section
4684          * Classes extending L.Control will inherit the following methods:
4685          *
4686          * @method getPosition: string
4687          * Returns the position of the control.
4688          */
4689         getPosition: function () {
4690                 return this.options.position;
4691         },
4692
4693         // @method setPosition(position: string): this
4694         // Sets the position of the control.
4695         setPosition: function (position) {
4696                 var map = this._map;
4697
4698                 if (map) {
4699                         map.removeControl(this);
4700                 }
4701
4702                 this.options.position = position;
4703
4704                 if (map) {
4705                         map.addControl(this);
4706                 }
4707
4708                 return this;
4709         },
4710
4711         // @method getContainer: HTMLElement
4712         // Returns the HTMLElement that contains the control.
4713         getContainer: function () {
4714                 return this._container;
4715         },
4716
4717         // @method addTo(map: Map): this
4718         // Adds the control to the given map.
4719         addTo: function (map) {
4720                 this.remove();
4721                 this._map = map;
4722
4723                 var container = this._container = this.onAdd(map),
4724                     pos = this.getPosition(),
4725                     corner = map._controlCorners[pos];
4726
4727                 addClass(container, 'leaflet-control');
4728
4729                 if (pos.indexOf('bottom') !== -1) {
4730                         corner.insertBefore(container, corner.firstChild);
4731                 } else {
4732                         corner.appendChild(container);
4733                 }
4734
4735                 return this;
4736         },
4737
4738         // @method remove: this
4739         // Removes the control from the map it is currently active on.
4740         remove: function () {
4741                 if (!this._map) {
4742                         return this;
4743                 }
4744
4745                 remove(this._container);
4746
4747                 if (this.onRemove) {
4748                         this.onRemove(this._map);
4749                 }
4750
4751                 this._map = null;
4752
4753                 return this;
4754         },
4755
4756         _refocusOnMap: function (e) {
4757                 // if map exists and event is not a keyboard event
4758                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4759                         this._map.getContainer().focus();
4760                 }
4761         }
4762 });
4763
4764 var control = function (options) {
4765         return new Control(options);
4766 };
4767
4768 /* @section Extension methods
4769  * @uninheritable
4770  *
4771  * Every control should extend from `L.Control` and (re-)implement the following methods.
4772  *
4773  * @method onAdd(map: Map): HTMLElement
4774  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4775  *
4776  * @method onRemove(map: Map)
4777  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4778  */
4779
4780 /* @namespace Map
4781  * @section Methods for Layers and Controls
4782  */
4783 Map.include({
4784         // @method addControl(control: Control): this
4785         // Adds the given control to the map
4786         addControl: function (control) {
4787                 control.addTo(this);
4788                 return this;
4789         },
4790
4791         // @method removeControl(control: Control): this
4792         // Removes the given control from the map
4793         removeControl: function (control) {
4794                 control.remove();
4795                 return this;
4796         },
4797
4798         _initControlPos: function () {
4799                 var corners = this._controlCorners = {},
4800                     l = 'leaflet-',
4801                     container = this._controlContainer =
4802                             create$1('div', l + 'control-container', this._container);
4803
4804                 function createCorner(vSide, hSide) {
4805                         var className = l + vSide + ' ' + l + hSide;
4806
4807                         corners[vSide + hSide] = create$1('div', className, container);
4808                 }
4809
4810                 createCorner('top', 'left');
4811                 createCorner('top', 'right');
4812                 createCorner('bottom', 'left');
4813                 createCorner('bottom', 'right');
4814         },
4815
4816         _clearControlPos: function () {
4817                 for (var i in this._controlCorners) {
4818                         remove(this._controlCorners[i]);
4819                 }
4820                 remove(this._controlContainer);
4821                 delete this._controlCorners;
4822                 delete this._controlContainer;
4823         }
4824 });
4825
4826 /*
4827  * @class Control.Layers
4828  * @aka L.Control.Layers
4829  * @inherits Control
4830  *
4831  * 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`.
4832  *
4833  * @example
4834  *
4835  * ```js
4836  * var baseLayers = {
4837  *      "Mapbox": mapbox,
4838  *      "OpenStreetMap": osm
4839  * };
4840  *
4841  * var overlays = {
4842  *      "Marker": marker,
4843  *      "Roads": roadsLayer
4844  * };
4845  *
4846  * L.control.layers(baseLayers, overlays).addTo(map);
4847  * ```
4848  *
4849  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4850  *
4851  * ```js
4852  * {
4853  *     "<someName1>": layer1,
4854  *     "<someName2>": layer2
4855  * }
4856  * ```
4857  *
4858  * The layer names can contain HTML, which allows you to add additional styling to the items:
4859  *
4860  * ```js
4861  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4862  * ```
4863  */
4864
4865 var Layers = Control.extend({
4866         // @section
4867         // @aka Control.Layers options
4868         options: {
4869                 // @option collapsed: Boolean = true
4870                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4871                 collapsed: true,
4872                 position: 'topright',
4873
4874                 // @option autoZIndex: Boolean = true
4875                 // 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.
4876                 autoZIndex: true,
4877
4878                 // @option hideSingleBase: Boolean = false
4879                 // If `true`, the base layers in the control will be hidden when there is only one.
4880                 hideSingleBase: false,
4881
4882                 // @option sortLayers: Boolean = false
4883                 // Whether to sort the layers. When `false`, layers will keep the order
4884                 // in which they were added to the control.
4885                 sortLayers: false,
4886
4887                 // @option sortFunction: Function = *
4888                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4889                 // that will be used for sorting the layers, when `sortLayers` is `true`.
4890                 // The function receives both the `L.Layer` instances and their names, as in
4891                 // `sortFunction(layerA, layerB, nameA, nameB)`.
4892                 // By default, it sorts layers alphabetically by their name.
4893                 sortFunction: function (layerA, layerB, nameA, nameB) {
4894                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4895                 }
4896         },
4897
4898         initialize: function (baseLayers, overlays, options) {
4899                 setOptions(this, options);
4900
4901                 this._layerControlInputs = [];
4902                 this._layers = [];
4903                 this._lastZIndex = 0;
4904                 this._handlingClick = false;
4905
4906                 for (var i in baseLayers) {
4907                         this._addLayer(baseLayers[i], i);
4908                 }
4909
4910                 for (i in overlays) {
4911                         this._addLayer(overlays[i], i, true);
4912                 }
4913         },
4914
4915         onAdd: function (map) {
4916                 this._initLayout();
4917                 this._update();
4918
4919                 this._map = map;
4920                 map.on('zoomend', this._checkDisabledLayers, this);
4921
4922                 for (var i = 0; i < this._layers.length; i++) {
4923                         this._layers[i].layer.on('add remove', this._onLayerChange, this);
4924                 }
4925
4926                 return this._container;
4927         },
4928
4929         addTo: function (map) {
4930                 Control.prototype.addTo.call(this, map);
4931                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4932                 return this._expandIfNotCollapsed();
4933         },
4934
4935         onRemove: function () {
4936                 this._map.off('zoomend', this._checkDisabledLayers, this);
4937
4938                 for (var i = 0; i < this._layers.length; i++) {
4939                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
4940                 }
4941         },
4942
4943         // @method addBaseLayer(layer: Layer, name: String): this
4944         // Adds a base layer (radio button entry) with the given name to the control.
4945         addBaseLayer: function (layer, name) {
4946                 this._addLayer(layer, name);
4947                 return (this._map) ? this._update() : this;
4948         },
4949
4950         // @method addOverlay(layer: Layer, name: String): this
4951         // Adds an overlay (checkbox entry) with the given name to the control.
4952         addOverlay: function (layer, name) {
4953                 this._addLayer(layer, name, true);
4954                 return (this._map) ? this._update() : this;
4955         },
4956
4957         // @method removeLayer(layer: Layer): this
4958         // Remove the given layer from the control.
4959         removeLayer: function (layer) {
4960                 layer.off('add remove', this._onLayerChange, this);
4961
4962                 var obj = this._getLayer(stamp(layer));
4963                 if (obj) {
4964                         this._layers.splice(this._layers.indexOf(obj), 1);
4965                 }
4966                 return (this._map) ? this._update() : this;
4967         },
4968
4969         // @method expand(): this
4970         // Expand the control container if collapsed.
4971         expand: function () {
4972                 addClass(this._container, 'leaflet-control-layers-expanded');
4973                 this._form.style.height = null;
4974                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
4975                 if (acceptableHeight < this._form.clientHeight) {
4976                         addClass(this._form, 'leaflet-control-layers-scrollbar');
4977                         this._form.style.height = acceptableHeight + 'px';
4978                 } else {
4979                         removeClass(this._form, 'leaflet-control-layers-scrollbar');
4980                 }
4981                 this._checkDisabledLayers();
4982                 return this;
4983         },
4984
4985         // @method collapse(): this
4986         // Collapse the control container if expanded.
4987         collapse: function () {
4988                 removeClass(this._container, 'leaflet-control-layers-expanded');
4989                 return this;
4990         },
4991
4992         _initLayout: function () {
4993                 var className = 'leaflet-control-layers',
4994                     container = this._container = create$1('div', className),
4995                     collapsed = this.options.collapsed;
4996
4997                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
4998                 container.setAttribute('aria-haspopup', true);
4999
5000                 disableClickPropagation(container);
5001                 disableScrollPropagation(container);
5002
5003                 var form = this._form = create$1('form', className + '-list');
5004
5005                 if (collapsed) {
5006                         this._map.on('click', this.collapse, this);
5007
5008                         if (!android) {
5009                                 on(container, {
5010                                         mouseenter: this.expand,
5011                                         mouseleave: this.collapse
5012                                 }, this);
5013                         }
5014                 }
5015
5016                 var link = this._layersLink = create$1('a', className + '-toggle', container);
5017                 link.href = '#';
5018                 link.title = 'Layers';
5019
5020                 if (touch) {
5021                         on(link, 'click', stop);
5022                         on(link, 'click', this.expand, this);
5023                 } else {
5024                         on(link, 'focus', this.expand, this);
5025                 }
5026
5027                 if (!collapsed) {
5028                         this.expand();
5029                 }
5030
5031                 this._baseLayersList = create$1('div', className + '-base', form);
5032                 this._separator = create$1('div', className + '-separator', form);
5033                 this._overlaysList = create$1('div', className + '-overlays', form);
5034
5035                 container.appendChild(form);
5036         },
5037
5038         _getLayer: function (id) {
5039                 for (var i = 0; i < this._layers.length; i++) {
5040
5041                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {
5042                                 return this._layers[i];
5043                         }
5044                 }
5045         },
5046
5047         _addLayer: function (layer, name, overlay) {
5048                 if (this._map) {
5049                         layer.on('add remove', this._onLayerChange, this);
5050                 }
5051
5052                 this._layers.push({
5053                         layer: layer,
5054                         name: name,
5055                         overlay: overlay
5056                 });
5057
5058                 if (this.options.sortLayers) {
5059                         this._layers.sort(bind(function (a, b) {
5060                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
5061                         }, this));
5062                 }
5063
5064                 if (this.options.autoZIndex && layer.setZIndex) {
5065                         this._lastZIndex++;
5066                         layer.setZIndex(this._lastZIndex);
5067                 }
5068
5069                 this._expandIfNotCollapsed();
5070         },
5071
5072         _update: function () {
5073                 if (!this._container) { return this; }
5074
5075                 empty(this._baseLayersList);
5076                 empty(this._overlaysList);
5077
5078                 this._layerControlInputs = [];
5079                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
5080
5081                 for (i = 0; i < this._layers.length; i++) {
5082                         obj = this._layers[i];
5083                         this._addItem(obj);
5084                         overlaysPresent = overlaysPresent || obj.overlay;
5085                         baseLayersPresent = baseLayersPresent || !obj.overlay;
5086                         baseLayersCount += !obj.overlay ? 1 : 0;
5087                 }
5088
5089                 // Hide base layers section if there's only one layer.
5090                 if (this.options.hideSingleBase) {
5091                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
5092                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
5093                 }
5094
5095                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
5096
5097                 return this;
5098         },
5099
5100         _onLayerChange: function (e) {
5101                 if (!this._handlingClick) {
5102                         this._update();
5103                 }
5104
5105                 var obj = this._getLayer(stamp(e.target));
5106
5107                 // @namespace Map
5108                 // @section Layer events
5109                 // @event baselayerchange: LayersControlEvent
5110                 // Fired when the base layer is changed through the [layer control](#control-layers).
5111                 // @event overlayadd: LayersControlEvent
5112                 // Fired when an overlay is selected through the [layer control](#control-layers).
5113                 // @event overlayremove: LayersControlEvent
5114                 // Fired when an overlay is deselected through the [layer control](#control-layers).
5115                 // @namespace Control.Layers
5116                 var type = obj.overlay ?
5117                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5118                         (e.type === 'add' ? 'baselayerchange' : null);
5119
5120                 if (type) {
5121                         this._map.fire(type, obj);
5122                 }
5123         },
5124
5125         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5126         _createRadioElement: function (name, checked) {
5127
5128                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5129                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5130
5131                 var radioFragment = document.createElement('div');
5132                 radioFragment.innerHTML = radioHtml;
5133
5134                 return radioFragment.firstChild;
5135         },
5136
5137         _addItem: function (obj) {
5138                 var label = document.createElement('label'),
5139                     checked = this._map.hasLayer(obj.layer),
5140                     input;
5141
5142                 if (obj.overlay) {
5143                         input = document.createElement('input');
5144                         input.type = 'checkbox';
5145                         input.className = 'leaflet-control-layers-selector';
5146                         input.defaultChecked = checked;
5147                 } else {
5148                         input = this._createRadioElement('leaflet-base-layers', checked);
5149                 }
5150
5151                 this._layerControlInputs.push(input);
5152                 input.layerId = stamp(obj.layer);
5153
5154                 on(input, 'click', this._onInputClick, this);
5155
5156                 var name = document.createElement('span');
5157                 name.innerHTML = ' ' + obj.name;
5158
5159                 // Helps from preventing layer control flicker when checkboxes are disabled
5160                 // https://github.com/Leaflet/Leaflet/issues/2771
5161                 var holder = document.createElement('div');
5162
5163                 label.appendChild(holder);
5164                 holder.appendChild(input);
5165                 holder.appendChild(name);
5166
5167                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5168                 container.appendChild(label);
5169
5170                 this._checkDisabledLayers();
5171                 return label;
5172         },
5173
5174         _onInputClick: function () {
5175                 var inputs = this._layerControlInputs,
5176                     input, layer;
5177                 var addedLayers = [],
5178                     removedLayers = [];
5179
5180                 this._handlingClick = true;
5181
5182                 for (var i = inputs.length - 1; i >= 0; i--) {
5183                         input = inputs[i];
5184                         layer = this._getLayer(input.layerId).layer;
5185
5186                         if (input.checked) {
5187                                 addedLayers.push(layer);
5188                         } else if (!input.checked) {
5189                                 removedLayers.push(layer);
5190                         }
5191                 }
5192
5193                 // Bugfix issue 2318: Should remove all old layers before readding new ones
5194                 for (i = 0; i < removedLayers.length; i++) {
5195                         if (this._map.hasLayer(removedLayers[i])) {
5196                                 this._map.removeLayer(removedLayers[i]);
5197                         }
5198                 }
5199                 for (i = 0; i < addedLayers.length; i++) {
5200                         if (!this._map.hasLayer(addedLayers[i])) {
5201                                 this._map.addLayer(addedLayers[i]);
5202                         }
5203                 }
5204
5205                 this._handlingClick = false;
5206
5207                 this._refocusOnMap();
5208         },
5209
5210         _checkDisabledLayers: function () {
5211                 var inputs = this._layerControlInputs,
5212                     input,
5213                     layer,
5214                     zoom = this._map.getZoom();
5215
5216                 for (var i = inputs.length - 1; i >= 0; i--) {
5217                         input = inputs[i];
5218                         layer = this._getLayer(input.layerId).layer;
5219                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5220                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5221
5222                 }
5223         },
5224
5225         _expandIfNotCollapsed: function () {
5226                 if (this._map && !this.options.collapsed) {
5227                         this.expand();
5228                 }
5229                 return this;
5230         },
5231
5232         _expand: function () {
5233                 // Backward compatibility, remove me in 1.1.
5234                 return this.expand();
5235         },
5236
5237         _collapse: function () {
5238                 // Backward compatibility, remove me in 1.1.
5239                 return this.collapse();
5240         }
5241
5242 });
5243
5244
5245 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5246 // 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.
5247 var layers = function (baseLayers, overlays, options) {
5248         return new Layers(baseLayers, overlays, options);
5249 };
5250
5251 /*
5252  * @class Control.Zoom
5253  * @aka L.Control.Zoom
5254  * @inherits Control
5255  *
5256  * 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`.
5257  */
5258
5259 var Zoom = Control.extend({
5260         // @section
5261         // @aka Control.Zoom options
5262         options: {
5263                 position: 'topleft',
5264
5265                 // @option zoomInText: String = '+'
5266                 // The text set on the 'zoom in' button.
5267                 zoomInText: '+',
5268
5269                 // @option zoomInTitle: String = 'Zoom in'
5270                 // The title set on the 'zoom in' button.
5271                 zoomInTitle: 'Zoom in',
5272
5273                 // @option zoomOutText: String = '&#x2212;'
5274                 // The text set on the 'zoom out' button.
5275                 zoomOutText: '&#x2212;',
5276
5277                 // @option zoomOutTitle: String = 'Zoom out'
5278                 // The title set on the 'zoom out' button.
5279                 zoomOutTitle: 'Zoom out'
5280         },
5281
5282         onAdd: function (map) {
5283                 var zoomName = 'leaflet-control-zoom',
5284                     container = create$1('div', zoomName + ' leaflet-bar'),
5285                     options = this.options;
5286
5287                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
5288                         zoomName + '-in',  container, this._zoomIn);
5289                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5290                         zoomName + '-out', container, this._zoomOut);
5291
5292                 this._updateDisabled();
5293                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5294
5295                 return container;
5296         },
5297
5298         onRemove: function (map) {
5299                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5300         },
5301
5302         disable: function () {
5303                 this._disabled = true;
5304                 this._updateDisabled();
5305                 return this;
5306         },
5307
5308         enable: function () {
5309                 this._disabled = false;
5310                 this._updateDisabled();
5311                 return this;
5312         },
5313
5314         _zoomIn: function (e) {
5315                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5316                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5317                 }
5318         },
5319
5320         _zoomOut: function (e) {
5321                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5322                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5323                 }
5324         },
5325
5326         _createButton: function (html, title, className, container, fn) {
5327                 var link = create$1('a', className, container);
5328                 link.innerHTML = html;
5329                 link.href = '#';
5330                 link.title = title;
5331
5332                 /*
5333                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5334                  */
5335                 link.setAttribute('role', 'button');
5336                 link.setAttribute('aria-label', title);
5337
5338                 disableClickPropagation(link);
5339                 on(link, 'click', stop);
5340                 on(link, 'click', fn, this);
5341                 on(link, 'click', this._refocusOnMap, this);
5342
5343                 return link;
5344         },
5345
5346         _updateDisabled: function () {
5347                 var map = this._map,
5348                     className = 'leaflet-disabled';
5349
5350                 removeClass(this._zoomInButton, className);
5351                 removeClass(this._zoomOutButton, className);
5352
5353                 if (this._disabled || map._zoom === map.getMinZoom()) {
5354                         addClass(this._zoomOutButton, className);
5355                 }
5356                 if (this._disabled || map._zoom === map.getMaxZoom()) {
5357                         addClass(this._zoomInButton, className);
5358                 }
5359         }
5360 });
5361
5362 // @namespace Map
5363 // @section Control options
5364 // @option zoomControl: Boolean = true
5365 // Whether a [zoom control](#control-zoom) is added to the map by default.
5366 Map.mergeOptions({
5367         zoomControl: true
5368 });
5369
5370 Map.addInitHook(function () {
5371         if (this.options.zoomControl) {
5372                 // @section Controls
5373                 // @property zoomControl: Control.Zoom
5374                 // The default zoom control (only available if the
5375                 // [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
5376                 this.zoomControl = new Zoom();
5377                 this.addControl(this.zoomControl);
5378         }
5379 });
5380
5381 // @namespace Control.Zoom
5382 // @factory L.control.zoom(options: Control.Zoom options)
5383 // Creates a zoom control
5384 var zoom = function (options) {
5385         return new Zoom(options);
5386 };
5387
5388 /*
5389  * @class Control.Scale
5390  * @aka L.Control.Scale
5391  * @inherits Control
5392  *
5393  * 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`.
5394  *
5395  * @example
5396  *
5397  * ```js
5398  * L.control.scale().addTo(map);
5399  * ```
5400  */
5401
5402 var Scale = Control.extend({
5403         // @section
5404         // @aka Control.Scale options
5405         options: {
5406                 position: 'bottomleft',
5407
5408                 // @option maxWidth: Number = 100
5409                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5410                 maxWidth: 100,
5411
5412                 // @option metric: Boolean = True
5413                 // Whether to show the metric scale line (m/km).
5414                 metric: true,
5415
5416                 // @option imperial: Boolean = True
5417                 // Whether to show the imperial scale line (mi/ft).
5418                 imperial: true
5419
5420                 // @option updateWhenIdle: Boolean = false
5421                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5422         },
5423
5424         onAdd: function (map) {
5425                 var className = 'leaflet-control-scale',
5426                     container = create$1('div', className),
5427                     options = this.options;
5428
5429                 this._addScales(options, className + '-line', container);
5430
5431                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5432                 map.whenReady(this._update, this);
5433
5434                 return container;
5435         },
5436
5437         onRemove: function (map) {
5438                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5439         },
5440
5441         _addScales: function (options, className, container) {
5442                 if (options.metric) {
5443                         this._mScale = create$1('div', className, container);
5444                 }
5445                 if (options.imperial) {
5446                         this._iScale = create$1('div', className, container);
5447                 }
5448         },
5449
5450         _update: function () {
5451                 var map = this._map,
5452                     y = map.getSize().y / 2;
5453
5454                 var maxMeters = map.distance(
5455                         map.containerPointToLatLng([0, y]),
5456                         map.containerPointToLatLng([this.options.maxWidth, y]));
5457
5458                 this._updateScales(maxMeters);
5459         },
5460
5461         _updateScales: function (maxMeters) {
5462                 if (this.options.metric && maxMeters) {
5463                         this._updateMetric(maxMeters);
5464                 }
5465                 if (this.options.imperial && maxMeters) {
5466                         this._updateImperial(maxMeters);
5467                 }
5468         },
5469
5470         _updateMetric: function (maxMeters) {
5471                 var meters = this._getRoundNum(maxMeters),
5472                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5473
5474                 this._updateScale(this._mScale, label, meters / maxMeters);
5475         },
5476
5477         _updateImperial: function (maxMeters) {
5478                 var maxFeet = maxMeters * 3.2808399,
5479                     maxMiles, miles, feet;
5480
5481                 if (maxFeet > 5280) {
5482                         maxMiles = maxFeet / 5280;
5483                         miles = this._getRoundNum(maxMiles);
5484                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5485
5486                 } else {
5487                         feet = this._getRoundNum(maxFeet);
5488                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5489                 }
5490         },
5491
5492         _updateScale: function (scale, text, ratio) {
5493                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5494                 scale.innerHTML = text;
5495         },
5496
5497         _getRoundNum: function (num) {
5498                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5499                     d = num / pow10;
5500
5501                 d = d >= 10 ? 10 :
5502                     d >= 5 ? 5 :
5503                     d >= 3 ? 3 :
5504                     d >= 2 ? 2 : 1;
5505
5506                 return pow10 * d;
5507         }
5508 });
5509
5510
5511 // @factory L.control.scale(options?: Control.Scale options)
5512 // Creates an scale control with the given options.
5513 var scale = function (options) {
5514         return new Scale(options);
5515 };
5516
5517 /*
5518  * @class Control.Attribution
5519  * @aka L.Control.Attribution
5520  * @inherits Control
5521  *
5522  * 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.
5523  */
5524
5525 var Attribution = Control.extend({
5526         // @section
5527         // @aka Control.Attribution options
5528         options: {
5529                 position: 'bottomright',
5530
5531                 // @option prefix: String = 'Leaflet'
5532                 // The HTML text shown before the attributions. Pass `false` to disable.
5533                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5534         },
5535
5536         initialize: function (options) {
5537                 setOptions(this, options);
5538
5539                 this._attributions = {};
5540         },
5541
5542         onAdd: function (map) {
5543                 map.attributionControl = this;
5544                 this._container = create$1('div', 'leaflet-control-attribution');
5545                 disableClickPropagation(this._container);
5546
5547                 // TODO ugly, refactor
5548                 for (var i in map._layers) {
5549                         if (map._layers[i].getAttribution) {
5550                                 this.addAttribution(map._layers[i].getAttribution());
5551                         }
5552                 }
5553
5554                 this._update();
5555
5556                 return this._container;
5557         },
5558
5559         // @method setPrefix(prefix: String): this
5560         // Sets the text before the attributions.
5561         setPrefix: function (prefix) {
5562                 this.options.prefix = prefix;
5563                 this._update();
5564                 return this;
5565         },
5566
5567         // @method addAttribution(text: String): this
5568         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5569         addAttribution: function (text) {
5570                 if (!text) { return this; }
5571
5572                 if (!this._attributions[text]) {
5573                         this._attributions[text] = 0;
5574                 }
5575                 this._attributions[text]++;
5576
5577                 this._update();
5578
5579                 return this;
5580         },
5581
5582         // @method removeAttribution(text: String): this
5583         // Removes an attribution text.
5584         removeAttribution: function (text) {
5585                 if (!text) { return this; }
5586
5587                 if (this._attributions[text]) {
5588                         this._attributions[text]--;
5589                         this._update();
5590                 }
5591
5592                 return this;
5593         },
5594
5595         _update: function () {
5596                 if (!this._map) { return; }
5597
5598                 var attribs = [];
5599
5600                 for (var i in this._attributions) {
5601                         if (this._attributions[i]) {
5602                                 attribs.push(i);
5603                         }
5604                 }
5605
5606                 var prefixAndAttribs = [];
5607
5608                 if (this.options.prefix) {
5609                         prefixAndAttribs.push(this.options.prefix);
5610                 }
5611                 if (attribs.length) {
5612                         prefixAndAttribs.push(attribs.join(', '));
5613                 }
5614
5615                 this._container.innerHTML = prefixAndAttribs.join(' | ');
5616         }
5617 });
5618
5619 // @namespace Map
5620 // @section Control options
5621 // @option attributionControl: Boolean = true
5622 // Whether a [attribution control](#control-attribution) is added to the map by default.
5623 Map.mergeOptions({
5624         attributionControl: true
5625 });
5626
5627 Map.addInitHook(function () {
5628         if (this.options.attributionControl) {
5629                 new Attribution().addTo(this);
5630         }
5631 });
5632
5633 // @namespace Control.Attribution
5634 // @factory L.control.attribution(options: Control.Attribution options)
5635 // Creates an attribution control.
5636 var attribution = function (options) {
5637         return new Attribution(options);
5638 };
5639
5640 Control.Layers = Layers;
5641 Control.Zoom = Zoom;
5642 Control.Scale = Scale;
5643 Control.Attribution = Attribution;
5644
5645 control.layers = layers;
5646 control.zoom = zoom;
5647 control.scale = scale;
5648 control.attribution = attribution;
5649
5650 /*
5651         L.Handler is a base class for handler classes that are used internally to inject
5652         interaction features like dragging to classes like Map and Marker.
5653 */
5654
5655 // @class Handler
5656 // @aka L.Handler
5657 // Abstract class for map interaction handlers
5658
5659 var Handler = Class.extend({
5660         initialize: function (map) {
5661                 this._map = map;
5662         },
5663
5664         // @method enable(): this
5665         // Enables the handler
5666         enable: function () {
5667                 if (this._enabled) { return this; }
5668
5669                 this._enabled = true;
5670                 this.addHooks();
5671                 return this;
5672         },
5673
5674         // @method disable(): this
5675         // Disables the handler
5676         disable: function () {
5677                 if (!this._enabled) { return this; }
5678
5679                 this._enabled = false;
5680                 this.removeHooks();
5681                 return this;
5682         },
5683
5684         // @method enabled(): Boolean
5685         // Returns `true` if the handler is enabled
5686         enabled: function () {
5687                 return !!this._enabled;
5688         }
5689
5690         // @section Extension methods
5691         // Classes inheriting from `Handler` must implement the two following methods:
5692         // @method addHooks()
5693         // Called when the handler is enabled, should add event hooks.
5694         // @method removeHooks()
5695         // Called when the handler is disabled, should remove the event hooks added previously.
5696 });
5697
5698 // @section There is static function which can be called without instantiating L.Handler:
5699 // @function addTo(map: Map, name: String): this
5700 // Adds a new Handler to the given map with the given name.
5701 Handler.addTo = function (map, name) {
5702         map.addHandler(name, this);
5703         return this;
5704 };
5705
5706 var Mixin = {Events: Events};
5707
5708 /*
5709  * @class Draggable
5710  * @aka L.Draggable
5711  * @inherits Evented
5712  *
5713  * A class for making DOM elements draggable (including touch support).
5714  * Used internally for map and marker dragging. Only works for elements
5715  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5716  *
5717  * @example
5718  * ```js
5719  * var draggable = new L.Draggable(elementToDrag);
5720  * draggable.enable();
5721  * ```
5722  */
5723
5724 var START = touch ? 'touchstart mousedown' : 'mousedown';
5725 var END = {
5726         mousedown: 'mouseup',
5727         touchstart: 'touchend',
5728         pointerdown: 'touchend',
5729         MSPointerDown: 'touchend'
5730 };
5731 var MOVE = {
5732         mousedown: 'mousemove',
5733         touchstart: 'touchmove',
5734         pointerdown: 'touchmove',
5735         MSPointerDown: 'touchmove'
5736 };
5737
5738
5739 var Draggable = Evented.extend({
5740
5741         options: {
5742                 // @section
5743                 // @aka Draggable options
5744                 // @option clickTolerance: Number = 3
5745                 // The max number of pixels a user can shift the mouse pointer during a click
5746                 // for it to be considered a valid click (as opposed to a mouse drag).
5747                 clickTolerance: 3
5748         },
5749
5750         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5751         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5752         initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5753                 setOptions(this, options);
5754
5755                 this._element = element;
5756                 this._dragStartTarget = dragStartTarget || element;
5757                 this._preventOutline = preventOutline$$1;
5758         },
5759
5760         // @method enable()
5761         // Enables the dragging ability
5762         enable: function () {
5763                 if (this._enabled) { return; }
5764
5765                 on(this._dragStartTarget, START, this._onDown, this);
5766
5767                 this._enabled = true;
5768         },
5769
5770         // @method disable()
5771         // Disables the dragging ability
5772         disable: function () {
5773                 if (!this._enabled) { return; }
5774
5775                 // If we're currently dragging this draggable,
5776                 // disabling it counts as first ending the drag.
5777                 if (Draggable._dragging === this) {
5778                         this.finishDrag();
5779                 }
5780
5781                 off(this._dragStartTarget, START, this._onDown, this);
5782
5783                 this._enabled = false;
5784                 this._moved = false;
5785         },
5786
5787         _onDown: function (e) {
5788                 // Ignore simulated events, since we handle both touch and
5789                 // mouse explicitly; otherwise we risk getting duplicates of
5790                 // touch events, see #4315.
5791                 // Also ignore the event if disabled; this happens in IE11
5792                 // under some circumstances, see #3666.
5793                 if (e._simulated || !this._enabled) { return; }
5794
5795                 this._moved = false;
5796
5797                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5798
5799                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5800                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.
5801
5802                 if (this._preventOutline) {
5803                         preventOutline(this._element);
5804                 }
5805
5806                 disableImageDrag();
5807                 disableTextSelection();
5808
5809                 if (this._moving) { return; }
5810
5811                 // @event down: Event
5812                 // Fired when a drag is about to start.
5813                 this.fire('down');
5814
5815                 var first = e.touches ? e.touches[0] : e,
5816                     sizedParent = getSizedParentNode(this._element);
5817
5818                 this._startPoint = new Point(first.clientX, first.clientY);
5819
5820                 // Cache the scale, so that we can continuously compensate for it during drag (_onMove).
5821                 this._parentScale = getScale(sizedParent);
5822
5823                 on(document, MOVE[e.type], this._onMove, this);
5824                 on(document, END[e.type], this._onUp, this);
5825         },
5826
5827         _onMove: function (e) {
5828                 // Ignore simulated events, since we handle both touch and
5829                 // mouse explicitly; otherwise we risk getting duplicates of
5830                 // touch events, see #4315.
5831                 // Also ignore the event if disabled; this happens in IE11
5832                 // under some circumstances, see #3666.
5833                 if (e._simulated || !this._enabled) { return; }
5834
5835                 if (e.touches && e.touches.length > 1) {
5836                         this._moved = true;
5837                         return;
5838                 }
5839
5840                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5841                     offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);
5842
5843                 if (!offset.x && !offset.y) { return; }
5844                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5845
5846                 // We assume that the parent container's position, border and scale do not change for the duration of the drag.
5847                 // Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
5848                 // and we can use the cached value for the scale.
5849                 offset.x /= this._parentScale.x;
5850                 offset.y /= this._parentScale.y;
5851
5852                 preventDefault(e);
5853
5854                 if (!this._moved) {
5855                         // @event dragstart: Event
5856                         // Fired when a drag starts
5857                         this.fire('dragstart');
5858
5859                         this._moved = true;
5860                         this._startPos = getPosition(this._element).subtract(offset);
5861
5862                         addClass(document.body, 'leaflet-dragging');
5863
5864                         this._lastTarget = e.target || e.srcElement;
5865                         // IE and Edge do not give the <use> element, so fetch it
5866                         // if necessary
5867                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5868                                 this._lastTarget = this._lastTarget.correspondingUseElement;
5869                         }
5870                         addClass(this._lastTarget, 'leaflet-drag-target');
5871                 }
5872
5873                 this._newPos = this._startPos.add(offset);
5874                 this._moving = true;
5875
5876                 cancelAnimFrame(this._animRequest);
5877                 this._lastEvent = e;
5878                 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5879         },
5880
5881         _updatePosition: function () {
5882                 var e = {originalEvent: this._lastEvent};
5883
5884                 // @event predrag: Event
5885                 // Fired continuously during dragging *before* each corresponding
5886                 // update of the element's position.
5887                 this.fire('predrag', e);
5888                 setPosition(this._element, this._newPos);
5889
5890                 // @event drag: Event
5891                 // Fired continuously during dragging.
5892                 this.fire('drag', e);
5893         },
5894
5895         _onUp: function (e) {
5896                 // Ignore simulated events, since we handle both touch and
5897                 // mouse explicitly; otherwise we risk getting duplicates of
5898                 // touch events, see #4315.
5899                 // Also ignore the event if disabled; this happens in IE11
5900                 // under some circumstances, see #3666.
5901                 if (e._simulated || !this._enabled) { return; }
5902                 this.finishDrag();
5903         },
5904
5905         finishDrag: function () {
5906                 removeClass(document.body, 'leaflet-dragging');
5907
5908                 if (this._lastTarget) {
5909                         removeClass(this._lastTarget, 'leaflet-drag-target');
5910                         this._lastTarget = null;
5911                 }
5912
5913                 for (var i in MOVE) {
5914                         off(document, MOVE[i], this._onMove, this);
5915                         off(document, END[i], this._onUp, this);
5916                 }
5917
5918                 enableImageDrag();
5919                 enableTextSelection();
5920
5921                 if (this._moved && this._moving) {
5922                         // ensure drag is not fired after dragend
5923                         cancelAnimFrame(this._animRequest);
5924
5925                         // @event dragend: DragEndEvent
5926                         // Fired when the drag ends.
5927                         this.fire('dragend', {
5928                                 distance: this._newPos.distanceTo(this._startPos)
5929                         });
5930                 }
5931
5932                 this._moving = false;
5933                 Draggable._dragging = false;
5934         }
5935
5936 });
5937
5938 /*
5939  * @namespace LineUtil
5940  *
5941  * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
5942  */
5943
5944 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5945 // Improves rendering performance dramatically by lessening the number of points to draw.
5946
5947 // @function simplify(points: Point[], tolerance: Number): Point[]
5948 // Dramatically reduces the number of points in a polyline while retaining
5949 // its shape and returns a new array of simplified points, using the
5950 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5951 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5952 // each zoom level and also reducing visual noise. tolerance affects the amount of
5953 // simplification (lesser value means higher quality but slower and with more points).
5954 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5955 function simplify(points, tolerance) {
5956         if (!tolerance || !points.length) {
5957                 return points.slice();
5958         }
5959
5960         var sqTolerance = tolerance * tolerance;
5961
5962             // stage 1: vertex reduction
5963             points = _reducePoints(points, sqTolerance);
5964
5965             // stage 2: Douglas-Peucker simplification
5966             points = _simplifyDP(points, sqTolerance);
5967
5968         return points;
5969 }
5970
5971 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
5972 // Returns the distance between point `p` and segment `p1` to `p2`.
5973 function pointToSegmentDistance(p, p1, p2) {
5974         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
5975 }
5976
5977 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
5978 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
5979 function closestPointOnSegment(p, p1, p2) {
5980         return _sqClosestPointOnSegment(p, p1, p2);
5981 }
5982
5983 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5984 function _simplifyDP(points, sqTolerance) {
5985
5986         var len = points.length,
5987             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5988             markers = new ArrayConstructor(len);
5989
5990             markers[0] = markers[len - 1] = 1;
5991
5992         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5993
5994         var i,
5995             newPoints = [];
5996
5997         for (i = 0; i < len; i++) {
5998                 if (markers[i]) {
5999                         newPoints.push(points[i]);
6000                 }
6001         }
6002
6003         return newPoints;
6004 }
6005
6006 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
6007
6008         var maxSqDist = 0,
6009         index, i, sqDist;
6010
6011         for (i = first + 1; i <= last - 1; i++) {
6012                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
6013
6014                 if (sqDist > maxSqDist) {
6015                         index = i;
6016                         maxSqDist = sqDist;
6017                 }
6018         }
6019
6020         if (maxSqDist > sqTolerance) {
6021                 markers[index] = 1;
6022
6023                 _simplifyDPStep(points, markers, sqTolerance, first, index);
6024                 _simplifyDPStep(points, markers, sqTolerance, index, last);
6025         }
6026 }
6027
6028 // reduce points that are too close to each other to a single point
6029 function _reducePoints(points, sqTolerance) {
6030         var reducedPoints = [points[0]];
6031
6032         for (var i = 1, prev = 0, len = points.length; i < len; i++) {
6033                 if (_sqDist(points[i], points[prev]) > sqTolerance) {
6034                         reducedPoints.push(points[i]);
6035                         prev = i;
6036                 }
6037         }
6038         if (prev < len - 1) {
6039                 reducedPoints.push(points[len - 1]);
6040         }
6041         return reducedPoints;
6042 }
6043
6044 var _lastCode;
6045
6046 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
6047 // Clips the segment a to b by rectangular bounds with the
6048 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
6049 // (modifying the segment points directly!). Used by Leaflet to only show polyline
6050 // points that are on the screen or near, increasing performance.
6051 function clipSegment(a, b, bounds, useLastCode, round) {
6052         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
6053             codeB = _getBitCode(b, bounds),
6054
6055             codeOut, p, newCode;
6056
6057             // save 2nd code to avoid calculating it on the next segment
6058             _lastCode = codeB;
6059
6060         while (true) {
6061                 // if a,b is inside the clip window (trivial accept)
6062                 if (!(codeA | codeB)) {
6063                         return [a, b];
6064                 }
6065
6066                 // if a,b is outside the clip window (trivial reject)
6067                 if (codeA & codeB) {
6068                         return false;
6069                 }
6070
6071                 // other cases
6072                 codeOut = codeA || codeB;
6073                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
6074                 newCode = _getBitCode(p, bounds);
6075
6076                 if (codeOut === codeA) {
6077                         a = p;
6078                         codeA = newCode;
6079                 } else {
6080                         b = p;
6081                         codeB = newCode;
6082                 }
6083         }
6084 }
6085
6086 function _getEdgeIntersection(a, b, code, bounds, round) {
6087         var dx = b.x - a.x,
6088             dy = b.y - a.y,
6089             min = bounds.min,
6090             max = bounds.max,
6091             x, y;
6092
6093         if (code & 8) { // top
6094                 x = a.x + dx * (max.y - a.y) / dy;
6095                 y = max.y;
6096
6097         } else if (code & 4) { // bottom
6098                 x = a.x + dx * (min.y - a.y) / dy;
6099                 y = min.y;
6100
6101         } else if (code & 2) { // right
6102                 x = max.x;
6103                 y = a.y + dy * (max.x - a.x) / dx;
6104
6105         } else if (code & 1) { // left
6106                 x = min.x;
6107                 y = a.y + dy * (min.x - a.x) / dx;
6108         }
6109
6110         return new Point(x, y, round);
6111 }
6112
6113 function _getBitCode(p, bounds) {
6114         var code = 0;
6115
6116         if (p.x < bounds.min.x) { // left
6117                 code |= 1;
6118         } else if (p.x > bounds.max.x) { // right
6119                 code |= 2;
6120         }
6121
6122         if (p.y < bounds.min.y) { // bottom
6123                 code |= 4;
6124         } else if (p.y > bounds.max.y) { // top
6125                 code |= 8;
6126         }
6127
6128         return code;
6129 }
6130
6131 // square distance (to avoid unnecessary Math.sqrt calls)
6132 function _sqDist(p1, p2) {
6133         var dx = p2.x - p1.x,
6134             dy = p2.y - p1.y;
6135         return dx * dx + dy * dy;
6136 }
6137
6138 // return closest point on segment or distance to that point
6139 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6140         var x = p1.x,
6141             y = p1.y,
6142             dx = p2.x - x,
6143             dy = p2.y - y,
6144             dot = dx * dx + dy * dy,
6145             t;
6146
6147         if (dot > 0) {
6148                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6149
6150                 if (t > 1) {
6151                         x = p2.x;
6152                         y = p2.y;
6153                 } else if (t > 0) {
6154                         x += dx * t;
6155                         y += dy * t;
6156                 }
6157         }
6158
6159         dx = p.x - x;
6160         dy = p.y - y;
6161
6162         return sqDist ? dx * dx + dy * dy : new Point(x, y);
6163 }
6164
6165
6166 // @function isFlat(latlngs: LatLng[]): Boolean
6167 // Returns true if `latlngs` is a flat array, false is nested.
6168 function isFlat(latlngs) {
6169         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6170 }
6171
6172 function _flat(latlngs) {
6173         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6174         return isFlat(latlngs);
6175 }
6176
6177
6178 var LineUtil = (Object.freeze || Object)({
6179         simplify: simplify,
6180         pointToSegmentDistance: pointToSegmentDistance,
6181         closestPointOnSegment: closestPointOnSegment,
6182         clipSegment: clipSegment,
6183         _getEdgeIntersection: _getEdgeIntersection,
6184         _getBitCode: _getBitCode,
6185         _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6186         isFlat: isFlat,
6187         _flat: _flat
6188 });
6189
6190 /*
6191  * @namespace PolyUtil
6192  * Various utility functions for polygon geometries.
6193  */
6194
6195 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6196  * 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)).
6197  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6198  * performance. Note that polygon points needs different algorithm for clipping
6199  * than polyline, so there's a separate method for it.
6200  */
6201 function clipPolygon(points, bounds, round) {
6202         var clippedPoints,
6203             edges = [1, 4, 2, 8],
6204             i, j, k,
6205             a, b,
6206             len, edge, p;
6207
6208         for (i = 0, len = points.length; i < len; i++) {
6209                 points[i]._code = _getBitCode(points[i], bounds);
6210         }
6211
6212         // for each edge (left, bottom, right, top)
6213         for (k = 0; k < 4; k++) {
6214                 edge = edges[k];
6215                 clippedPoints = [];
6216
6217                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6218                         a = points[i];
6219                         b = points[j];
6220
6221                         // if a is inside the clip window
6222                         if (!(a._code & edge)) {
6223                                 // if b is outside the clip window (a->b goes out of screen)
6224                                 if (b._code & edge) {
6225                                         p = _getEdgeIntersection(b, a, edge, bounds, round);
6226                                         p._code = _getBitCode(p, bounds);
6227                                         clippedPoints.push(p);
6228                                 }
6229                                 clippedPoints.push(a);
6230
6231                         // else if b is inside the clip window (a->b enters the screen)
6232                         } else if (!(b._code & edge)) {
6233                                 p = _getEdgeIntersection(b, a, edge, bounds, round);
6234                                 p._code = _getBitCode(p, bounds);
6235                                 clippedPoints.push(p);
6236                         }
6237                 }
6238                 points = clippedPoints;
6239         }
6240
6241         return points;
6242 }
6243
6244
6245 var PolyUtil = (Object.freeze || Object)({
6246         clipPolygon: clipPolygon
6247 });
6248
6249 /*
6250  * @namespace Projection
6251  * @section
6252  * Leaflet comes with a set of already defined Projections out of the box:
6253  *
6254  * @projection L.Projection.LonLat
6255  *
6256  * Equirectangular, or Plate Carree projection — the most simple projection,
6257  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6258  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6259  * `EPSG:4326` and `Simple` CRS.
6260  */
6261
6262 var LonLat = {
6263         project: function (latlng) {
6264                 return new Point(latlng.lng, latlng.lat);
6265         },
6266
6267         unproject: function (point) {
6268                 return new LatLng(point.y, point.x);
6269         },
6270
6271         bounds: new Bounds([-180, -90], [180, 90])
6272 };
6273
6274 /*
6275  * @namespace Projection
6276  * @projection L.Projection.Mercator
6277  *
6278  * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
6279  */
6280
6281 var Mercator = {
6282         R: 6378137,
6283         R_MINOR: 6356752.314245179,
6284
6285         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6286
6287         project: function (latlng) {
6288                 var d = Math.PI / 180,
6289                     r = this.R,
6290                     y = latlng.lat * d,
6291                     tmp = this.R_MINOR / r,
6292                     e = Math.sqrt(1 - tmp * tmp),
6293                     con = e * Math.sin(y);
6294
6295                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6296                 y = -r * Math.log(Math.max(ts, 1E-10));
6297
6298                 return new Point(latlng.lng * d * r, y);
6299         },
6300
6301         unproject: function (point) {
6302                 var d = 180 / Math.PI,
6303                     r = this.R,
6304                     tmp = this.R_MINOR / r,
6305                     e = Math.sqrt(1 - tmp * tmp),
6306                     ts = Math.exp(-point.y / r),
6307                     phi = Math.PI / 2 - 2 * Math.atan(ts);
6308
6309                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6310                         con = e * Math.sin(phi);
6311                         con = Math.pow((1 - con) / (1 + con), e / 2);
6312                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6313                         phi += dphi;
6314                 }
6315
6316                 return new LatLng(phi * d, point.x * d / r);
6317         }
6318 };
6319
6320 /*
6321  * @class Projection
6322
6323  * An object with methods for projecting geographical coordinates of the world onto
6324  * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6325
6326  * @property bounds: Bounds
6327  * The bounds (specified in CRS units) where the projection is valid
6328
6329  * @method project(latlng: LatLng): Point
6330  * Projects geographical coordinates into a 2D point.
6331  * Only accepts actual `L.LatLng` instances, not arrays.
6332
6333  * @method unproject(point: Point): LatLng
6334  * The inverse of `project`. Projects a 2D point into a geographical location.
6335  * Only accepts actual `L.Point` instances, not arrays.
6336
6337  * Note that the projection instances do not inherit from Leafet's `Class` object,
6338  * and can't be instantiated. Also, new classes can't inherit from them,
6339  * and methods can't be added to them with the `include` function.
6340
6341  */
6342
6343
6344
6345
6346 var index = (Object.freeze || Object)({
6347         LonLat: LonLat,
6348         Mercator: Mercator,
6349         SphericalMercator: SphericalMercator
6350 });
6351
6352 /*
6353  * @namespace CRS
6354  * @crs L.CRS.EPSG3395
6355  *
6356  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6357  */
6358 var EPSG3395 = extend({}, Earth, {
6359         code: 'EPSG:3395',
6360         projection: Mercator,
6361
6362         transformation: (function () {
6363                 var scale = 0.5 / (Math.PI * Mercator.R);
6364                 return toTransformation(scale, 0.5, -scale, 0.5);
6365         }())
6366 });
6367
6368 /*
6369  * @namespace CRS
6370  * @crs L.CRS.EPSG4326
6371  *
6372  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6373  *
6374  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6375  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
6376  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6377  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6378  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6379  */
6380
6381 var EPSG4326 = extend({}, Earth, {
6382         code: 'EPSG:4326',
6383         projection: LonLat,
6384         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6385 });
6386
6387 /*
6388  * @namespace CRS
6389  * @crs L.CRS.Simple
6390  *
6391  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6392  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6393  * axis should still be inverted (going from bottom to top). `distance()` returns
6394  * simple euclidean distance.
6395  */
6396
6397 var Simple = extend({}, CRS, {
6398         projection: LonLat,
6399         transformation: toTransformation(1, 0, -1, 0),
6400
6401         scale: function (zoom) {
6402                 return Math.pow(2, zoom);
6403         },
6404
6405         zoom: function (scale) {
6406                 return Math.log(scale) / Math.LN2;
6407         },
6408
6409         distance: function (latlng1, latlng2) {
6410                 var dx = latlng2.lng - latlng1.lng,
6411                     dy = latlng2.lat - latlng1.lat;
6412
6413                 return Math.sqrt(dx * dx + dy * dy);
6414         },
6415
6416         infinite: true
6417 });
6418
6419 CRS.Earth = Earth;
6420 CRS.EPSG3395 = EPSG3395;
6421 CRS.EPSG3857 = EPSG3857;
6422 CRS.EPSG900913 = EPSG900913;
6423 CRS.EPSG4326 = EPSG4326;
6424 CRS.Simple = Simple;
6425
6426 /*
6427  * @class Layer
6428  * @inherits Evented
6429  * @aka L.Layer
6430  * @aka ILayer
6431  *
6432  * A set of methods from the Layer base class that all Leaflet layers use.
6433  * Inherits all methods, options and events from `L.Evented`.
6434  *
6435  * @example
6436  *
6437  * ```js
6438  * var layer = L.Marker(latlng).addTo(map);
6439  * layer.addTo(map);
6440  * layer.remove();
6441  * ```
6442  *
6443  * @event add: Event
6444  * Fired after the layer is added to a map
6445  *
6446  * @event remove: Event
6447  * Fired after the layer is removed from a map
6448  */
6449
6450
6451 var Layer = Evented.extend({
6452
6453         // Classes extending `L.Layer` will inherit the following options:
6454         options: {
6455                 // @option pane: String = 'overlayPane'
6456                 // 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.
6457                 pane: 'overlayPane',
6458
6459                 // @option attribution: String = null
6460                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6461                 attribution: null,
6462
6463                 bubblingMouseEvents: true
6464         },
6465
6466         /* @section
6467          * Classes extending `L.Layer` will inherit the following methods:
6468          *
6469          * @method addTo(map: Map|LayerGroup): this
6470          * Adds the layer to the given map or layer group.
6471          */
6472         addTo: function (map) {
6473                 map.addLayer(this);
6474                 return this;
6475         },
6476
6477         // @method remove: this
6478         // Removes the layer from the map it is currently active on.
6479         remove: function () {
6480                 return this.removeFrom(this._map || this._mapToAdd);
6481         },
6482
6483         // @method removeFrom(map: Map): this
6484         // Removes the layer from the given map
6485         removeFrom: function (obj) {
6486                 if (obj) {
6487                         obj.removeLayer(this);
6488                 }
6489                 return this;
6490         },
6491
6492         // @method getPane(name? : String): HTMLElement
6493         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6494         getPane: function (name) {
6495                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6496         },
6497
6498         addInteractiveTarget: function (targetEl) {
6499                 this._map._targets[stamp(targetEl)] = this;
6500                 return this;
6501         },
6502
6503         removeInteractiveTarget: function (targetEl) {
6504                 delete this._map._targets[stamp(targetEl)];
6505                 return this;
6506         },
6507
6508         // @method getAttribution: String
6509         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6510         getAttribution: function () {
6511                 return this.options.attribution;
6512         },
6513
6514         _layerAdd: function (e) {
6515                 var map = e.target;
6516
6517                 // check in case layer gets added and then removed before the map is ready
6518                 if (!map.hasLayer(this)) { return; }
6519
6520                 this._map = map;
6521                 this._zoomAnimated = map._zoomAnimated;
6522
6523                 if (this.getEvents) {
6524                         var events = this.getEvents();
6525                         map.on(events, this);
6526                         this.once('remove', function () {
6527                                 map.off(events, this);
6528                         }, this);
6529                 }
6530
6531                 this.onAdd(map);
6532
6533                 if (this.getAttribution && map.attributionControl) {
6534                         map.attributionControl.addAttribution(this.getAttribution());
6535                 }
6536
6537                 this.fire('add');
6538                 map.fire('layeradd', {layer: this});
6539         }
6540 });
6541
6542 /* @section Extension methods
6543  * @uninheritable
6544  *
6545  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6546  *
6547  * @method onAdd(map: Map): this
6548  * 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).
6549  *
6550  * @method onRemove(map: Map): this
6551  * 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).
6552  *
6553  * @method getEvents(): Object
6554  * 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.
6555  *
6556  * @method getAttribution(): String
6557  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6558  *
6559  * @method beforeAdd(map: Map): this
6560  * 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.
6561  */
6562
6563
6564 /* @namespace Map
6565  * @section Layer events
6566  *
6567  * @event layeradd: LayerEvent
6568  * Fired when a new layer is added to the map.
6569  *
6570  * @event layerremove: LayerEvent
6571  * Fired when some layer is removed from the map
6572  *
6573  * @section Methods for Layers and Controls
6574  */
6575 Map.include({
6576         // @method addLayer(layer: Layer): this
6577         // Adds the given layer to the map
6578         addLayer: function (layer) {
6579                 if (!layer._layerAdd) {
6580                         throw new Error('The provided object is not a Layer.');
6581                 }
6582
6583                 var id = stamp(layer);
6584                 if (this._layers[id]) { return this; }
6585                 this._layers[id] = layer;
6586
6587                 layer._mapToAdd = this;
6588
6589                 if (layer.beforeAdd) {
6590                         layer.beforeAdd(this);
6591                 }
6592
6593                 this.whenReady(layer._layerAdd, layer);
6594
6595                 return this;
6596         },
6597
6598         // @method removeLayer(layer: Layer): this
6599         // Removes the given layer from the map.
6600         removeLayer: function (layer) {
6601                 var id = stamp(layer);
6602
6603                 if (!this._layers[id]) { return this; }
6604
6605                 if (this._loaded) {
6606                         layer.onRemove(this);
6607                 }
6608
6609                 if (layer.getAttribution && this.attributionControl) {
6610                         this.attributionControl.removeAttribution(layer.getAttribution());
6611                 }
6612
6613                 delete this._layers[id];
6614
6615                 if (this._loaded) {
6616                         this.fire('layerremove', {layer: layer});
6617                         layer.fire('remove');
6618                 }
6619
6620                 layer._map = layer._mapToAdd = null;
6621
6622                 return this;
6623         },
6624
6625         // @method hasLayer(layer: Layer): Boolean
6626         // Returns `true` if the given layer is currently added to the map
6627         hasLayer: function (layer) {
6628                 return !!layer && (stamp(layer) in this._layers);
6629         },
6630
6631         /* @method eachLayer(fn: Function, context?: Object): this
6632          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6633          * ```
6634          * map.eachLayer(function(layer){
6635          *     layer.bindPopup('Hello');
6636          * });
6637          * ```
6638          */
6639         eachLayer: function (method, context) {
6640                 for (var i in this._layers) {
6641                         method.call(context, this._layers[i]);
6642                 }
6643                 return this;
6644         },
6645
6646         _addLayers: function (layers) {
6647                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6648
6649                 for (var i = 0, len = layers.length; i < len; i++) {
6650                         this.addLayer(layers[i]);
6651                 }
6652         },
6653
6654         _addZoomLimit: function (layer) {
6655                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6656                         this._zoomBoundLayers[stamp(layer)] = layer;
6657                         this._updateZoomLevels();
6658                 }
6659         },
6660
6661         _removeZoomLimit: function (layer) {
6662                 var id = stamp(layer);
6663
6664                 if (this._zoomBoundLayers[id]) {
6665                         delete this._zoomBoundLayers[id];
6666                         this._updateZoomLevels();
6667                 }
6668         },
6669
6670         _updateZoomLevels: function () {
6671                 var minZoom = Infinity,
6672                     maxZoom = -Infinity,
6673                     oldZoomSpan = this._getZoomSpan();
6674
6675                 for (var i in this._zoomBoundLayers) {
6676                         var options = this._zoomBoundLayers[i].options;
6677
6678                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6679                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6680                 }
6681
6682                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6683                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6684
6685                 // @section Map state change events
6686                 // @event zoomlevelschange: Event
6687                 // Fired when the number of zoomlevels on the map is changed due
6688                 // to adding or removing a layer.
6689                 if (oldZoomSpan !== this._getZoomSpan()) {
6690                         this.fire('zoomlevelschange');
6691                 }
6692
6693                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6694                         this.setZoom(this._layersMaxZoom);
6695                 }
6696                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6697                         this.setZoom(this._layersMinZoom);
6698                 }
6699         }
6700 });
6701
6702 /*
6703  * @class LayerGroup
6704  * @aka L.LayerGroup
6705  * @inherits Layer
6706  *
6707  * Used to group several layers and handle them as one. If you add it to the map,
6708  * any layers added or removed from the group will be added/removed on the map as
6709  * well. Extends `Layer`.
6710  *
6711  * @example
6712  *
6713  * ```js
6714  * L.layerGroup([marker1, marker2])
6715  *      .addLayer(polyline)
6716  *      .addTo(map);
6717  * ```
6718  */
6719
6720 var LayerGroup = Layer.extend({
6721
6722         initialize: function (layers, options) {
6723                 setOptions(this, options);
6724
6725                 this._layers = {};
6726
6727                 var i, len;
6728
6729                 if (layers) {
6730                         for (i = 0, len = layers.length; i < len; i++) {
6731                                 this.addLayer(layers[i]);
6732                         }
6733                 }
6734         },
6735
6736         // @method addLayer(layer: Layer): this
6737         // Adds the given layer to the group.
6738         addLayer: function (layer) {
6739                 var id = this.getLayerId(layer);
6740
6741                 this._layers[id] = layer;
6742
6743                 if (this._map) {
6744                         this._map.addLayer(layer);
6745                 }
6746
6747                 return this;
6748         },
6749
6750         // @method removeLayer(layer: Layer): this
6751         // Removes the given layer from the group.
6752         // @alternative
6753         // @method removeLayer(id: Number): this
6754         // Removes the layer with the given internal ID from the group.
6755         removeLayer: function (layer) {
6756                 var id = layer in this._layers ? layer : this.getLayerId(layer);
6757
6758                 if (this._map && this._layers[id]) {
6759                         this._map.removeLayer(this._layers[id]);
6760                 }
6761
6762                 delete this._layers[id];
6763
6764                 return this;
6765         },
6766
6767         // @method hasLayer(layer: Layer): Boolean
6768         // Returns `true` if the given layer is currently added to the group.
6769         // @alternative
6770         // @method hasLayer(id: Number): Boolean
6771         // Returns `true` if the given internal ID is currently added to the group.
6772         hasLayer: function (layer) {
6773                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6774         },
6775
6776         // @method clearLayers(): this
6777         // Removes all the layers from the group.
6778         clearLayers: function () {
6779                 return this.eachLayer(this.removeLayer, this);
6780         },
6781
6782         // @method invoke(methodName: String, …): this
6783         // Calls `methodName` on every layer contained in this group, passing any
6784         // additional parameters. Has no effect if the layers contained do not
6785         // implement `methodName`.
6786         invoke: function (methodName) {
6787                 var args = Array.prototype.slice.call(arguments, 1),
6788                     i, layer;
6789
6790                 for (i in this._layers) {
6791                         layer = this._layers[i];
6792
6793                         if (layer[methodName]) {
6794                                 layer[methodName].apply(layer, args);
6795                         }
6796                 }
6797
6798                 return this;
6799         },
6800
6801         onAdd: function (map) {
6802                 this.eachLayer(map.addLayer, map);
6803         },
6804
6805         onRemove: function (map) {
6806                 this.eachLayer(map.removeLayer, map);
6807         },
6808
6809         // @method eachLayer(fn: Function, context?: Object): this
6810         // Iterates over the layers of the group, optionally specifying context of the iterator function.
6811         // ```js
6812         // group.eachLayer(function (layer) {
6813         //      layer.bindPopup('Hello');
6814         // });
6815         // ```
6816         eachLayer: function (method, context) {
6817                 for (var i in this._layers) {
6818                         method.call(context, this._layers[i]);
6819                 }
6820                 return this;
6821         },
6822
6823         // @method getLayer(id: Number): Layer
6824         // Returns the layer with the given internal ID.
6825         getLayer: function (id) {
6826                 return this._layers[id];
6827         },
6828
6829         // @method getLayers(): Layer[]
6830         // Returns an array of all the layers added to the group.
6831         getLayers: function () {
6832                 var layers = [];
6833                 this.eachLayer(layers.push, layers);
6834                 return layers;
6835         },
6836
6837         // @method setZIndex(zIndex: Number): this
6838         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6839         setZIndex: function (zIndex) {
6840                 return this.invoke('setZIndex', zIndex);
6841         },
6842
6843         // @method getLayerId(layer: Layer): Number
6844         // Returns the internal ID for a layer
6845         getLayerId: function (layer) {
6846                 return stamp(layer);
6847         }
6848 });
6849
6850
6851 // @factory L.layerGroup(layers?: Layer[], options?: Object)
6852 // Create a layer group, optionally given an initial set of layers and an `options` object.
6853 var layerGroup = function (layers, options) {
6854         return new LayerGroup(layers, options);
6855 };
6856
6857 /*
6858  * @class FeatureGroup
6859  * @aka L.FeatureGroup
6860  * @inherits LayerGroup
6861  *
6862  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6863  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6864  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
6865  * handler, it will handle events from any of the layers. This includes mouse events
6866  * and custom events.
6867  *  * Has `layeradd` and `layerremove` events
6868  *
6869  * @example
6870  *
6871  * ```js
6872  * L.featureGroup([marker1, marker2, polyline])
6873  *      .bindPopup('Hello world!')
6874  *      .on('click', function() { alert('Clicked on a member of the group!'); })
6875  *      .addTo(map);
6876  * ```
6877  */
6878
6879 var FeatureGroup = LayerGroup.extend({
6880
6881         addLayer: function (layer) {
6882                 if (this.hasLayer(layer)) {
6883                         return this;
6884                 }
6885
6886                 layer.addEventParent(this);
6887
6888                 LayerGroup.prototype.addLayer.call(this, layer);
6889
6890                 // @event layeradd: LayerEvent
6891                 // Fired when a layer is added to this `FeatureGroup`
6892                 return this.fire('layeradd', {layer: layer});
6893         },
6894
6895         removeLayer: function (layer) {
6896                 if (!this.hasLayer(layer)) {
6897                         return this;
6898                 }
6899                 if (layer in this._layers) {
6900                         layer = this._layers[layer];
6901                 }
6902
6903                 layer.removeEventParent(this);
6904
6905                 LayerGroup.prototype.removeLayer.call(this, layer);
6906
6907                 // @event layerremove: LayerEvent
6908                 // Fired when a layer is removed from this `FeatureGroup`
6909                 return this.fire('layerremove', {layer: layer});
6910         },
6911
6912         // @method setStyle(style: Path options): this
6913         // Sets the given path options to each layer of the group that has a `setStyle` method.
6914         setStyle: function (style) {
6915                 return this.invoke('setStyle', style);
6916         },
6917
6918         // @method bringToFront(): this
6919         // Brings the layer group to the top of all other layers
6920         bringToFront: function () {
6921                 return this.invoke('bringToFront');
6922         },
6923
6924         // @method bringToBack(): this
6925         // Brings the layer group to the back of all other layers
6926         bringToBack: function () {
6927                 return this.invoke('bringToBack');
6928         },
6929
6930         // @method getBounds(): LatLngBounds
6931         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6932         getBounds: function () {
6933                 var bounds = new LatLngBounds();
6934
6935                 for (var id in this._layers) {
6936                         var layer = this._layers[id];
6937                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6938                 }
6939                 return bounds;
6940         }
6941 });
6942
6943 // @factory L.featureGroup(layers: Layer[])
6944 // Create a feature group, optionally given an initial set of layers.
6945 var featureGroup = function (layers) {
6946         return new FeatureGroup(layers);
6947 };
6948
6949 /*
6950  * @class Icon
6951  * @aka L.Icon
6952  *
6953  * Represents an icon to provide when creating a marker.
6954  *
6955  * @example
6956  *
6957  * ```js
6958  * var myIcon = L.icon({
6959  *     iconUrl: 'my-icon.png',
6960  *     iconRetinaUrl: 'my-icon@2x.png',
6961  *     iconSize: [38, 95],
6962  *     iconAnchor: [22, 94],
6963  *     popupAnchor: [-3, -76],
6964  *     shadowUrl: 'my-icon-shadow.png',
6965  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
6966  *     shadowSize: [68, 95],
6967  *     shadowAnchor: [22, 94]
6968  * });
6969  *
6970  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6971  * ```
6972  *
6973  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6974  *
6975  */
6976
6977 var Icon = Class.extend({
6978
6979         /* @section
6980          * @aka Icon options
6981          *
6982          * @option iconUrl: String = null
6983          * **(required)** The URL to the icon image (absolute or relative to your script path).
6984          *
6985          * @option iconRetinaUrl: String = null
6986          * The URL to a retina sized version of the icon image (absolute or relative to your
6987          * script path). Used for Retina screen devices.
6988          *
6989          * @option iconSize: Point = null
6990          * Size of the icon image in pixels.
6991          *
6992          * @option iconAnchor: Point = null
6993          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6994          * will be aligned so that this point is at the marker's geographical location. Centered
6995          * by default if size is specified, also can be set in CSS with negative margins.
6996          *
6997          * @option popupAnchor: Point = [0, 0]
6998          * The coordinates of the point from which popups will "open", relative to the icon anchor.
6999          *
7000          * @option tooltipAnchor: Point = [0, 0]
7001          * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
7002          *
7003          * @option shadowUrl: String = null
7004          * The URL to the icon shadow image. If not specified, no shadow image will be created.
7005          *
7006          * @option shadowRetinaUrl: String = null
7007          *
7008          * @option shadowSize: Point = null
7009          * Size of the shadow image in pixels.
7010          *
7011          * @option shadowAnchor: Point = null
7012          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
7013          * as iconAnchor if not specified).
7014          *
7015          * @option className: String = ''
7016          * A custom class name to assign to both icon and shadow images. Empty by default.
7017          */
7018
7019         options: {
7020                 popupAnchor: [0, 0],
7021                 tooltipAnchor: [0, 0]
7022         },
7023
7024         initialize: function (options) {
7025                 setOptions(this, options);
7026         },
7027
7028         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
7029         // Called internally when the icon has to be shown, returns a `<img>` HTML element
7030         // styled according to the options.
7031         createIcon: function (oldIcon) {
7032                 return this._createIcon('icon', oldIcon);
7033         },
7034
7035         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
7036         // As `createIcon`, but for the shadow beneath it.
7037         createShadow: function (oldIcon) {
7038                 return this._createIcon('shadow', oldIcon);
7039         },
7040
7041         _createIcon: function (name, oldIcon) {
7042                 var src = this._getIconUrl(name);
7043
7044                 if (!src) {
7045                         if (name === 'icon') {
7046                                 throw new Error('iconUrl not set in Icon options (see the docs).');
7047                         }
7048                         return null;
7049                 }
7050
7051                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
7052                 this._setIconStyles(img, name);
7053
7054                 return img;
7055         },
7056
7057         _setIconStyles: function (img, name) {
7058                 var options = this.options;
7059                 var sizeOption = options[name + 'Size'];
7060
7061                 if (typeof sizeOption === 'number') {
7062                         sizeOption = [sizeOption, sizeOption];
7063                 }
7064
7065                 var size = toPoint(sizeOption),
7066                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
7067                             size && size.divideBy(2, true));
7068
7069                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
7070
7071                 if (anchor) {
7072                         img.style.marginLeft = (-anchor.x) + 'px';
7073                         img.style.marginTop  = (-anchor.y) + 'px';
7074                 }
7075
7076                 if (size) {
7077                         img.style.width  = size.x + 'px';
7078                         img.style.height = size.y + 'px';
7079                 }
7080         },
7081
7082         _createImg: function (src, el) {
7083                 el = el || document.createElement('img');
7084                 el.src = src;
7085                 return el;
7086         },
7087
7088         _getIconUrl: function (name) {
7089                 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
7090         }
7091 });
7092
7093
7094 // @factory L.icon(options: Icon options)
7095 // Creates an icon instance with the given options.
7096 function icon(options) {
7097         return new Icon(options);
7098 }
7099
7100 /*
7101  * @miniclass Icon.Default (Icon)
7102  * @aka L.Icon.Default
7103  * @section
7104  *
7105  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
7106  * no icon is specified. Points to the blue marker image distributed with Leaflet
7107  * releases.
7108  *
7109  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
7110  * (which is a set of `Icon options`).
7111  *
7112  * If you want to _completely_ replace the default icon, override the
7113  * `L.Marker.prototype.options.icon` with your own icon instead.
7114  */
7115
7116 var IconDefault = Icon.extend({
7117
7118         options: {
7119                 iconUrl:       'marker-icon.png',
7120                 iconRetinaUrl: 'marker-icon-2x.png',
7121                 shadowUrl:     'marker-shadow.png',
7122                 iconSize:    [25, 41],
7123                 iconAnchor:  [12, 41],
7124                 popupAnchor: [1, -34],
7125                 tooltipAnchor: [16, -28],
7126                 shadowSize:  [41, 41]
7127         },
7128
7129         _getIconUrl: function (name) {
7130                 if (!IconDefault.imagePath) {   // Deprecated, backwards-compatibility only
7131                         IconDefault.imagePath = this._detectIconPath();
7132                 }
7133
7134                 // @option imagePath: String
7135                 // `Icon.Default` will try to auto-detect the location of the
7136                 // blue icon images. If you are placing these images in a non-standard
7137                 // way, set this option to point to the right path.
7138                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7139         },
7140
7141         _detectIconPath: function () {
7142                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7143                 var path = getStyle(el, 'background-image') ||
7144                            getStyle(el, 'backgroundImage');     // IE8
7145
7146                 document.body.removeChild(el);
7147
7148                 if (path === null || path.indexOf('url') !== 0) {
7149                         path = '';
7150                 } else {
7151                         path = path.replace(/^url\(["']?/, '').replace(/marker-icon\.png["']?\)$/, '');
7152                 }
7153
7154                 return path;
7155         }
7156 });
7157
7158 /*
7159  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7160  */
7161
7162
7163 /* @namespace Marker
7164  * @section Interaction handlers
7165  *
7166  * 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:
7167  *
7168  * ```js
7169  * marker.dragging.disable();
7170  * ```
7171  *
7172  * @property dragging: Handler
7173  * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7174  */
7175
7176 var MarkerDrag = Handler.extend({
7177         initialize: function (marker) {
7178                 this._marker = marker;
7179         },
7180
7181         addHooks: function () {
7182                 var icon = this._marker._icon;
7183
7184                 if (!this._draggable) {
7185                         this._draggable = new Draggable(icon, icon, true);
7186                 }
7187
7188                 this._draggable.on({
7189                         dragstart: this._onDragStart,
7190                         predrag: this._onPreDrag,
7191                         drag: this._onDrag,
7192                         dragend: this._onDragEnd
7193                 }, this).enable();
7194
7195                 addClass(icon, 'leaflet-marker-draggable');
7196         },
7197
7198         removeHooks: function () {
7199                 this._draggable.off({
7200                         dragstart: this._onDragStart,
7201                         predrag: this._onPreDrag,
7202                         drag: this._onDrag,
7203                         dragend: this._onDragEnd
7204                 }, this).disable();
7205
7206                 if (this._marker._icon) {
7207                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7208                 }
7209         },
7210
7211         moved: function () {
7212                 return this._draggable && this._draggable._moved;
7213         },
7214
7215         _adjustPan: function (e) {
7216                 var marker = this._marker,
7217                     map = marker._map,
7218                     speed = this._marker.options.autoPanSpeed,
7219                     padding = this._marker.options.autoPanPadding,
7220                     iconPos = getPosition(marker._icon),
7221                     bounds = map.getPixelBounds(),
7222                     origin = map.getPixelOrigin();
7223
7224                 var panBounds = toBounds(
7225                         bounds.min._subtract(origin).add(padding),
7226                         bounds.max._subtract(origin).subtract(padding)
7227                 );
7228
7229                 if (!panBounds.contains(iconPos)) {
7230                         // Compute incremental movement
7231                         var movement = toPoint(
7232                                 (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
7233                                 (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),
7234
7235                                 (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
7236                                 (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
7237                         ).multiplyBy(speed);
7238
7239                         map.panBy(movement, {animate: false});
7240
7241                         this._draggable._newPos._add(movement);
7242                         this._draggable._startPos._add(movement);
7243
7244                         setPosition(marker._icon, this._draggable._newPos);
7245                         this._onDrag(e);
7246
7247                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7248                 }
7249         },
7250
7251         _onDragStart: function () {
7252                 // @section Dragging events
7253                 // @event dragstart: Event
7254                 // Fired when the user starts dragging the marker.
7255
7256                 // @event movestart: Event
7257                 // Fired when the marker starts moving (because of dragging).
7258
7259                 this._oldLatLng = this._marker.getLatLng();
7260                 this._marker
7261                     .closePopup()
7262                     .fire('movestart')
7263                     .fire('dragstart');
7264         },
7265
7266         _onPreDrag: function (e) {
7267                 if (this._marker.options.autoPan) {
7268                         cancelAnimFrame(this._panRequest);
7269                         this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
7270                 }
7271         },
7272
7273         _onDrag: function (e) {
7274                 var marker = this._marker,
7275                     shadow = marker._shadow,
7276                     iconPos = getPosition(marker._icon),
7277                     latlng = marker._map.layerPointToLatLng(iconPos);
7278
7279                 // update shadow position
7280                 if (shadow) {
7281                         setPosition(shadow, iconPos);
7282                 }
7283
7284                 marker._latlng = latlng;
7285                 e.latlng = latlng;
7286                 e.oldLatLng = this._oldLatLng;
7287
7288                 // @event drag: Event
7289                 // Fired repeatedly while the user drags the marker.
7290                 marker
7291                     .fire('move', e)
7292                     .fire('drag', e);
7293         },
7294
7295         _onDragEnd: function (e) {
7296                 // @event dragend: DragEndEvent
7297                 // Fired when the user stops dragging the marker.
7298
7299                  cancelAnimFrame(this._panRequest);
7300
7301                 // @event moveend: Event
7302                 // Fired when the marker stops moving (because of dragging).
7303                 delete this._oldLatLng;
7304                 this._marker
7305                     .fire('moveend')
7306                     .fire('dragend', e);
7307         }
7308 });
7309
7310 /*
7311  * @class Marker
7312  * @inherits Interactive layer
7313  * @aka L.Marker
7314  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7315  *
7316  * @example
7317  *
7318  * ```js
7319  * L.marker([50.5, 30.5]).addTo(map);
7320  * ```
7321  */
7322
7323 var Marker = Layer.extend({
7324
7325         // @section
7326         // @aka Marker options
7327         options: {
7328                 // @option icon: Icon = *
7329                 // Icon instance to use for rendering the marker.
7330                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7331                 // If not specified, a common instance of `L.Icon.Default` is used.
7332                 icon: new IconDefault(),
7333
7334                 // Option inherited from "Interactive layer" abstract class
7335                 interactive: true,
7336
7337                 // @option keyboard: Boolean = true
7338                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7339                 keyboard: true,
7340
7341                 // @option title: String = ''
7342                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7343                 title: '',
7344
7345                 // @option alt: String = ''
7346                 // Text for the `alt` attribute of the icon image (useful for accessibility).
7347                 alt: '',
7348
7349                 // @option zIndexOffset: Number = 0
7350                 // 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).
7351                 zIndexOffset: 0,
7352
7353                 // @option opacity: Number = 1.0
7354                 // The opacity of the marker.
7355                 opacity: 1,
7356
7357                 // @option riseOnHover: Boolean = false
7358                 // If `true`, the marker will get on top of others when you hover the mouse over it.
7359                 riseOnHover: false,
7360
7361                 // @option riseOffset: Number = 250
7362                 // The z-index offset used for the `riseOnHover` feature.
7363                 riseOffset: 250,
7364
7365                 // @option pane: String = 'markerPane'
7366                 // `Map pane` where the markers icon will be added.
7367                 pane: 'markerPane',
7368
7369                 // @option bubblingMouseEvents: Boolean = false
7370                 // When `true`, a mouse event on this marker will trigger the same event on the map
7371                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7372                 bubblingMouseEvents: false,
7373
7374                 // @section Draggable marker options
7375                 // @option draggable: Boolean = false
7376                 // Whether the marker is draggable with mouse/touch or not.
7377                 draggable: false,
7378
7379                 // @option autoPan: Boolean = false
7380                 // Whether to pan the map when dragging this marker near its edge or not.
7381                 autoPan: false,
7382
7383                 // @option autoPanPadding: Point = Point(50, 50)
7384                 // Distance (in pixels to the left/right and to the top/bottom) of the
7385                 // map edge to start panning the map.
7386                 autoPanPadding: [50, 50],
7387
7388                 // @option autoPanSpeed: Number = 10
7389                 // Number of pixels the map should pan by.
7390                 autoPanSpeed: 10
7391         },
7392
7393         /* @section
7394          *
7395          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7396          */
7397
7398         initialize: function (latlng, options) {
7399                 setOptions(this, options);
7400                 this._latlng = toLatLng(latlng);
7401         },
7402
7403         onAdd: function (map) {
7404                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7405
7406                 if (this._zoomAnimated) {
7407                         map.on('zoomanim', this._animateZoom, this);
7408                 }
7409
7410                 this._initIcon();
7411                 this.update();
7412         },
7413
7414         onRemove: function (map) {
7415                 if (this.dragging && this.dragging.enabled()) {
7416                         this.options.draggable = true;
7417                         this.dragging.removeHooks();
7418                 }
7419                 delete this.dragging;
7420
7421                 if (this._zoomAnimated) {
7422                         map.off('zoomanim', this._animateZoom, this);
7423                 }
7424
7425                 this._removeIcon();
7426                 this._removeShadow();
7427         },
7428
7429         getEvents: function () {
7430                 return {
7431                         zoom: this.update,
7432                         viewreset: this.update
7433                 };
7434         },
7435
7436         // @method getLatLng: LatLng
7437         // Returns the current geographical position of the marker.
7438         getLatLng: function () {
7439                 return this._latlng;
7440         },
7441
7442         // @method setLatLng(latlng: LatLng): this
7443         // Changes the marker position to the given point.
7444         setLatLng: function (latlng) {
7445                 var oldLatLng = this._latlng;
7446                 this._latlng = toLatLng(latlng);
7447                 this.update();
7448
7449                 // @event move: Event
7450                 // 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`.
7451                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7452         },
7453
7454         // @method setZIndexOffset(offset: Number): this
7455         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7456         setZIndexOffset: function (offset) {
7457                 this.options.zIndexOffset = offset;
7458                 return this.update();
7459         },
7460
7461         // @method setIcon(icon: Icon): this
7462         // Changes the marker icon.
7463         setIcon: function (icon) {
7464
7465                 this.options.icon = icon;
7466
7467                 if (this._map) {
7468                         this._initIcon();
7469                         this.update();
7470                 }
7471
7472                 if (this._popup) {
7473                         this.bindPopup(this._popup, this._popup.options);
7474                 }
7475
7476                 return this;
7477         },
7478
7479         getElement: function () {
7480                 return this._icon;
7481         },
7482
7483         update: function () {
7484
7485                 if (this._icon && this._map) {
7486                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
7487                         this._setPos(pos);
7488                 }
7489
7490                 return this;
7491         },
7492
7493         _initIcon: function () {
7494                 var options = this.options,
7495                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7496
7497                 var icon = options.icon.createIcon(this._icon),
7498                     addIcon = false;
7499
7500                 // if we're not reusing the icon, remove the old one and init new one
7501                 if (icon !== this._icon) {
7502                         if (this._icon) {
7503                                 this._removeIcon();
7504                         }
7505                         addIcon = true;
7506
7507                         if (options.title) {
7508                                 icon.title = options.title;
7509                         }
7510
7511                         if (icon.tagName === 'IMG') {
7512                                 icon.alt = options.alt || '';
7513                         }
7514                 }
7515
7516                 addClass(icon, classToAdd);
7517
7518                 if (options.keyboard) {
7519                         icon.tabIndex = '0';
7520                 }
7521
7522                 this._icon = icon;
7523
7524                 if (options.riseOnHover) {
7525                         this.on({
7526                                 mouseover: this._bringToFront,
7527                                 mouseout: this._resetZIndex
7528                         });
7529                 }
7530
7531                 var newShadow = options.icon.createShadow(this._shadow),
7532                     addShadow = false;
7533
7534                 if (newShadow !== this._shadow) {
7535                         this._removeShadow();
7536                         addShadow = true;
7537                 }
7538
7539                 if (newShadow) {
7540                         addClass(newShadow, classToAdd);
7541                         newShadow.alt = '';
7542                 }
7543                 this._shadow = newShadow;
7544
7545
7546                 if (options.opacity < 1) {
7547                         this._updateOpacity();
7548                 }
7549
7550
7551                 if (addIcon) {
7552                         this.getPane().appendChild(this._icon);
7553                 }
7554                 this._initInteraction();
7555                 if (newShadow && addShadow) {
7556                         this.getPane('shadowPane').appendChild(this._shadow);
7557                 }
7558         },
7559
7560         _removeIcon: function () {
7561                 if (this.options.riseOnHover) {
7562                         this.off({
7563                                 mouseover: this._bringToFront,
7564                                 mouseout: this._resetZIndex
7565                         });
7566                 }
7567
7568                 remove(this._icon);
7569                 this.removeInteractiveTarget(this._icon);
7570
7571                 this._icon = null;
7572         },
7573
7574         _removeShadow: function () {
7575                 if (this._shadow) {
7576                         remove(this._shadow);
7577                 }
7578                 this._shadow = null;
7579         },
7580
7581         _setPos: function (pos) {
7582                 setPosition(this._icon, pos);
7583
7584                 if (this._shadow) {
7585                         setPosition(this._shadow, pos);
7586                 }
7587
7588                 this._zIndex = pos.y + this.options.zIndexOffset;
7589
7590                 this._resetZIndex();
7591         },
7592
7593         _updateZIndex: function (offset) {
7594                 this._icon.style.zIndex = this._zIndex + offset;
7595         },
7596
7597         _animateZoom: function (opt) {
7598                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7599
7600                 this._setPos(pos);
7601         },
7602
7603         _initInteraction: function () {
7604
7605                 if (!this.options.interactive) { return; }
7606
7607                 addClass(this._icon, 'leaflet-interactive');
7608
7609                 this.addInteractiveTarget(this._icon);
7610
7611                 if (MarkerDrag) {
7612                         var draggable = this.options.draggable;
7613                         if (this.dragging) {
7614                                 draggable = this.dragging.enabled();
7615                                 this.dragging.disable();
7616                         }
7617
7618                         this.dragging = new MarkerDrag(this);
7619
7620                         if (draggable) {
7621                                 this.dragging.enable();
7622                         }
7623                 }
7624         },
7625
7626         // @method setOpacity(opacity: Number): this
7627         // Changes the opacity of the marker.
7628         setOpacity: function (opacity) {
7629                 this.options.opacity = opacity;
7630                 if (this._map) {
7631                         this._updateOpacity();
7632                 }
7633
7634                 return this;
7635         },
7636
7637         _updateOpacity: function () {
7638                 var opacity = this.options.opacity;
7639
7640                 setOpacity(this._icon, opacity);
7641
7642                 if (this._shadow) {
7643                         setOpacity(this._shadow, opacity);
7644                 }
7645         },
7646
7647         _bringToFront: function () {
7648                 this._updateZIndex(this.options.riseOffset);
7649         },
7650
7651         _resetZIndex: function () {
7652                 this._updateZIndex(0);
7653         },
7654
7655         _getPopupAnchor: function () {
7656                 return this.options.icon.options.popupAnchor;
7657         },
7658
7659         _getTooltipAnchor: function () {
7660                 return this.options.icon.options.tooltipAnchor;
7661         }
7662 });
7663
7664
7665 // factory L.marker(latlng: LatLng, options? : Marker options)
7666
7667 // @factory L.marker(latlng: LatLng, options? : Marker options)
7668 // Instantiates a Marker object given a geographical point and optionally an options object.
7669 function marker(latlng, options) {
7670         return new Marker(latlng, options);
7671 }
7672
7673 /*
7674  * @class Path
7675  * @aka L.Path
7676  * @inherits Interactive layer
7677  *
7678  * An abstract class that contains options and constants shared between vector
7679  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7680  */
7681
7682 var Path = Layer.extend({
7683
7684         // @section
7685         // @aka Path options
7686         options: {
7687                 // @option stroke: Boolean = true
7688                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7689                 stroke: true,
7690
7691                 // @option color: String = '#3388ff'
7692                 // Stroke color
7693                 color: '#3388ff',
7694
7695                 // @option weight: Number = 3
7696                 // Stroke width in pixels
7697                 weight: 3,
7698
7699                 // @option opacity: Number = 1.0
7700                 // Stroke opacity
7701                 opacity: 1,
7702
7703                 // @option lineCap: String= 'round'
7704                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7705                 lineCap: 'round',
7706
7707                 // @option lineJoin: String = 'round'
7708                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7709                 lineJoin: 'round',
7710
7711                 // @option dashArray: String = null
7712                 // 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).
7713                 dashArray: null,
7714
7715                 // @option dashOffset: String = null
7716                 // 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).
7717                 dashOffset: null,
7718
7719                 // @option fill: Boolean = depends
7720                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7721                 fill: false,
7722
7723                 // @option fillColor: String = *
7724                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7725                 fillColor: null,
7726
7727                 // @option fillOpacity: Number = 0.2
7728                 // Fill opacity.
7729                 fillOpacity: 0.2,
7730
7731                 // @option fillRule: String = 'evenodd'
7732                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7733                 fillRule: 'evenodd',
7734
7735                 // className: '',
7736
7737                 // Option inherited from "Interactive layer" abstract class
7738                 interactive: true,
7739
7740                 // @option bubblingMouseEvents: Boolean = true
7741                 // When `true`, a mouse event on this path will trigger the same event on the map
7742                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7743                 bubblingMouseEvents: true
7744         },
7745
7746         beforeAdd: function (map) {
7747                 // Renderer is set here because we need to call renderer.getEvents
7748                 // before this.getEvents.
7749                 this._renderer = map.getRenderer(this);
7750         },
7751
7752         onAdd: function () {
7753                 this._renderer._initPath(this);
7754                 this._reset();
7755                 this._renderer._addPath(this);
7756         },
7757
7758         onRemove: function () {
7759                 this._renderer._removePath(this);
7760         },
7761
7762         // @method redraw(): this
7763         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7764         redraw: function () {
7765                 if (this._map) {
7766                         this._renderer._updatePath(this);
7767                 }
7768                 return this;
7769         },
7770
7771         // @method setStyle(style: Path options): this
7772         // Changes the appearance of a Path based on the options in the `Path options` object.
7773         setStyle: function (style) {
7774                 setOptions(this, style);
7775                 if (this._renderer) {
7776                         this._renderer._updateStyle(this);
7777                 }
7778                 return this;
7779         },
7780
7781         // @method bringToFront(): this
7782         // Brings the layer to the top of all path layers.
7783         bringToFront: function () {
7784                 if (this._renderer) {
7785                         this._renderer._bringToFront(this);
7786                 }
7787                 return this;
7788         },
7789
7790         // @method bringToBack(): this
7791         // Brings the layer to the bottom of all path layers.
7792         bringToBack: function () {
7793                 if (this._renderer) {
7794                         this._renderer._bringToBack(this);
7795                 }
7796                 return this;
7797         },
7798
7799         getElement: function () {
7800                 return this._path;
7801         },
7802
7803         _reset: function () {
7804                 // defined in child classes
7805                 this._project();
7806                 this._update();
7807         },
7808
7809         _clickTolerance: function () {
7810                 // used when doing hit detection for Canvas layers
7811                 return (this.options.stroke ? this.options.weight / 2 : 0) + this._renderer.options.tolerance;
7812         }
7813 });
7814
7815 /*
7816  * @class CircleMarker
7817  * @aka L.CircleMarker
7818  * @inherits Path
7819  *
7820  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7821  */
7822
7823 var CircleMarker = Path.extend({
7824
7825         // @section
7826         // @aka CircleMarker options
7827         options: {
7828                 fill: true,
7829
7830                 // @option radius: Number = 10
7831                 // Radius of the circle marker, in pixels
7832                 radius: 10
7833         },
7834
7835         initialize: function (latlng, options) {
7836                 setOptions(this, options);
7837                 this._latlng = toLatLng(latlng);
7838                 this._radius = this.options.radius;
7839         },
7840
7841         // @method setLatLng(latLng: LatLng): this
7842         // Sets the position of a circle marker to a new location.
7843         setLatLng: function (latlng) {
7844                 this._latlng = toLatLng(latlng);
7845                 this.redraw();
7846                 return this.fire('move', {latlng: this._latlng});
7847         },
7848
7849         // @method getLatLng(): LatLng
7850         // Returns the current geographical position of the circle marker
7851         getLatLng: function () {
7852                 return this._latlng;
7853         },
7854
7855         // @method setRadius(radius: Number): this
7856         // Sets the radius of a circle marker. Units are in pixels.
7857         setRadius: function (radius) {
7858                 this.options.radius = this._radius = radius;
7859                 return this.redraw();
7860         },
7861
7862         // @method getRadius(): Number
7863         // Returns the current radius of the circle
7864         getRadius: function () {
7865                 return this._radius;
7866         },
7867
7868         setStyle : function (options) {
7869                 var radius = options && options.radius || this._radius;
7870                 Path.prototype.setStyle.call(this, options);
7871                 this.setRadius(radius);
7872                 return this;
7873         },
7874
7875         _project: function () {
7876                 this._point = this._map.latLngToLayerPoint(this._latlng);
7877                 this._updateBounds();
7878         },
7879
7880         _updateBounds: function () {
7881                 var r = this._radius,
7882                     r2 = this._radiusY || r,
7883                     w = this._clickTolerance(),
7884                     p = [r + w, r2 + w];
7885                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7886         },
7887
7888         _update: function () {
7889                 if (this._map) {
7890                         this._updatePath();
7891                 }
7892         },
7893
7894         _updatePath: function () {
7895                 this._renderer._updateCircle(this);
7896         },
7897
7898         _empty: function () {
7899                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7900         },
7901
7902         // Needed by the `Canvas` renderer for interactivity
7903         _containsPoint: function (p) {
7904                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7905         }
7906 });
7907
7908
7909 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7910 // Instantiates a circle marker object given a geographical point, and an optional options object.
7911 function circleMarker(latlng, options) {
7912         return new CircleMarker(latlng, options);
7913 }
7914
7915 /*
7916  * @class Circle
7917  * @aka L.Circle
7918  * @inherits CircleMarker
7919  *
7920  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7921  *
7922  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7923  *
7924  * @example
7925  *
7926  * ```js
7927  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7928  * ```
7929  */
7930
7931 var Circle = CircleMarker.extend({
7932
7933         initialize: function (latlng, options, legacyOptions) {
7934                 if (typeof options === 'number') {
7935                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7936                         options = extend({}, legacyOptions, {radius: options});
7937                 }
7938                 setOptions(this, options);
7939                 this._latlng = toLatLng(latlng);
7940
7941                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7942
7943                 // @section
7944                 // @aka Circle options
7945                 // @option radius: Number; Radius of the circle, in meters.
7946                 this._mRadius = this.options.radius;
7947         },
7948
7949         // @method setRadius(radius: Number): this
7950         // Sets the radius of a circle. Units are in meters.
7951         setRadius: function (radius) {
7952                 this._mRadius = radius;
7953                 return this.redraw();
7954         },
7955
7956         // @method getRadius(): Number
7957         // Returns the current radius of a circle. Units are in meters.
7958         getRadius: function () {
7959                 return this._mRadius;
7960         },
7961
7962         // @method getBounds(): LatLngBounds
7963         // Returns the `LatLngBounds` of the path.
7964         getBounds: function () {
7965                 var half = [this._radius, this._radiusY || this._radius];
7966
7967                 return new LatLngBounds(
7968                         this._map.layerPointToLatLng(this._point.subtract(half)),
7969                         this._map.layerPointToLatLng(this._point.add(half)));
7970         },
7971
7972         setStyle: Path.prototype.setStyle,
7973
7974         _project: function () {
7975
7976                 var lng = this._latlng.lng,
7977                     lat = this._latlng.lat,
7978                     map = this._map,
7979                     crs = map.options.crs;
7980
7981                 if (crs.distance === Earth.distance) {
7982                         var d = Math.PI / 180,
7983                             latR = (this._mRadius / Earth.R) / d,
7984                             top = map.project([lat + latR, lng]),
7985                             bottom = map.project([lat - latR, lng]),
7986                             p = top.add(bottom).divideBy(2),
7987                             lat2 = map.unproject(p).lat,
7988                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7989                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7990
7991                         if (isNaN(lngR) || lngR === 0) {
7992                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7993                         }
7994
7995                         this._point = p.subtract(map.getPixelOrigin());
7996                         this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
7997                         this._radiusY = p.y - top.y;
7998
7999                 } else {
8000                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8001
8002                         this._point = map.latLngToLayerPoint(this._latlng);
8003                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8004                 }
8005
8006                 this._updateBounds();
8007         }
8008 });
8009
8010 // @factory L.circle(latlng: LatLng, options?: Circle options)
8011 // Instantiates a circle object given a geographical point, and an options object
8012 // which contains the circle radius.
8013 // @alternative
8014 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8015 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8016 // Do not use in new applications or plugins.
8017 function circle(latlng, options, legacyOptions) {
8018         return new Circle(latlng, options, legacyOptions);
8019 }
8020
8021 /*
8022  * @class Polyline
8023  * @aka L.Polyline
8024  * @inherits Path
8025  *
8026  * A class for drawing polyline overlays on a map. Extends `Path`.
8027  *
8028  * @example
8029  *
8030  * ```js
8031  * // create a red polyline from an array of LatLng points
8032  * var latlngs = [
8033  *      [45.51, -122.68],
8034  *      [37.77, -122.43],
8035  *      [34.04, -118.2]
8036  * ];
8037  *
8038  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8039  *
8040  * // zoom the map to the polyline
8041  * map.fitBounds(polyline.getBounds());
8042  * ```
8043  *
8044  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8045  *
8046  * ```js
8047  * // create a red polyline from an array of arrays of LatLng points
8048  * var latlngs = [
8049  *      [[45.51, -122.68],
8050  *       [37.77, -122.43],
8051  *       [34.04, -118.2]],
8052  *      [[40.78, -73.91],
8053  *       [41.83, -87.62],
8054  *       [32.76, -96.72]]
8055  * ];
8056  * ```
8057  */
8058
8059
8060 var Polyline = Path.extend({
8061
8062         // @section
8063         // @aka Polyline options
8064         options: {
8065                 // @option smoothFactor: Number = 1.0
8066                 // How much to simplify the polyline on each zoom level. More means
8067                 // better performance and smoother look, and less means more accurate representation.
8068                 smoothFactor: 1.0,
8069
8070                 // @option noClip: Boolean = false
8071                 // Disable polyline clipping.
8072                 noClip: false
8073         },
8074
8075         initialize: function (latlngs, options) {
8076                 setOptions(this, options);
8077                 this._setLatLngs(latlngs);
8078         },
8079
8080         // @method getLatLngs(): LatLng[]
8081         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8082         getLatLngs: function () {
8083                 return this._latlngs;
8084         },
8085
8086         // @method setLatLngs(latlngs: LatLng[]): this
8087         // Replaces all the points in the polyline with the given array of geographical points.
8088         setLatLngs: function (latlngs) {
8089                 this._setLatLngs(latlngs);
8090                 return this.redraw();
8091         },
8092
8093         // @method isEmpty(): Boolean
8094         // Returns `true` if the Polyline has no LatLngs.
8095         isEmpty: function () {
8096                 return !this._latlngs.length;
8097         },
8098
8099         // @method closestLayerPoint(p: Point): Point
8100         // Returns the point closest to `p` on the Polyline.
8101         closestLayerPoint: function (p) {
8102                 var minDistance = Infinity,
8103                     minPoint = null,
8104                     closest = _sqClosestPointOnSegment,
8105                     p1, p2;
8106
8107                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8108                         var points = this._parts[j];
8109
8110                         for (var i = 1, len = points.length; i < len; i++) {
8111                                 p1 = points[i - 1];
8112                                 p2 = points[i];
8113
8114                                 var sqDist = closest(p, p1, p2, true);
8115
8116                                 if (sqDist < minDistance) {
8117                                         minDistance = sqDist;
8118                                         minPoint = closest(p, p1, p2);
8119                                 }
8120                         }
8121                 }
8122                 if (minPoint) {
8123                         minPoint.distance = Math.sqrt(minDistance);
8124                 }
8125                 return minPoint;
8126         },
8127
8128         // @method getCenter(): LatLng
8129         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8130         getCenter: function () {
8131                 // throws error when not yet added to map as this center calculation requires projected coordinates
8132                 if (!this._map) {
8133                         throw new Error('Must add layer to map before using getCenter()');
8134                 }
8135
8136                 var i, halfDist, segDist, dist, p1, p2, ratio,
8137                     points = this._rings[0],
8138                     len = points.length;
8139
8140                 if (!len) { return null; }
8141
8142                 // polyline centroid algorithm; only uses the first ring if there are multiple
8143
8144                 for (i = 0, halfDist = 0; i < len - 1; i++) {
8145                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
8146                 }
8147
8148                 // The line is so small in the current view that all points are on the same pixel.
8149                 if (halfDist === 0) {
8150                         return this._map.layerPointToLatLng(points[0]);
8151                 }
8152
8153                 for (i = 0, dist = 0; i < len - 1; i++) {
8154                         p1 = points[i];
8155                         p2 = points[i + 1];
8156                         segDist = p1.distanceTo(p2);
8157                         dist += segDist;
8158
8159                         if (dist > halfDist) {
8160                                 ratio = (dist - halfDist) / segDist;
8161                                 return this._map.layerPointToLatLng([
8162                                         p2.x - ratio * (p2.x - p1.x),
8163                                         p2.y - ratio * (p2.y - p1.y)
8164                                 ]);
8165                         }
8166                 }
8167         },
8168
8169         // @method getBounds(): LatLngBounds
8170         // Returns the `LatLngBounds` of the path.
8171         getBounds: function () {
8172                 return this._bounds;
8173         },
8174
8175         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8176         // Adds a given point to the polyline. By default, adds to the first ring of
8177         // the polyline in case of a multi-polyline, but can be overridden by passing
8178         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8179         addLatLng: function (latlng, latlngs) {
8180                 latlngs = latlngs || this._defaultShape();
8181                 latlng = toLatLng(latlng);
8182                 latlngs.push(latlng);
8183                 this._bounds.extend(latlng);
8184                 return this.redraw();
8185         },
8186
8187         _setLatLngs: function (latlngs) {
8188                 this._bounds = new LatLngBounds();
8189                 this._latlngs = this._convertLatLngs(latlngs);
8190         },
8191
8192         _defaultShape: function () {
8193                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8194         },
8195
8196         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8197         _convertLatLngs: function (latlngs) {
8198                 var result = [],
8199                     flat = isFlat(latlngs);
8200
8201                 for (var i = 0, len = latlngs.length; i < len; i++) {
8202                         if (flat) {
8203                                 result[i] = toLatLng(latlngs[i]);
8204                                 this._bounds.extend(result[i]);
8205                         } else {
8206                                 result[i] = this._convertLatLngs(latlngs[i]);
8207                         }
8208                 }
8209
8210                 return result;
8211         },
8212
8213         _project: function () {
8214                 var pxBounds = new Bounds();
8215                 this._rings = [];
8216                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8217
8218                 var w = this._clickTolerance(),
8219                     p = new Point(w, w);
8220
8221                 if (this._bounds.isValid() && pxBounds.isValid()) {
8222                         pxBounds.min._subtract(p);
8223                         pxBounds.max._add(p);
8224                         this._pxBounds = pxBounds;
8225                 }
8226         },
8227
8228         // recursively turns latlngs into a set of rings with projected coordinates
8229         _projectLatlngs: function (latlngs, result, projectedBounds) {
8230                 var flat = latlngs[0] instanceof LatLng,
8231                     len = latlngs.length,
8232                     i, ring;
8233
8234                 if (flat) {
8235                         ring = [];
8236                         for (i = 0; i < len; i++) {
8237                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8238                                 projectedBounds.extend(ring[i]);
8239                         }
8240                         result.push(ring);
8241                 } else {
8242                         for (i = 0; i < len; i++) {
8243                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8244                         }
8245                 }
8246         },
8247
8248         // clip polyline by renderer bounds so that we have less to render for performance
8249         _clipPoints: function () {
8250                 var bounds = this._renderer._bounds;
8251
8252                 this._parts = [];
8253                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8254                         return;
8255                 }
8256
8257                 if (this.options.noClip) {
8258                         this._parts = this._rings;
8259                         return;
8260                 }
8261
8262                 var parts = this._parts,
8263                     i, j, k, len, len2, segment, points;
8264
8265                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8266                         points = this._rings[i];
8267
8268                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8269                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8270
8271                                 if (!segment) { continue; }
8272
8273                                 parts[k] = parts[k] || [];
8274                                 parts[k].push(segment[0]);
8275
8276                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8277                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8278                                         parts[k].push(segment[1]);
8279                                         k++;
8280                                 }
8281                         }
8282                 }
8283         },
8284
8285         // simplify each clipped part of the polyline for performance
8286         _simplifyPoints: function () {
8287                 var parts = this._parts,
8288                     tolerance = this.options.smoothFactor;
8289
8290                 for (var i = 0, len = parts.length; i < len; i++) {
8291                         parts[i] = simplify(parts[i], tolerance);
8292                 }
8293         },
8294
8295         _update: function () {
8296                 if (!this._map) { return; }
8297
8298                 this._clipPoints();
8299                 this._simplifyPoints();
8300                 this._updatePath();
8301         },
8302
8303         _updatePath: function () {
8304                 this._renderer._updatePoly(this);
8305         },
8306
8307         // Needed by the `Canvas` renderer for interactivity
8308         _containsPoint: function (p, closed) {
8309                 var i, j, k, len, len2, part,
8310                     w = this._clickTolerance();
8311
8312                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8313
8314                 // hit detection for polylines
8315                 for (i = 0, len = this._parts.length; i < len; i++) {
8316                         part = this._parts[i];
8317
8318                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8319                                 if (!closed && (j === 0)) { continue; }
8320
8321                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8322                                         return true;
8323                                 }
8324                         }
8325                 }
8326                 return false;
8327         }
8328 });
8329
8330 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8331 // Instantiates a polyline object given an array of geographical points and
8332 // optionally an options object. You can create a `Polyline` object with
8333 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8334 // of geographic points.
8335 function polyline(latlngs, options) {
8336         return new Polyline(latlngs, options);
8337 }
8338
8339 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8340 Polyline._flat = _flat;
8341
8342 /*
8343  * @class Polygon
8344  * @aka L.Polygon
8345  * @inherits Polyline
8346  *
8347  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8348  *
8349  * 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.
8350  *
8351  *
8352  * @example
8353  *
8354  * ```js
8355  * // create a red polygon from an array of LatLng points
8356  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8357  *
8358  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8359  *
8360  * // zoom the map to the polygon
8361  * map.fitBounds(polygon.getBounds());
8362  * ```
8363  *
8364  * 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:
8365  *
8366  * ```js
8367  * var latlngs = [
8368  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8369  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8370  * ];
8371  * ```
8372  *
8373  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8374  *
8375  * ```js
8376  * var latlngs = [
8377  *   [ // first polygon
8378  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8379  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8380  *   ],
8381  *   [ // second polygon
8382  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8383  *   ]
8384  * ];
8385  * ```
8386  */
8387
8388 var Polygon = Polyline.extend({
8389
8390         options: {
8391                 fill: true
8392         },
8393
8394         isEmpty: function () {
8395                 return !this._latlngs.length || !this._latlngs[0].length;
8396         },
8397
8398         getCenter: function () {
8399                 // throws error when not yet added to map as this center calculation requires projected coordinates
8400                 if (!this._map) {
8401                         throw new Error('Must add layer to map before using getCenter()');
8402                 }
8403
8404                 var i, j, p1, p2, f, area, x, y, center,
8405                     points = this._rings[0],
8406                     len = points.length;
8407
8408                 if (!len) { return null; }
8409
8410                 // polygon centroid algorithm; only uses the first ring if there are multiple
8411
8412                 area = x = y = 0;
8413
8414                 for (i = 0, j = len - 1; i < len; j = i++) {
8415                         p1 = points[i];
8416                         p2 = points[j];
8417
8418                         f = p1.y * p2.x - p2.y * p1.x;
8419                         x += (p1.x + p2.x) * f;
8420                         y += (p1.y + p2.y) * f;
8421                         area += f * 3;
8422                 }
8423
8424                 if (area === 0) {
8425                         // Polygon is so small that all points are on same pixel.
8426                         center = points[0];
8427                 } else {
8428                         center = [x / area, y / area];
8429                 }
8430                 return this._map.layerPointToLatLng(center);
8431         },
8432
8433         _convertLatLngs: function (latlngs) {
8434                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8435                     len = result.length;
8436
8437                 // remove last point if it equals first one
8438                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8439                         result.pop();
8440                 }
8441                 return result;
8442         },
8443
8444         _setLatLngs: function (latlngs) {
8445                 Polyline.prototype._setLatLngs.call(this, latlngs);
8446                 if (isFlat(this._latlngs)) {
8447                         this._latlngs = [this._latlngs];
8448                 }
8449         },
8450
8451         _defaultShape: function () {
8452                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8453         },
8454
8455         _clipPoints: function () {
8456                 // polygons need a different clipping algorithm so we redefine that
8457
8458                 var bounds = this._renderer._bounds,
8459                     w = this.options.weight,
8460                     p = new Point(w, w);
8461
8462                 // increase clip padding by stroke width to avoid stroke on clip edges
8463                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8464
8465                 this._parts = [];
8466                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8467                         return;
8468                 }
8469
8470                 if (this.options.noClip) {
8471                         this._parts = this._rings;
8472                         return;
8473                 }
8474
8475                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8476                         clipped = clipPolygon(this._rings[i], bounds, true);
8477                         if (clipped.length) {
8478                                 this._parts.push(clipped);
8479                         }
8480                 }
8481         },
8482
8483         _updatePath: function () {
8484                 this._renderer._updatePoly(this, true);
8485         },
8486
8487         // Needed by the `Canvas` renderer for interactivity
8488         _containsPoint: function (p) {
8489                 var inside = false,
8490                     part, p1, p2, i, j, k, len, len2;
8491
8492                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8493
8494                 // ray casting algorithm for detecting if point is in polygon
8495                 for (i = 0, len = this._parts.length; i < len; i++) {
8496                         part = this._parts[i];
8497
8498                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8499                                 p1 = part[j];
8500                                 p2 = part[k];
8501
8502                                 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)) {
8503                                         inside = !inside;
8504                                 }
8505                         }
8506                 }
8507
8508                 // also check if it's on polygon stroke
8509                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8510         }
8511
8512 });
8513
8514
8515 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8516 function polygon(latlngs, options) {
8517         return new Polygon(latlngs, options);
8518 }
8519
8520 /*
8521  * @class GeoJSON
8522  * @aka L.GeoJSON
8523  * @inherits FeatureGroup
8524  *
8525  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8526  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8527  *
8528  * @example
8529  *
8530  * ```js
8531  * L.geoJSON(data, {
8532  *      style: function (feature) {
8533  *              return {color: feature.properties.color};
8534  *      }
8535  * }).bindPopup(function (layer) {
8536  *      return layer.feature.properties.description;
8537  * }).addTo(map);
8538  * ```
8539  */
8540
8541 var GeoJSON = FeatureGroup.extend({
8542
8543         /* @section
8544          * @aka GeoJSON options
8545          *
8546          * @option pointToLayer: Function = *
8547          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8548          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8549          * The default is to spawn a default `Marker`:
8550          * ```js
8551          * function(geoJsonPoint, latlng) {
8552          *      return L.marker(latlng);
8553          * }
8554          * ```
8555          *
8556          * @option style: Function = *
8557          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8558          * called internally when data is added.
8559          * The default value is to not override any defaults:
8560          * ```js
8561          * function (geoJsonFeature) {
8562          *      return {}
8563          * }
8564          * ```
8565          *
8566          * @option onEachFeature: Function = *
8567          * A `Function` that will be called once for each created `Feature`, after it has
8568          * been created and styled. Useful for attaching events and popups to features.
8569          * The default is to do nothing with the newly created layers:
8570          * ```js
8571          * function (feature, layer) {}
8572          * ```
8573          *
8574          * @option filter: Function = *
8575          * A `Function` that will be used to decide whether to include a feature or not.
8576          * The default is to include all features:
8577          * ```js
8578          * function (geoJsonFeature) {
8579          *      return true;
8580          * }
8581          * ```
8582          * Note: dynamically changing the `filter` option will have effect only on newly
8583          * added data. It will _not_ re-evaluate already included features.
8584          *
8585          * @option coordsToLatLng: Function = *
8586          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8587          * The default is the `coordsToLatLng` static method.
8588          */
8589
8590         initialize: function (geojson, options) {
8591                 setOptions(this, options);
8592
8593                 this._layers = {};
8594
8595                 if (geojson) {
8596                         this.addData(geojson);
8597                 }
8598         },
8599
8600         // @method addData( <GeoJSON> data ): this
8601         // Adds a GeoJSON object to the layer.
8602         addData: function (geojson) {
8603                 var features = isArray(geojson) ? geojson : geojson.features,
8604                     i, len, feature;
8605
8606                 if (features) {
8607                         for (i = 0, len = features.length; i < len; i++) {
8608                                 // only add this if geometry or geometries are set and not null
8609                                 feature = features[i];
8610                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8611                                         this.addData(feature);
8612                                 }
8613                         }
8614                         return this;
8615                 }
8616
8617                 var options = this.options;
8618
8619                 if (options.filter && !options.filter(geojson)) { return this; }
8620
8621                 var layer = geometryToLayer(geojson, options);
8622                 if (!layer) {
8623                         return this;
8624                 }
8625                 layer.feature = asFeature(geojson);
8626
8627                 layer.defaultOptions = layer.options;
8628                 this.resetStyle(layer);
8629
8630                 if (options.onEachFeature) {
8631                         options.onEachFeature(geojson, layer);
8632                 }
8633
8634                 return this.addLayer(layer);
8635         },
8636
8637         // @method resetStyle( <Path> layer ): this
8638         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8639         resetStyle: function (layer) {
8640                 // reset any custom styles
8641                 layer.options = extend({}, layer.defaultOptions);
8642                 this._setLayerStyle(layer, this.options.style);
8643                 return this;
8644         },
8645
8646         // @method setStyle( <Function> style ): this
8647         // Changes styles of GeoJSON vector layers with the given style function.
8648         setStyle: function (style) {
8649                 return this.eachLayer(function (layer) {
8650                         this._setLayerStyle(layer, style);
8651                 }, this);
8652         },
8653
8654         _setLayerStyle: function (layer, style) {
8655                 if (typeof style === 'function') {
8656                         style = style(layer.feature);
8657                 }
8658                 if (layer.setStyle) {
8659                         layer.setStyle(style);
8660                 }
8661         }
8662 });
8663
8664 // @section
8665 // There are several static functions which can be called without instantiating L.GeoJSON:
8666
8667 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8668 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8669 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8670 // functions if provided as options.
8671 function geometryToLayer(geojson, options) {
8672
8673         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8674             coords = geometry ? geometry.coordinates : null,
8675             layers = [],
8676             pointToLayer = options && options.pointToLayer,
8677             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8678             latlng, latlngs, i, len;
8679
8680         if (!coords && !geometry) {
8681                 return null;
8682         }
8683
8684         switch (geometry.type) {
8685         case 'Point':
8686                 latlng = _coordsToLatLng(coords);
8687                 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8688
8689         case 'MultiPoint':
8690                 for (i = 0, len = coords.length; i < len; i++) {
8691                         latlng = _coordsToLatLng(coords[i]);
8692                         layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8693                 }
8694                 return new FeatureGroup(layers);
8695
8696         case 'LineString':
8697         case 'MultiLineString':
8698                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8699                 return new Polyline(latlngs, options);
8700
8701         case 'Polygon':
8702         case 'MultiPolygon':
8703                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8704                 return new Polygon(latlngs, options);
8705
8706         case 'GeometryCollection':
8707                 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8708                         var layer = geometryToLayer({
8709                                 geometry: geometry.geometries[i],
8710                                 type: 'Feature',
8711                                 properties: geojson.properties
8712                         }, options);
8713
8714                         if (layer) {
8715                                 layers.push(layer);
8716                         }
8717                 }
8718                 return new FeatureGroup(layers);
8719
8720         default:
8721                 throw new Error('Invalid GeoJSON object.');
8722         }
8723 }
8724
8725 // @function coordsToLatLng(coords: Array): LatLng
8726 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8727 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8728 function coordsToLatLng(coords) {
8729         return new LatLng(coords[1], coords[0], coords[2]);
8730 }
8731
8732 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8733 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8734 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8735 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8736 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8737         var latlngs = [];
8738
8739         for (var i = 0, len = coords.length, latlng; i < len; i++) {
8740                 latlng = levelsDeep ?
8741                         coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8742                         (_coordsToLatLng || coordsToLatLng)(coords[i]);
8743
8744                 latlngs.push(latlng);
8745         }
8746
8747         return latlngs;
8748 }
8749
8750 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8751 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8752 function latLngToCoords(latlng, precision) {
8753         precision = typeof precision === 'number' ? precision : 6;
8754         return latlng.alt !== undefined ?
8755                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8756                 [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8757 }
8758
8759 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8760 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8761 // `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.
8762 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8763         var coords = [];
8764
8765         for (var i = 0, len = latlngs.length; i < len; i++) {
8766                 coords.push(levelsDeep ?
8767                         latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8768                         latLngToCoords(latlngs[i], precision));
8769         }
8770
8771         if (!levelsDeep && closed) {
8772                 coords.push(coords[0]);
8773         }
8774
8775         return coords;
8776 }
8777
8778 function getFeature(layer, newGeometry) {
8779         return layer.feature ?
8780                 extend({}, layer.feature, {geometry: newGeometry}) :
8781                 asFeature(newGeometry);
8782 }
8783
8784 // @function asFeature(geojson: Object): Object
8785 // Normalize GeoJSON geometries/features into GeoJSON features.
8786 function asFeature(geojson) {
8787         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8788                 return geojson;
8789         }
8790
8791         return {
8792                 type: 'Feature',
8793                 properties: {},
8794                 geometry: geojson
8795         };
8796 }
8797
8798 var PointToGeoJSON = {
8799         toGeoJSON: function (precision) {
8800                 return getFeature(this, {
8801                         type: 'Point',
8802                         coordinates: latLngToCoords(this.getLatLng(), precision)
8803                 });
8804         }
8805 };
8806
8807 // @namespace Marker
8808 // @method toGeoJSON(): Object
8809 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8810 Marker.include(PointToGeoJSON);
8811
8812 // @namespace CircleMarker
8813 // @method toGeoJSON(): Object
8814 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8815 Circle.include(PointToGeoJSON);
8816 CircleMarker.include(PointToGeoJSON);
8817
8818
8819 // @namespace Polyline
8820 // @method toGeoJSON(): Object
8821 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8822 Polyline.include({
8823         toGeoJSON: function (precision) {
8824                 var multi = !isFlat(this._latlngs);
8825
8826                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8827
8828                 return getFeature(this, {
8829                         type: (multi ? 'Multi' : '') + 'LineString',
8830                         coordinates: coords
8831                 });
8832         }
8833 });
8834
8835 // @namespace Polygon
8836 // @method toGeoJSON(): Object
8837 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8838 Polygon.include({
8839         toGeoJSON: function (precision) {
8840                 var holes = !isFlat(this._latlngs),
8841                     multi = holes && !isFlat(this._latlngs[0]);
8842
8843                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8844
8845                 if (!holes) {
8846                         coords = [coords];
8847                 }
8848
8849                 return getFeature(this, {
8850                         type: (multi ? 'Multi' : '') + 'Polygon',
8851                         coordinates: coords
8852                 });
8853         }
8854 });
8855
8856
8857 // @namespace LayerGroup
8858 LayerGroup.include({
8859         toMultiPoint: function (precision) {
8860                 var coords = [];
8861
8862                 this.eachLayer(function (layer) {
8863                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8864                 });
8865
8866                 return getFeature(this, {
8867                         type: 'MultiPoint',
8868                         coordinates: coords
8869                 });
8870         },
8871
8872         // @method toGeoJSON(): Object
8873         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8874         toGeoJSON: function (precision) {
8875
8876                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8877
8878                 if (type === 'MultiPoint') {
8879                         return this.toMultiPoint(precision);
8880                 }
8881
8882                 var isGeometryCollection = type === 'GeometryCollection',
8883                     jsons = [];
8884
8885                 this.eachLayer(function (layer) {
8886                         if (layer.toGeoJSON) {
8887                                 var json = layer.toGeoJSON(precision);
8888                                 if (isGeometryCollection) {
8889                                         jsons.push(json.geometry);
8890                                 } else {
8891                                         var feature = asFeature(json);
8892                                         // Squash nested feature collections
8893                                         if (feature.type === 'FeatureCollection') {
8894                                                 jsons.push.apply(jsons, feature.features);
8895                                         } else {
8896                                                 jsons.push(feature);
8897                                         }
8898                                 }
8899                         }
8900                 });
8901
8902                 if (isGeometryCollection) {
8903                         return getFeature(this, {
8904                                 geometries: jsons,
8905                                 type: 'GeometryCollection'
8906                         });
8907                 }
8908
8909                 return {
8910                         type: 'FeatureCollection',
8911                         features: jsons
8912                 };
8913         }
8914 });
8915
8916 // @namespace GeoJSON
8917 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8918 // Creates a GeoJSON layer. Optionally accepts an object in
8919 // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
8920 // (you can alternatively add it later with `addData` method) and an `options` object.
8921 function geoJSON(geojson, options) {
8922         return new GeoJSON(geojson, options);
8923 }
8924
8925 // Backward compatibility.
8926 var geoJson = geoJSON;
8927
8928 /*
8929  * @class ImageOverlay
8930  * @aka L.ImageOverlay
8931  * @inherits Interactive layer
8932  *
8933  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8934  *
8935  * @example
8936  *
8937  * ```js
8938  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8939  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8940  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8941  * ```
8942  */
8943
8944 var ImageOverlay = Layer.extend({
8945
8946         // @section
8947         // @aka ImageOverlay options
8948         options: {
8949                 // @option opacity: Number = 1.0
8950                 // The opacity of the image overlay.
8951                 opacity: 1,
8952
8953                 // @option alt: String = ''
8954                 // Text for the `alt` attribute of the image (useful for accessibility).
8955                 alt: '',
8956
8957                 // @option interactive: Boolean = false
8958                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
8959                 interactive: false,
8960
8961                 // @option crossOrigin: Boolean|String = false
8962                 // Whether the crossOrigin attribute will be added to the image.
8963                 // 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.
8964                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
8965                 crossOrigin: false,
8966
8967                 // @option errorOverlayUrl: String = ''
8968                 // URL to the overlay image to show in place of the overlay that failed to load.
8969                 errorOverlayUrl: '',
8970
8971                 // @option zIndex: Number = 1
8972                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
8973                 zIndex: 1,
8974
8975                 // @option className: String = ''
8976                 // A custom class name to assign to the image. Empty by default.
8977                 className: ''
8978         },
8979
8980         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
8981                 this._url = url;
8982                 this._bounds = toLatLngBounds(bounds);
8983
8984                 setOptions(this, options);
8985         },
8986
8987         onAdd: function () {
8988                 if (!this._image) {
8989                         this._initImage();
8990
8991                         if (this.options.opacity < 1) {
8992                                 this._updateOpacity();
8993                         }
8994                 }
8995
8996                 if (this.options.interactive) {
8997                         addClass(this._image, 'leaflet-interactive');
8998                         this.addInteractiveTarget(this._image);
8999                 }
9000
9001                 this.getPane().appendChild(this._image);
9002                 this._reset();
9003         },
9004
9005         onRemove: function () {
9006                 remove(this._image);
9007                 if (this.options.interactive) {
9008                         this.removeInteractiveTarget(this._image);
9009                 }
9010         },
9011
9012         // @method setOpacity(opacity: Number): this
9013         // Sets the opacity of the overlay.
9014         setOpacity: function (opacity) {
9015                 this.options.opacity = opacity;
9016
9017                 if (this._image) {
9018                         this._updateOpacity();
9019                 }
9020                 return this;
9021         },
9022
9023         setStyle: function (styleOpts) {
9024                 if (styleOpts.opacity) {
9025                         this.setOpacity(styleOpts.opacity);
9026                 }
9027                 return this;
9028         },
9029
9030         // @method bringToFront(): this
9031         // Brings the layer to the top of all overlays.
9032         bringToFront: function () {
9033                 if (this._map) {
9034                         toFront(this._image);
9035                 }
9036                 return this;
9037         },
9038
9039         // @method bringToBack(): this
9040         // Brings the layer to the bottom of all overlays.
9041         bringToBack: function () {
9042                 if (this._map) {
9043                         toBack(this._image);
9044                 }
9045                 return this;
9046         },
9047
9048         // @method setUrl(url: String): this
9049         // Changes the URL of the image.
9050         setUrl: function (url) {
9051                 this._url = url;
9052
9053                 if (this._image) {
9054                         this._image.src = url;
9055                 }
9056                 return this;
9057         },
9058
9059         // @method setBounds(bounds: LatLngBounds): this
9060         // Update the bounds that this ImageOverlay covers
9061         setBounds: function (bounds) {
9062                 this._bounds = toLatLngBounds(bounds);
9063
9064                 if (this._map) {
9065                         this._reset();
9066                 }
9067                 return this;
9068         },
9069
9070         getEvents: function () {
9071                 var events = {
9072                         zoom: this._reset,
9073                         viewreset: this._reset
9074                 };
9075
9076                 if (this._zoomAnimated) {
9077                         events.zoomanim = this._animateZoom;
9078                 }
9079
9080                 return events;
9081         },
9082
9083         // @method setZIndex(value: Number): this
9084         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
9085         setZIndex: function (value) {
9086                 this.options.zIndex = value;
9087                 this._updateZIndex();
9088                 return this;
9089         },
9090
9091         // @method getBounds(): LatLngBounds
9092         // Get the bounds that this ImageOverlay covers
9093         getBounds: function () {
9094                 return this._bounds;
9095         },
9096
9097         // @method getElement(): HTMLElement
9098         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
9099         // used by this overlay.
9100         getElement: function () {
9101                 return this._image;
9102         },
9103
9104         _initImage: function () {
9105                 var wasElementSupplied = this._url.tagName === 'IMG';
9106                 var img = this._image = wasElementSupplied ? this._url : create$1('img');
9107
9108                 addClass(img, 'leaflet-image-layer');
9109                 if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
9110                 if (this.options.className) { addClass(img, this.options.className); }
9111
9112                 img.onselectstart = falseFn;
9113                 img.onmousemove = falseFn;
9114
9115                 // @event load: Event
9116                 // Fired when the ImageOverlay layer has loaded its image
9117                 img.onload = bind(this.fire, this, 'load');
9118                 img.onerror = bind(this._overlayOnError, this, 'error');
9119
9120                 if (this.options.crossOrigin || this.options.crossOrigin === '') {
9121                         img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
9122                 }
9123
9124                 if (this.options.zIndex) {
9125                         this._updateZIndex();
9126                 }
9127
9128                 if (wasElementSupplied) {
9129                         this._url = img.src;
9130                         return;
9131                 }
9132
9133                 img.src = this._url;
9134                 img.alt = this.options.alt;
9135         },
9136
9137         _animateZoom: function (e) {
9138                 var scale = this._map.getZoomScale(e.zoom),
9139                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
9140
9141                 setTransform(this._image, offset, scale);
9142         },
9143
9144         _reset: function () {
9145                 var image = this._image,
9146                     bounds = new Bounds(
9147                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
9148                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
9149                     size = bounds.getSize();
9150
9151                 setPosition(image, bounds.min);
9152
9153                 image.style.width  = size.x + 'px';
9154                 image.style.height = size.y + 'px';
9155         },
9156
9157         _updateOpacity: function () {
9158                 setOpacity(this._image, this.options.opacity);
9159         },
9160
9161         _updateZIndex: function () {
9162                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
9163                         this._image.style.zIndex = this.options.zIndex;
9164                 }
9165         },
9166
9167         _overlayOnError: function () {
9168                 // @event error: Event
9169                 // Fired when the ImageOverlay layer fails to load its image
9170                 this.fire('error');
9171
9172                 var errorUrl = this.options.errorOverlayUrl;
9173                 if (errorUrl && this._url !== errorUrl) {
9174                         this._url = errorUrl;
9175                         this._image.src = errorUrl;
9176                 }
9177         }
9178 });
9179
9180 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
9181 // Instantiates an image overlay object given the URL of the image and the
9182 // geographical bounds it is tied to.
9183 var imageOverlay = function (url, bounds, options) {
9184         return new ImageOverlay(url, bounds, options);
9185 };
9186
9187 /*
9188  * @class VideoOverlay
9189  * @aka L.VideoOverlay
9190  * @inherits ImageOverlay
9191  *
9192  * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
9193  *
9194  * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
9195  * HTML5 element.
9196  *
9197  * @example
9198  *
9199  * ```js
9200  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
9201  *      videoBounds = [[ 32, -130], [ 13, -100]];
9202  * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
9203  * ```
9204  */
9205
9206 var VideoOverlay = ImageOverlay.extend({
9207
9208         // @section
9209         // @aka VideoOverlay options
9210         options: {
9211                 // @option autoplay: Boolean = true
9212                 // Whether the video starts playing automatically when loaded.
9213                 autoplay: true,
9214
9215                 // @option loop: Boolean = true
9216                 // Whether the video will loop back to the beginning when played.
9217                 loop: true
9218         },
9219
9220         _initImage: function () {
9221                 var wasElementSupplied = this._url.tagName === 'VIDEO';
9222                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9223
9224                 addClass(vid, 'leaflet-image-layer');
9225                 if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
9226
9227                 vid.onselectstart = falseFn;
9228                 vid.onmousemove = falseFn;
9229
9230                 // @event load: Event
9231                 // Fired when the video has finished loading the first frame
9232                 vid.onloadeddata = bind(this.fire, this, 'load');
9233
9234                 if (wasElementSupplied) {
9235                         var sourceElements = vid.getElementsByTagName('source');
9236                         var sources = [];
9237                         for (var j = 0; j < sourceElements.length; j++) {
9238                                 sources.push(sourceElements[j].src);
9239                         }
9240
9241                         this._url = (sourceElements.length > 0) ? sources : [vid.src];
9242                         return;
9243                 }
9244
9245                 if (!isArray(this._url)) { this._url = [this._url]; }
9246
9247                 vid.autoplay = !!this.options.autoplay;
9248                 vid.loop = !!this.options.loop;
9249                 for (var i = 0; i < this._url.length; i++) {
9250                         var source = create$1('source');
9251                         source.src = this._url[i];
9252                         vid.appendChild(source);
9253                 }
9254         }
9255
9256         // @method getElement(): HTMLVideoElement
9257         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9258         // used by this overlay.
9259 });
9260
9261
9262 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9263 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9264 // geographical bounds it is tied to.
9265
9266 function videoOverlay(video, bounds, options) {
9267         return new VideoOverlay(video, bounds, options);
9268 }
9269
9270 /*
9271  * @class DivOverlay
9272  * @inherits Layer
9273  * @aka L.DivOverlay
9274  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9275  */
9276
9277 // @namespace DivOverlay
9278 var DivOverlay = Layer.extend({
9279
9280         // @section
9281         // @aka DivOverlay options
9282         options: {
9283                 // @option offset: Point = Point(0, 7)
9284                 // The offset of the popup position. Useful to control the anchor
9285                 // of the popup when opening it on some overlays.
9286                 offset: [0, 7],
9287
9288                 // @option className: String = ''
9289                 // A custom CSS class name to assign to the popup.
9290                 className: '',
9291
9292                 // @option pane: String = 'popupPane'
9293                 // `Map pane` where the popup will be added.
9294                 pane: 'popupPane'
9295         },
9296
9297         initialize: function (options, source) {
9298                 setOptions(this, options);
9299
9300                 this._source = source;
9301         },
9302
9303         onAdd: function (map) {
9304                 this._zoomAnimated = map._zoomAnimated;
9305
9306                 if (!this._container) {
9307                         this._initLayout();
9308                 }
9309
9310                 if (map._fadeAnimated) {
9311                         setOpacity(this._container, 0);
9312                 }
9313
9314                 clearTimeout(this._removeTimeout);
9315                 this.getPane().appendChild(this._container);
9316                 this.update();
9317
9318                 if (map._fadeAnimated) {
9319                         setOpacity(this._container, 1);
9320                 }
9321
9322                 this.bringToFront();
9323         },
9324
9325         onRemove: function (map) {
9326                 if (map._fadeAnimated) {
9327                         setOpacity(this._container, 0);
9328                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9329                 } else {
9330                         remove(this._container);
9331                 }
9332         },
9333
9334         // @namespace Popup
9335         // @method getLatLng: LatLng
9336         // Returns the geographical point of popup.
9337         getLatLng: function () {
9338                 return this._latlng;
9339         },
9340
9341         // @method setLatLng(latlng: LatLng): this
9342         // Sets the geographical point where the popup will open.
9343         setLatLng: function (latlng) {
9344                 this._latlng = toLatLng(latlng);
9345                 if (this._map) {
9346                         this._updatePosition();
9347                         this._adjustPan();
9348                 }
9349                 return this;
9350         },
9351
9352         // @method getContent: String|HTMLElement
9353         // Returns the content of the popup.
9354         getContent: function () {
9355                 return this._content;
9356         },
9357
9358         // @method setContent(htmlContent: String|HTMLElement|Function): this
9359         // 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.
9360         setContent: function (content) {
9361                 this._content = content;
9362                 this.update();
9363                 return this;
9364         },
9365
9366         // @method getElement: String|HTMLElement
9367         // Alias for [getContent()](#popup-getcontent)
9368         getElement: function () {
9369                 return this._container;
9370         },
9371
9372         // @method update: null
9373         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9374         update: function () {
9375                 if (!this._map) { return; }
9376
9377                 this._container.style.visibility = 'hidden';
9378
9379                 this._updateContent();
9380                 this._updateLayout();
9381                 this._updatePosition();
9382
9383                 this._container.style.visibility = '';
9384
9385                 this._adjustPan();
9386         },
9387
9388         getEvents: function () {
9389                 var events = {
9390                         zoom: this._updatePosition,
9391                         viewreset: this._updatePosition
9392                 };
9393
9394                 if (this._zoomAnimated) {
9395                         events.zoomanim = this._animateZoom;
9396                 }
9397                 return events;
9398         },
9399
9400         // @method isOpen: Boolean
9401         // Returns `true` when the popup is visible on the map.
9402         isOpen: function () {
9403                 return !!this._map && this._map.hasLayer(this);
9404         },
9405
9406         // @method bringToFront: this
9407         // Brings this popup in front of other popups (in the same map pane).
9408         bringToFront: function () {
9409                 if (this._map) {
9410                         toFront(this._container);
9411                 }
9412                 return this;
9413         },
9414
9415         // @method bringToBack: this
9416         // Brings this popup to the back of other popups (in the same map pane).
9417         bringToBack: function () {
9418                 if (this._map) {
9419                         toBack(this._container);
9420                 }
9421                 return this;
9422         },
9423
9424         _updateContent: function () {
9425                 if (!this._content) { return; }
9426
9427                 var node = this._contentNode;
9428                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9429
9430                 if (typeof content === 'string') {
9431                         node.innerHTML = content;
9432                 } else {
9433                         while (node.hasChildNodes()) {
9434                                 node.removeChild(node.firstChild);
9435                         }
9436                         node.appendChild(content);
9437                 }
9438                 this.fire('contentupdate');
9439         },
9440
9441         _updatePosition: function () {
9442                 if (!this._map) { return; }
9443
9444                 var pos = this._map.latLngToLayerPoint(this._latlng),
9445                     offset = toPoint(this.options.offset),
9446                     anchor = this._getAnchor();
9447
9448                 if (this._zoomAnimated) {
9449                         setPosition(this._container, pos.add(anchor));
9450                 } else {
9451                         offset = offset.add(pos).add(anchor);
9452                 }
9453
9454                 var bottom = this._containerBottom = -offset.y,
9455                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9456
9457                 // bottom position the popup in case the height of the popup changes (images loading etc)
9458                 this._container.style.bottom = bottom + 'px';
9459                 this._container.style.left = left + 'px';
9460         },
9461
9462         _getAnchor: function () {
9463                 return [0, 0];
9464         }
9465
9466 });
9467
9468 /*
9469  * @class Popup
9470  * @inherits DivOverlay
9471  * @aka L.Popup
9472  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9473  * open popups while making sure that only one popup is open at one time
9474  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9475  *
9476  * @example
9477  *
9478  * If you want to just bind a popup to marker click and then open it, it's really easy:
9479  *
9480  * ```js
9481  * marker.bindPopup(popupContent).openPopup();
9482  * ```
9483  * Path overlays like polylines also have a `bindPopup` method.
9484  * Here's a more complicated way to open a popup on a map:
9485  *
9486  * ```js
9487  * var popup = L.popup()
9488  *      .setLatLng(latlng)
9489  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9490  *      .openOn(map);
9491  * ```
9492  */
9493
9494
9495 // @namespace Popup
9496 var Popup = DivOverlay.extend({
9497
9498         // @section
9499         // @aka Popup options
9500         options: {
9501                 // @option maxWidth: Number = 300
9502                 // Max width of the popup, in pixels.
9503                 maxWidth: 300,
9504
9505                 // @option minWidth: Number = 50
9506                 // Min width of the popup, in pixels.
9507                 minWidth: 50,
9508
9509                 // @option maxHeight: Number = null
9510                 // If set, creates a scrollable container of the given height
9511                 // inside a popup if its content exceeds it.
9512                 maxHeight: null,
9513
9514                 // @option autoPan: Boolean = true
9515                 // Set it to `false` if you don't want the map to do panning animation
9516                 // to fit the opened popup.
9517                 autoPan: true,
9518
9519                 // @option autoPanPaddingTopLeft: Point = null
9520                 // The margin between the popup and the top left corner of the map
9521                 // view after autopanning was performed.
9522                 autoPanPaddingTopLeft: null,
9523
9524                 // @option autoPanPaddingBottomRight: Point = null
9525                 // The margin between the popup and the bottom right corner of the map
9526                 // view after autopanning was performed.
9527                 autoPanPaddingBottomRight: null,
9528
9529                 // @option autoPanPadding: Point = Point(5, 5)
9530                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9531                 autoPanPadding: [5, 5],
9532
9533                 // @option keepInView: Boolean = false
9534                 // Set it to `true` if you want to prevent users from panning the popup
9535                 // off of the screen while it is open.
9536                 keepInView: false,
9537
9538                 // @option closeButton: Boolean = true
9539                 // Controls the presence of a close button in the popup.
9540                 closeButton: true,
9541
9542                 // @option autoClose: Boolean = true
9543                 // Set it to `false` if you want to override the default behavior of
9544                 // the popup closing when another popup is opened.
9545                 autoClose: true,
9546
9547                 // @option closeOnEscapeKey: Boolean = true
9548                 // Set it to `false` if you want to override the default behavior of
9549                 // the ESC key for closing of the popup.
9550                 closeOnEscapeKey: true,
9551
9552                 // @option closeOnClick: Boolean = *
9553                 // Set it if you want to override the default behavior of the popup closing when user clicks
9554                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9555
9556                 // @option className: String = ''
9557                 // A custom CSS class name to assign to the popup.
9558                 className: ''
9559         },
9560
9561         // @namespace Popup
9562         // @method openOn(map: Map): this
9563         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9564         openOn: function (map) {
9565                 map.openPopup(this);
9566                 return this;
9567         },
9568
9569         onAdd: function (map) {
9570                 DivOverlay.prototype.onAdd.call(this, map);
9571
9572                 // @namespace Map
9573                 // @section Popup events
9574                 // @event popupopen: PopupEvent
9575                 // Fired when a popup is opened in the map
9576                 map.fire('popupopen', {popup: this});
9577
9578                 if (this._source) {
9579                         // @namespace Layer
9580                         // @section Popup events
9581                         // @event popupopen: PopupEvent
9582                         // Fired when a popup bound to this layer is opened
9583                         this._source.fire('popupopen', {popup: this}, true);
9584                         // For non-path layers, we toggle the popup when clicking
9585                         // again the layer, so prevent the map to reopen it.
9586                         if (!(this._source instanceof Path)) {
9587                                 this._source.on('preclick', stopPropagation);
9588                         }
9589                 }
9590         },
9591
9592         onRemove: function (map) {
9593                 DivOverlay.prototype.onRemove.call(this, map);
9594
9595                 // @namespace Map
9596                 // @section Popup events
9597                 // @event popupclose: PopupEvent
9598                 // Fired when a popup in the map is closed
9599                 map.fire('popupclose', {popup: this});
9600
9601                 if (this._source) {
9602                         // @namespace Layer
9603                         // @section Popup events
9604                         // @event popupclose: PopupEvent
9605                         // Fired when a popup bound to this layer is closed
9606                         this._source.fire('popupclose', {popup: this}, true);
9607                         if (!(this._source instanceof Path)) {
9608                                 this._source.off('preclick', stopPropagation);
9609                         }
9610                 }
9611         },
9612
9613         getEvents: function () {
9614                 var events = DivOverlay.prototype.getEvents.call(this);
9615
9616                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9617                         events.preclick = this._close;
9618                 }
9619
9620                 if (this.options.keepInView) {
9621                         events.moveend = this._adjustPan;
9622                 }
9623
9624                 return events;
9625         },
9626
9627         _close: function () {
9628                 if (this._map) {
9629                         this._map.closePopup(this);
9630                 }
9631         },
9632
9633         _initLayout: function () {
9634                 var prefix = 'leaflet-popup',
9635                     container = this._container = create$1('div',
9636                         prefix + ' ' + (this.options.className || '') +
9637                         ' leaflet-zoom-animated');
9638
9639                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9640                 this._contentNode = create$1('div', prefix + '-content', wrapper);
9641
9642                 disableClickPropagation(wrapper);
9643                 disableScrollPropagation(this._contentNode);
9644                 on(wrapper, 'contextmenu', stopPropagation);
9645
9646                 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9647                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9648
9649                 if (this.options.closeButton) {
9650                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9651                         closeButton.href = '#close';
9652                         closeButton.innerHTML = '&#215;';
9653
9654                         on(closeButton, 'click', this._onCloseButtonClick, this);
9655                 }
9656         },
9657
9658         _updateLayout: function () {
9659                 var container = this._contentNode,
9660                     style = container.style;
9661
9662                 style.width = '';
9663                 style.whiteSpace = 'nowrap';
9664
9665                 var width = container.offsetWidth;
9666                 width = Math.min(width, this.options.maxWidth);
9667                 width = Math.max(width, this.options.minWidth);
9668
9669                 style.width = (width + 1) + 'px';
9670                 style.whiteSpace = '';
9671
9672                 style.height = '';
9673
9674                 var height = container.offsetHeight,
9675                     maxHeight = this.options.maxHeight,
9676                     scrolledClass = 'leaflet-popup-scrolled';
9677
9678                 if (maxHeight && height > maxHeight) {
9679                         style.height = maxHeight + 'px';
9680                         addClass(container, scrolledClass);
9681                 } else {
9682                         removeClass(container, scrolledClass);
9683                 }
9684
9685                 this._containerWidth = this._container.offsetWidth;
9686         },
9687
9688         _animateZoom: function (e) {
9689                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9690                     anchor = this._getAnchor();
9691                 setPosition(this._container, pos.add(anchor));
9692         },
9693
9694         _adjustPan: function () {
9695                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
9696
9697                 var map = this._map,
9698                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9699                     containerHeight = this._container.offsetHeight + marginBottom,
9700                     containerWidth = this._containerWidth,
9701                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9702
9703                 layerPos._add(getPosition(this._container));
9704
9705                 var containerPos = map.layerPointToContainerPoint(layerPos),
9706                     padding = toPoint(this.options.autoPanPadding),
9707                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9708                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9709                     size = map.getSize(),
9710                     dx = 0,
9711                     dy = 0;
9712
9713                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9714                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9715                 }
9716                 if (containerPos.x - dx - paddingTL.x < 0) { // left
9717                         dx = containerPos.x - paddingTL.x;
9718                 }
9719                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9720                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9721                 }
9722                 if (containerPos.y - dy - paddingTL.y < 0) { // top
9723                         dy = containerPos.y - paddingTL.y;
9724                 }
9725
9726                 // @namespace Map
9727                 // @section Popup events
9728                 // @event autopanstart: Event
9729                 // Fired when the map starts autopanning when opening a popup.
9730                 if (dx || dy) {
9731                         map
9732                             .fire('autopanstart')
9733                             .panBy([dx, dy]);
9734                 }
9735         },
9736
9737         _onCloseButtonClick: function (e) {
9738                 this._close();
9739                 stop(e);
9740         },
9741
9742         _getAnchor: function () {
9743                 // Where should we anchor the popup on the source layer?
9744                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9745         }
9746
9747 });
9748
9749 // @namespace Popup
9750 // @factory L.popup(options?: Popup options, source?: Layer)
9751 // 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.
9752 var popup = function (options, source) {
9753         return new Popup(options, source);
9754 };
9755
9756
9757 /* @namespace Map
9758  * @section Interaction Options
9759  * @option closePopupOnClick: Boolean = true
9760  * Set it to `false` if you don't want popups to close when user clicks the map.
9761  */
9762 Map.mergeOptions({
9763         closePopupOnClick: true
9764 });
9765
9766
9767 // @namespace Map
9768 // @section Methods for Layers and Controls
9769 Map.include({
9770         // @method openPopup(popup: Popup): this
9771         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9772         // @alternative
9773         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9774         // Creates a popup with the specified content and options and opens it in the given point on a map.
9775         openPopup: function (popup, latlng, options) {
9776                 if (!(popup instanceof Popup)) {
9777                         popup = new Popup(options).setContent(popup);
9778                 }
9779
9780                 if (latlng) {
9781                         popup.setLatLng(latlng);
9782                 }
9783
9784                 if (this.hasLayer(popup)) {
9785                         return this;
9786                 }
9787
9788                 if (this._popup && this._popup.options.autoClose) {
9789                         this.closePopup();
9790                 }
9791
9792                 this._popup = popup;
9793                 return this.addLayer(popup);
9794         },
9795
9796         // @method closePopup(popup?: Popup): this
9797         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9798         closePopup: function (popup) {
9799                 if (!popup || popup === this._popup) {
9800                         popup = this._popup;
9801                         this._popup = null;
9802                 }
9803                 if (popup) {
9804                         this.removeLayer(popup);
9805                 }
9806                 return this;
9807         }
9808 });
9809
9810 /*
9811  * @namespace Layer
9812  * @section Popup methods example
9813  *
9814  * All layers share a set of methods convenient for binding popups to it.
9815  *
9816  * ```js
9817  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9818  * layer.openPopup();
9819  * layer.closePopup();
9820  * ```
9821  *
9822  * 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.
9823  */
9824
9825 // @section Popup methods
9826 Layer.include({
9827
9828         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9829         // Binds a popup to the layer with the passed `content` and sets up the
9830         // necessary event listeners. If a `Function` is passed it will receive
9831         // the layer as the first argument and should return a `String` or `HTMLElement`.
9832         bindPopup: function (content, options) {
9833
9834                 if (content instanceof Popup) {
9835                         setOptions(content, options);
9836                         this._popup = content;
9837                         content._source = this;
9838                 } else {
9839                         if (!this._popup || options) {
9840                                 this._popup = new Popup(options, this);
9841                         }
9842                         this._popup.setContent(content);
9843                 }
9844
9845                 if (!this._popupHandlersAdded) {
9846                         this.on({
9847                                 click: this._openPopup,
9848                                 keypress: this._onKeyPress,
9849                                 remove: this.closePopup,
9850                                 move: this._movePopup
9851                         });
9852                         this._popupHandlersAdded = true;
9853                 }
9854
9855                 return this;
9856         },
9857
9858         // @method unbindPopup(): this
9859         // Removes the popup previously bound with `bindPopup`.
9860         unbindPopup: function () {
9861                 if (this._popup) {
9862                         this.off({
9863                                 click: this._openPopup,
9864                                 keypress: this._onKeyPress,
9865                                 remove: this.closePopup,
9866                                 move: this._movePopup
9867                         });
9868                         this._popupHandlersAdded = false;
9869                         this._popup = null;
9870                 }
9871                 return this;
9872         },
9873
9874         // @method openPopup(latlng?: LatLng): this
9875         // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
9876         openPopup: function (layer, latlng) {
9877                 if (!(layer instanceof Layer)) {
9878                         latlng = layer;
9879                         layer = this;
9880                 }
9881
9882                 if (layer instanceof FeatureGroup) {
9883                         for (var id in this._layers) {
9884                                 layer = this._layers[id];
9885                                 break;
9886                         }
9887                 }
9888
9889                 if (!latlng) {
9890                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9891                 }
9892
9893                 if (this._popup && this._map) {
9894                         // set popup source to this layer
9895                         this._popup._source = layer;
9896
9897                         // update the popup (content, layout, ect...)
9898                         this._popup.update();
9899
9900                         // open the popup on the map
9901                         this._map.openPopup(this._popup, latlng);
9902                 }
9903
9904                 return this;
9905         },
9906
9907         // @method closePopup(): this
9908         // Closes the popup bound to this layer if it is open.
9909         closePopup: function () {
9910                 if (this._popup) {
9911                         this._popup._close();
9912                 }
9913                 return this;
9914         },
9915
9916         // @method togglePopup(): this
9917         // Opens or closes the popup bound to this layer depending on its current state.
9918         togglePopup: function (target) {
9919                 if (this._popup) {
9920                         if (this._popup._map) {
9921                                 this.closePopup();
9922                         } else {
9923                                 this.openPopup(target);
9924                         }
9925                 }
9926                 return this;
9927         },
9928
9929         // @method isPopupOpen(): boolean
9930         // Returns `true` if the popup bound to this layer is currently open.
9931         isPopupOpen: function () {
9932                 return (this._popup ? this._popup.isOpen() : false);
9933         },
9934
9935         // @method setPopupContent(content: String|HTMLElement|Popup): this
9936         // Sets the content of the popup bound to this layer.
9937         setPopupContent: function (content) {
9938                 if (this._popup) {
9939                         this._popup.setContent(content);
9940                 }
9941                 return this;
9942         },
9943
9944         // @method getPopup(): Popup
9945         // Returns the popup bound to this layer.
9946         getPopup: function () {
9947                 return this._popup;
9948         },
9949
9950         _openPopup: function (e) {
9951                 var layer = e.layer || e.target;
9952
9953                 if (!this._popup) {
9954                         return;
9955                 }
9956
9957                 if (!this._map) {
9958                         return;
9959                 }
9960
9961                 // prevent map click
9962                 stop(e);
9963
9964                 // if this inherits from Path its a vector and we can just
9965                 // open the popup at the new location
9966                 if (layer instanceof Path) {
9967                         this.openPopup(e.layer || e.target, e.latlng);
9968                         return;
9969                 }
9970
9971                 // otherwise treat it like a marker and figure out
9972                 // if we should toggle it open/closed
9973                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
9974                         this.closePopup();
9975                 } else {
9976                         this.openPopup(layer, e.latlng);
9977                 }
9978         },
9979
9980         _movePopup: function (e) {
9981                 this._popup.setLatLng(e.latlng);
9982         },
9983
9984         _onKeyPress: function (e) {
9985                 if (e.originalEvent.keyCode === 13) {
9986                         this._openPopup(e);
9987                 }
9988         }
9989 });
9990
9991 /*
9992  * @class Tooltip
9993  * @inherits DivOverlay
9994  * @aka L.Tooltip
9995  * Used to display small texts on top of map layers.
9996  *
9997  * @example
9998  *
9999  * ```js
10000  * marker.bindTooltip("my tooltip text").openTooltip();
10001  * ```
10002  * Note about tooltip offset. Leaflet takes two options in consideration
10003  * for computing tooltip offsetting:
10004  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
10005  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
10006  *   move it to the bottom. Negatives will move to the left and top.
10007  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
10008  *   should adapt this value if you use a custom icon.
10009  */
10010
10011
10012 // @namespace Tooltip
10013 var Tooltip = DivOverlay.extend({
10014
10015         // @section
10016         // @aka Tooltip options
10017         options: {
10018                 // @option pane: String = 'tooltipPane'
10019                 // `Map pane` where the tooltip will be added.
10020                 pane: 'tooltipPane',
10021
10022                 // @option offset: Point = Point(0, 0)
10023                 // Optional offset of the tooltip position.
10024                 offset: [0, 0],
10025
10026                 // @option direction: String = 'auto'
10027                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
10028                 // `top`, `bottom`, `center`, `auto`.
10029                 // `auto` will dynamically switch between `right` and `left` according to the tooltip
10030                 // position on the map.
10031                 direction: 'auto',
10032
10033                 // @option permanent: Boolean = false
10034                 // Whether to open the tooltip permanently or only on mouseover.
10035                 permanent: false,
10036
10037                 // @option sticky: Boolean = false
10038                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
10039                 sticky: false,
10040
10041                 // @option interactive: Boolean = false
10042                 // If true, the tooltip will listen to the feature events.
10043                 interactive: false,
10044
10045                 // @option opacity: Number = 0.9
10046                 // Tooltip container opacity.
10047                 opacity: 0.9
10048         },
10049
10050         onAdd: function (map) {
10051                 DivOverlay.prototype.onAdd.call(this, map);
10052                 this.setOpacity(this.options.opacity);
10053
10054                 // @namespace Map
10055                 // @section Tooltip events
10056                 // @event tooltipopen: TooltipEvent
10057                 // Fired when a tooltip is opened in the map.
10058                 map.fire('tooltipopen', {tooltip: this});
10059
10060                 if (this._source) {
10061                         // @namespace Layer
10062                         // @section Tooltip events
10063                         // @event tooltipopen: TooltipEvent
10064                         // Fired when a tooltip bound to this layer is opened.
10065                         this._source.fire('tooltipopen', {tooltip: this}, true);
10066                 }
10067         },
10068
10069         onRemove: function (map) {
10070                 DivOverlay.prototype.onRemove.call(this, map);
10071
10072                 // @namespace Map
10073                 // @section Tooltip events
10074                 // @event tooltipclose: TooltipEvent
10075                 // Fired when a tooltip in the map is closed.
10076                 map.fire('tooltipclose', {tooltip: this});
10077
10078                 if (this._source) {
10079                         // @namespace Layer
10080                         // @section Tooltip events
10081                         // @event tooltipclose: TooltipEvent
10082                         // Fired when a tooltip bound to this layer is closed.
10083                         this._source.fire('tooltipclose', {tooltip: this}, true);
10084                 }
10085         },
10086
10087         getEvents: function () {
10088                 var events = DivOverlay.prototype.getEvents.call(this);
10089
10090                 if (touch && !this.options.permanent) {
10091                         events.preclick = this._close;
10092                 }
10093
10094                 return events;
10095         },
10096
10097         _close: function () {
10098                 if (this._map) {
10099                         this._map.closeTooltip(this);
10100                 }
10101         },
10102
10103         _initLayout: function () {
10104                 var prefix = 'leaflet-tooltip',
10105                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
10106
10107                 this._contentNode = this._container = create$1('div', className);
10108         },
10109
10110         _updateLayout: function () {},
10111
10112         _adjustPan: function () {},
10113
10114         _setPosition: function (pos) {
10115                 var map = this._map,
10116                     container = this._container,
10117                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
10118                     tooltipPoint = map.layerPointToContainerPoint(pos),
10119                     direction = this.options.direction,
10120                     tooltipWidth = container.offsetWidth,
10121                     tooltipHeight = container.offsetHeight,
10122                     offset = toPoint(this.options.offset),
10123                     anchor = this._getAnchor();
10124
10125                 if (direction === 'top') {
10126                         pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
10127                 } else if (direction === 'bottom') {
10128                         pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
10129                 } else if (direction === 'center') {
10130                         pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
10131                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
10132                         direction = 'right';
10133                         pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
10134                 } else {
10135                         direction = 'left';
10136                         pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
10137                 }
10138
10139                 removeClass(container, 'leaflet-tooltip-right');
10140                 removeClass(container, 'leaflet-tooltip-left');
10141                 removeClass(container, 'leaflet-tooltip-top');
10142                 removeClass(container, 'leaflet-tooltip-bottom');
10143                 addClass(container, 'leaflet-tooltip-' + direction);
10144                 setPosition(container, pos);
10145         },
10146
10147         _updatePosition: function () {
10148                 var pos = this._map.latLngToLayerPoint(this._latlng);
10149                 this._setPosition(pos);
10150         },
10151
10152         setOpacity: function (opacity) {
10153                 this.options.opacity = opacity;
10154
10155                 if (this._container) {
10156                         setOpacity(this._container, opacity);
10157                 }
10158         },
10159
10160         _animateZoom: function (e) {
10161                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
10162                 this._setPosition(pos);
10163         },
10164
10165         _getAnchor: function () {
10166                 // Where should we anchor the tooltip on the source layer?
10167                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
10168         }
10169
10170 });
10171
10172 // @namespace Tooltip
10173 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
10174 // 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.
10175 var tooltip = function (options, source) {
10176         return new Tooltip(options, source);
10177 };
10178
10179 // @namespace Map
10180 // @section Methods for Layers and Controls
10181 Map.include({
10182
10183         // @method openTooltip(tooltip: Tooltip): this
10184         // Opens the specified tooltip.
10185         // @alternative
10186         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
10187         // Creates a tooltip with the specified content and options and open it.
10188         openTooltip: function (tooltip, latlng, options) {
10189                 if (!(tooltip instanceof Tooltip)) {
10190                         tooltip = new Tooltip(options).setContent(tooltip);
10191                 }
10192
10193                 if (latlng) {
10194                         tooltip.setLatLng(latlng);
10195                 }
10196
10197                 if (this.hasLayer(tooltip)) {
10198                         return this;
10199                 }
10200
10201                 return this.addLayer(tooltip);
10202         },
10203
10204         // @method closeTooltip(tooltip?: Tooltip): this
10205         // Closes the tooltip given as parameter.
10206         closeTooltip: function (tooltip) {
10207                 if (tooltip) {
10208                         this.removeLayer(tooltip);
10209                 }
10210                 return this;
10211         }
10212
10213 });
10214
10215 /*
10216  * @namespace Layer
10217  * @section Tooltip methods example
10218  *
10219  * All layers share a set of methods convenient for binding tooltips to it.
10220  *
10221  * ```js
10222  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10223  * layer.openTooltip();
10224  * layer.closeTooltip();
10225  * ```
10226  */
10227
10228 // @section Tooltip methods
10229 Layer.include({
10230
10231         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10232         // Binds a tooltip to the layer with the passed `content` and sets up the
10233         // necessary event listeners. If a `Function` is passed it will receive
10234         // the layer as the first argument and should return a `String` or `HTMLElement`.
10235         bindTooltip: function (content, options) {
10236
10237                 if (content instanceof Tooltip) {
10238                         setOptions(content, options);
10239                         this._tooltip = content;
10240                         content._source = this;
10241                 } else {
10242                         if (!this._tooltip || options) {
10243                                 this._tooltip = new Tooltip(options, this);
10244                         }
10245                         this._tooltip.setContent(content);
10246
10247                 }
10248
10249                 this._initTooltipInteractions();
10250
10251                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10252                         this.openTooltip();
10253                 }
10254
10255                 return this;
10256         },
10257
10258         // @method unbindTooltip(): this
10259         // Removes the tooltip previously bound with `bindTooltip`.
10260         unbindTooltip: function () {
10261                 if (this._tooltip) {
10262                         this._initTooltipInteractions(true);
10263                         this.closeTooltip();
10264                         this._tooltip = null;
10265                 }
10266                 return this;
10267         },
10268
10269         _initTooltipInteractions: function (remove$$1) {
10270                 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10271                 var onOff = remove$$1 ? 'off' : 'on',
10272                     events = {
10273                         remove: this.closeTooltip,
10274                         move: this._moveTooltip
10275                     };
10276                 if (!this._tooltip.options.permanent) {
10277                         events.mouseover = this._openTooltip;
10278                         events.mouseout = this.closeTooltip;
10279                         if (this._tooltip.options.sticky) {
10280                                 events.mousemove = this._moveTooltip;
10281                         }
10282                         if (touch) {
10283                                 events.click = this._openTooltip;
10284                         }
10285                 } else {
10286                         events.add = this._openTooltip;
10287                 }
10288                 this[onOff](events);
10289                 this._tooltipHandlersAdded = !remove$$1;
10290         },
10291
10292         // @method openTooltip(latlng?: LatLng): this
10293         // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
10294         openTooltip: function (layer, latlng) {
10295                 if (!(layer instanceof Layer)) {
10296                         latlng = layer;
10297                         layer = this;
10298                 }
10299
10300                 if (layer instanceof FeatureGroup) {
10301                         for (var id in this._layers) {
10302                                 layer = this._layers[id];
10303                                 break;
10304                         }
10305                 }
10306
10307                 if (!latlng) {
10308                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10309                 }
10310
10311                 if (this._tooltip && this._map) {
10312
10313                         // set tooltip source to this layer
10314                         this._tooltip._source = layer;
10315
10316                         // update the tooltip (content, layout, ect...)
10317                         this._tooltip.update();
10318
10319                         // open the tooltip on the map
10320                         this._map.openTooltip(this._tooltip, latlng);
10321
10322                         // Tooltip container may not be defined if not permanent and never
10323                         // opened.
10324                         if (this._tooltip.options.interactive && this._tooltip._container) {
10325                                 addClass(this._tooltip._container, 'leaflet-clickable');
10326                                 this.addInteractiveTarget(this._tooltip._container);
10327                         }
10328                 }
10329
10330                 return this;
10331         },
10332
10333         // @method closeTooltip(): this
10334         // Closes the tooltip bound to this layer if it is open.
10335         closeTooltip: function () {
10336                 if (this._tooltip) {
10337                         this._tooltip._close();
10338                         if (this._tooltip.options.interactive && this._tooltip._container) {
10339                                 removeClass(this._tooltip._container, 'leaflet-clickable');
10340                                 this.removeInteractiveTarget(this._tooltip._container);
10341                         }
10342                 }
10343                 return this;
10344         },
10345
10346         // @method toggleTooltip(): this
10347         // Opens or closes the tooltip bound to this layer depending on its current state.
10348         toggleTooltip: function (target) {
10349                 if (this._tooltip) {
10350                         if (this._tooltip._map) {
10351                                 this.closeTooltip();
10352                         } else {
10353                                 this.openTooltip(target);
10354                         }
10355                 }
10356                 return this;
10357         },
10358
10359         // @method isTooltipOpen(): boolean
10360         // Returns `true` if the tooltip bound to this layer is currently open.
10361         isTooltipOpen: function () {
10362                 return this._tooltip.isOpen();
10363         },
10364
10365         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10366         // Sets the content of the tooltip bound to this layer.
10367         setTooltipContent: function (content) {
10368                 if (this._tooltip) {
10369                         this._tooltip.setContent(content);
10370                 }
10371                 return this;
10372         },
10373
10374         // @method getTooltip(): Tooltip
10375         // Returns the tooltip bound to this layer.
10376         getTooltip: function () {
10377                 return this._tooltip;
10378         },
10379
10380         _openTooltip: function (e) {
10381                 var layer = e.layer || e.target;
10382
10383                 if (!this._tooltip || !this._map) {
10384                         return;
10385                 }
10386                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10387         },
10388
10389         _moveTooltip: function (e) {
10390                 var latlng = e.latlng, containerPoint, layerPoint;
10391                 if (this._tooltip.options.sticky && e.originalEvent) {
10392                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10393                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10394                         latlng = this._map.layerPointToLatLng(layerPoint);
10395                 }
10396                 this._tooltip.setLatLng(latlng);
10397         }
10398 });
10399
10400 /*
10401  * @class DivIcon
10402  * @aka L.DivIcon
10403  * @inherits Icon
10404  *
10405  * Represents a lightweight icon for markers that uses a simple `<div>`
10406  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10407  *
10408  * @example
10409  * ```js
10410  * var myIcon = L.divIcon({className: 'my-div-icon'});
10411  * // you can set .my-div-icon styles in CSS
10412  *
10413  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10414  * ```
10415  *
10416  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10417  */
10418
10419 var DivIcon = Icon.extend({
10420         options: {
10421                 // @section
10422                 // @aka DivIcon options
10423                 iconSize: [12, 12], // also can be set through CSS
10424
10425                 // iconAnchor: (Point),
10426                 // popupAnchor: (Point),
10427
10428                 // @option html: String = ''
10429                 // Custom HTML code to put inside the div element, empty by default.
10430                 html: false,
10431
10432                 // @option bgPos: Point = [0, 0]
10433                 // Optional relative position of the background, in pixels
10434                 bgPos: null,
10435
10436                 className: 'leaflet-div-icon'
10437         },
10438
10439         createIcon: function (oldIcon) {
10440                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10441                     options = this.options;
10442
10443                 div.innerHTML = options.html !== false ? options.html : '';
10444
10445                 if (options.bgPos) {
10446                         var bgPos = toPoint(options.bgPos);
10447                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10448                 }
10449                 this._setIconStyles(div, 'icon');
10450
10451                 return div;
10452         },
10453
10454         createShadow: function () {
10455                 return null;
10456         }
10457 });
10458
10459 // @factory L.divIcon(options: DivIcon options)
10460 // Creates a `DivIcon` instance with the given options.
10461 function divIcon(options) {
10462         return new DivIcon(options);
10463 }
10464
10465 Icon.Default = IconDefault;
10466
10467 /*
10468  * @class GridLayer
10469  * @inherits Layer
10470  * @aka L.GridLayer
10471  *
10472  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10473  * 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.
10474  *
10475  *
10476  * @section Synchronous usage
10477  * @example
10478  *
10479  * 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.
10480  *
10481  * ```js
10482  * var CanvasLayer = L.GridLayer.extend({
10483  *     createTile: function(coords){
10484  *         // create a <canvas> element for drawing
10485  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10486  *
10487  *         // setup tile width and height according to the options
10488  *         var size = this.getTileSize();
10489  *         tile.width = size.x;
10490  *         tile.height = size.y;
10491  *
10492  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10493  *         var ctx = tile.getContext('2d');
10494  *
10495  *         // return the tile so it can be rendered on screen
10496  *         return tile;
10497  *     }
10498  * });
10499  * ```
10500  *
10501  * @section Asynchronous usage
10502  * @example
10503  *
10504  * 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.
10505  *
10506  * ```js
10507  * var CanvasLayer = L.GridLayer.extend({
10508  *     createTile: function(coords, done){
10509  *         var error;
10510  *
10511  *         // create a <canvas> element for drawing
10512  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10513  *
10514  *         // setup tile width and height according to the options
10515  *         var size = this.getTileSize();
10516  *         tile.width = size.x;
10517  *         tile.height = size.y;
10518  *
10519  *         // draw something asynchronously and pass the tile to the done() callback
10520  *         setTimeout(function() {
10521  *             done(error, tile);
10522  *         }, 1000);
10523  *
10524  *         return tile;
10525  *     }
10526  * });
10527  * ```
10528  *
10529  * @section
10530  */
10531
10532
10533 var GridLayer = Layer.extend({
10534
10535         // @section
10536         // @aka GridLayer options
10537         options: {
10538                 // @option tileSize: Number|Point = 256
10539                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10540                 tileSize: 256,
10541
10542                 // @option opacity: Number = 1.0
10543                 // Opacity of the tiles. Can be used in the `createTile()` function.
10544                 opacity: 1,
10545
10546                 // @option updateWhenIdle: Boolean = (depends)
10547                 // Load new tiles only when panning ends.
10548                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10549                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10550                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10551                 updateWhenIdle: mobile,
10552
10553                 // @option updateWhenZooming: Boolean = true
10554                 // 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.
10555                 updateWhenZooming: true,
10556
10557                 // @option updateInterval: Number = 200
10558                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10559                 updateInterval: 200,
10560
10561                 // @option zIndex: Number = 1
10562                 // The explicit zIndex of the tile layer.
10563                 zIndex: 1,
10564
10565                 // @option bounds: LatLngBounds = undefined
10566                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10567                 bounds: null,
10568
10569                 // @option minZoom: Number = 0
10570                 // The minimum zoom level down to which this layer will be displayed (inclusive).
10571                 minZoom: 0,
10572
10573                 // @option maxZoom: Number = undefined
10574                 // The maximum zoom level up to which this layer will be displayed (inclusive).
10575                 maxZoom: undefined,
10576
10577                 // @option maxNativeZoom: Number = undefined
10578                 // Maximum zoom number the tile source has available. If it is specified,
10579                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10580                 // from `maxNativeZoom` level and auto-scaled.
10581                 maxNativeZoom: undefined,
10582
10583                 // @option minNativeZoom: Number = undefined
10584                 // Minimum zoom number the tile source has available. If it is specified,
10585                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10586                 // from `minNativeZoom` level and auto-scaled.
10587                 minNativeZoom: undefined,
10588
10589                 // @option noWrap: Boolean = false
10590                 // Whether the layer is wrapped around the antimeridian. If `true`, the
10591                 // GridLayer will only be displayed once at low zoom levels. Has no
10592                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10593                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10594                 // tiles outside the CRS limits.
10595                 noWrap: false,
10596
10597                 // @option pane: String = 'tilePane'
10598                 // `Map pane` where the grid layer will be added.
10599                 pane: 'tilePane',
10600
10601                 // @option className: String = ''
10602                 // A custom class name to assign to the tile layer. Empty by default.
10603                 className: '',
10604
10605                 // @option keepBuffer: Number = 2
10606                 // When panning the map, keep this many rows and columns of tiles before unloading them.
10607                 keepBuffer: 2
10608         },
10609
10610         initialize: function (options) {
10611                 setOptions(this, options);
10612         },
10613
10614         onAdd: function () {
10615                 this._initContainer();
10616
10617                 this._levels = {};
10618                 this._tiles = {};
10619
10620                 this._resetView();
10621                 this._update();
10622         },
10623
10624         beforeAdd: function (map) {
10625                 map._addZoomLimit(this);
10626         },
10627
10628         onRemove: function (map) {
10629                 this._removeAllTiles();
10630                 remove(this._container);
10631                 map._removeZoomLimit(this);
10632                 this._container = null;
10633                 this._tileZoom = undefined;
10634         },
10635
10636         // @method bringToFront: this
10637         // Brings the tile layer to the top of all tile layers.
10638         bringToFront: function () {
10639                 if (this._map) {
10640                         toFront(this._container);
10641                         this._setAutoZIndex(Math.max);
10642                 }
10643                 return this;
10644         },
10645
10646         // @method bringToBack: this
10647         // Brings the tile layer to the bottom of all tile layers.
10648         bringToBack: function () {
10649                 if (this._map) {
10650                         toBack(this._container);
10651                         this._setAutoZIndex(Math.min);
10652                 }
10653                 return this;
10654         },
10655
10656         // @method getContainer: HTMLElement
10657         // Returns the HTML element that contains the tiles for this layer.
10658         getContainer: function () {
10659                 return this._container;
10660         },
10661
10662         // @method setOpacity(opacity: Number): this
10663         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10664         setOpacity: function (opacity) {
10665                 this.options.opacity = opacity;
10666                 this._updateOpacity();
10667                 return this;
10668         },
10669
10670         // @method setZIndex(zIndex: Number): this
10671         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10672         setZIndex: function (zIndex) {
10673                 this.options.zIndex = zIndex;
10674                 this._updateZIndex();
10675
10676                 return this;
10677         },
10678
10679         // @method isLoading: Boolean
10680         // Returns `true` if any tile in the grid layer has not finished loading.
10681         isLoading: function () {
10682                 return this._loading;
10683         },
10684
10685         // @method redraw: this
10686         // Causes the layer to clear all the tiles and request them again.
10687         redraw: function () {
10688                 if (this._map) {
10689                         this._removeAllTiles();
10690                         this._update();
10691                 }
10692                 return this;
10693         },
10694
10695         getEvents: function () {
10696                 var events = {
10697                         viewprereset: this._invalidateAll,
10698                         viewreset: this._resetView,
10699                         zoom: this._resetView,
10700                         moveend: this._onMoveEnd
10701                 };
10702
10703                 if (!this.options.updateWhenIdle) {
10704                         // update tiles on move, but not more often than once per given interval
10705                         if (!this._onMove) {
10706                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10707                         }
10708
10709                         events.move = this._onMove;
10710                 }
10711
10712                 if (this._zoomAnimated) {
10713                         events.zoomanim = this._animateZoom;
10714                 }
10715
10716                 return events;
10717         },
10718
10719         // @section Extension methods
10720         // Layers extending `GridLayer` shall reimplement the following method.
10721         // @method createTile(coords: Object, done?: Function): HTMLElement
10722         // Called only internally, must be overridden by classes extending `GridLayer`.
10723         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10724         // is specified, it must be called when the tile has finished loading and drawing.
10725         createTile: function () {
10726                 return document.createElement('div');
10727         },
10728
10729         // @section
10730         // @method getTileSize: Point
10731         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10732         getTileSize: function () {
10733                 var s = this.options.tileSize;
10734                 return s instanceof Point ? s : new Point(s, s);
10735         },
10736
10737         _updateZIndex: function () {
10738                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10739                         this._container.style.zIndex = this.options.zIndex;
10740                 }
10741         },
10742
10743         _setAutoZIndex: function (compare) {
10744                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10745
10746                 var layers = this.getPane().children,
10747                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10748
10749                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10750
10751                         zIndex = layers[i].style.zIndex;
10752
10753                         if (layers[i] !== this._container && zIndex) {
10754                                 edgeZIndex = compare(edgeZIndex, +zIndex);
10755                         }
10756                 }
10757
10758                 if (isFinite(edgeZIndex)) {
10759                         this.options.zIndex = edgeZIndex + compare(-1, 1);
10760                         this._updateZIndex();
10761                 }
10762         },
10763
10764         _updateOpacity: function () {
10765                 if (!this._map) { return; }
10766
10767                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10768                 if (ielt9) { return; }
10769
10770                 setOpacity(this._container, this.options.opacity);
10771
10772                 var now = +new Date(),
10773                     nextFrame = false,
10774                     willPrune = false;
10775
10776                 for (var key in this._tiles) {
10777                         var tile = this._tiles[key];
10778                         if (!tile.current || !tile.loaded) { continue; }
10779
10780                         var fade = Math.min(1, (now - tile.loaded) / 200);
10781
10782                         setOpacity(tile.el, fade);
10783                         if (fade < 1) {
10784                                 nextFrame = true;
10785                         } else {
10786                                 if (tile.active) {
10787                                         willPrune = true;
10788                                 } else {
10789                                         this._onOpaqueTile(tile);
10790                                 }
10791                                 tile.active = true;
10792                         }
10793                 }
10794
10795                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10796
10797                 if (nextFrame) {
10798                         cancelAnimFrame(this._fadeFrame);
10799                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10800                 }
10801         },
10802
10803         _onOpaqueTile: falseFn,
10804
10805         _initContainer: function () {
10806                 if (this._container) { return; }
10807
10808                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10809                 this._updateZIndex();
10810
10811                 if (this.options.opacity < 1) {
10812                         this._updateOpacity();
10813                 }
10814
10815                 this.getPane().appendChild(this._container);
10816         },
10817
10818         _updateLevels: function () {
10819
10820                 var zoom = this._tileZoom,
10821                     maxZoom = this.options.maxZoom;
10822
10823                 if (zoom === undefined) { return undefined; }
10824
10825                 for (var z in this._levels) {
10826                         if (this._levels[z].el.children.length || z === zoom) {
10827                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10828                                 this._onUpdateLevel(z);
10829                         } else {
10830                                 remove(this._levels[z].el);
10831                                 this._removeTilesAtZoom(z);
10832                                 this._onRemoveLevel(z);
10833                                 delete this._levels[z];
10834                         }
10835                 }
10836
10837                 var level = this._levels[zoom],
10838                     map = this._map;
10839
10840                 if (!level) {
10841                         level = this._levels[zoom] = {};
10842
10843                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10844                         level.el.style.zIndex = maxZoom;
10845
10846                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10847                         level.zoom = zoom;
10848
10849                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
10850
10851                         // force the browser to consider the newly added element for transition
10852                         falseFn(level.el.offsetWidth);
10853
10854                         this._onCreateLevel(level);
10855                 }
10856
10857                 this._level = level;
10858
10859                 return level;
10860         },
10861
10862         _onUpdateLevel: falseFn,
10863
10864         _onRemoveLevel: falseFn,
10865
10866         _onCreateLevel: falseFn,
10867
10868         _pruneTiles: function () {
10869                 if (!this._map) {
10870                         return;
10871                 }
10872
10873                 var key, tile;
10874
10875                 var zoom = this._map.getZoom();
10876                 if (zoom > this.options.maxZoom ||
10877                         zoom < this.options.minZoom) {
10878                         this._removeAllTiles();
10879                         return;
10880                 }
10881
10882                 for (key in this._tiles) {
10883                         tile = this._tiles[key];
10884                         tile.retain = tile.current;
10885                 }
10886
10887                 for (key in this._tiles) {
10888                         tile = this._tiles[key];
10889                         if (tile.current && !tile.active) {
10890                                 var coords = tile.coords;
10891                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10892                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10893                                 }
10894                         }
10895                 }
10896
10897                 for (key in this._tiles) {
10898                         if (!this._tiles[key].retain) {
10899                                 this._removeTile(key);
10900                         }
10901                 }
10902         },
10903
10904         _removeTilesAtZoom: function (zoom) {
10905                 for (var key in this._tiles) {
10906                         if (this._tiles[key].coords.z !== zoom) {
10907                                 continue;
10908                         }
10909                         this._removeTile(key);
10910                 }
10911         },
10912
10913         _removeAllTiles: function () {
10914                 for (var key in this._tiles) {
10915                         this._removeTile(key);
10916                 }
10917         },
10918
10919         _invalidateAll: function () {
10920                 for (var z in this._levels) {
10921                         remove(this._levels[z].el);
10922                         this._onRemoveLevel(z);
10923                         delete this._levels[z];
10924                 }
10925                 this._removeAllTiles();
10926
10927                 this._tileZoom = undefined;
10928         },
10929
10930         _retainParent: function (x, y, z, minZoom) {
10931                 var x2 = Math.floor(x / 2),
10932                     y2 = Math.floor(y / 2),
10933                     z2 = z - 1,
10934                     coords2 = new Point(+x2, +y2);
10935                 coords2.z = +z2;
10936
10937                 var key = this._tileCoordsToKey(coords2),
10938                     tile = this._tiles[key];
10939
10940                 if (tile && tile.active) {
10941                         tile.retain = true;
10942                         return true;
10943
10944                 } else if (tile && tile.loaded) {
10945                         tile.retain = true;
10946                 }
10947
10948                 if (z2 > minZoom) {
10949                         return this._retainParent(x2, y2, z2, minZoom);
10950                 }
10951
10952                 return false;
10953         },
10954
10955         _retainChildren: function (x, y, z, maxZoom) {
10956
10957                 for (var i = 2 * x; i < 2 * x + 2; i++) {
10958                         for (var j = 2 * y; j < 2 * y + 2; j++) {
10959
10960                                 var coords = new Point(i, j);
10961                                 coords.z = z + 1;
10962
10963                                 var key = this._tileCoordsToKey(coords),
10964                                     tile = this._tiles[key];
10965
10966                                 if (tile && tile.active) {
10967                                         tile.retain = true;
10968                                         continue;
10969
10970                                 } else if (tile && tile.loaded) {
10971                                         tile.retain = true;
10972                                 }
10973
10974                                 if (z + 1 < maxZoom) {
10975                                         this._retainChildren(i, j, z + 1, maxZoom);
10976                                 }
10977                         }
10978                 }
10979         },
10980
10981         _resetView: function (e) {
10982                 var animating = e && (e.pinch || e.flyTo);
10983                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10984         },
10985
10986         _animateZoom: function (e) {
10987                 this._setView(e.center, e.zoom, true, e.noUpdate);
10988         },
10989
10990         _clampZoom: function (zoom) {
10991                 var options = this.options;
10992
10993                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10994                         return options.minNativeZoom;
10995                 }
10996
10997                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10998                         return options.maxNativeZoom;
10999                 }
11000
11001                 return zoom;
11002         },
11003
11004         _setView: function (center, zoom, noPrune, noUpdate) {
11005                 var tileZoom = this._clampZoom(Math.round(zoom));
11006                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
11007                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
11008                         tileZoom = undefined;
11009                 }
11010
11011                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
11012
11013                 if (!noUpdate || tileZoomChanged) {
11014
11015                         this._tileZoom = tileZoom;
11016
11017                         if (this._abortLoading) {
11018                                 this._abortLoading();
11019                         }
11020
11021                         this._updateLevels();
11022                         this._resetGrid();
11023
11024                         if (tileZoom !== undefined) {
11025                                 this._update(center);
11026                         }
11027
11028                         if (!noPrune) {
11029                                 this._pruneTiles();
11030                         }
11031
11032                         // Flag to prevent _updateOpacity from pruning tiles during
11033                         // a zoom anim or a pinch gesture
11034                         this._noPrune = !!noPrune;
11035                 }
11036
11037                 this._setZoomTransforms(center, zoom);
11038         },
11039
11040         _setZoomTransforms: function (center, zoom) {
11041                 for (var i in this._levels) {
11042                         this._setZoomTransform(this._levels[i], center, zoom);
11043                 }
11044         },
11045
11046         _setZoomTransform: function (level, center, zoom) {
11047                 var scale = this._map.getZoomScale(zoom, level.zoom),
11048                     translate = level.origin.multiplyBy(scale)
11049                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
11050
11051                 if (any3d) {
11052                         setTransform(level.el, translate, scale);
11053                 } else {
11054                         setPosition(level.el, translate);
11055                 }
11056         },
11057
11058         _resetGrid: function () {
11059                 var map = this._map,
11060                     crs = map.options.crs,
11061                     tileSize = this._tileSize = this.getTileSize(),
11062                     tileZoom = this._tileZoom;
11063
11064                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
11065                 if (bounds) {
11066                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
11067                 }
11068
11069                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
11070                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
11071                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
11072                 ];
11073                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
11074                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
11075                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
11076                 ];
11077         },
11078
11079         _onMoveEnd: function () {
11080                 if (!this._map || this._map._animatingZoom) { return; }
11081
11082                 this._update();
11083         },
11084
11085         _getTiledPixelBounds: function (center) {
11086                 var map = this._map,
11087                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
11088                     scale = map.getZoomScale(mapZoom, this._tileZoom),
11089                     pixelCenter = map.project(center, this._tileZoom).floor(),
11090                     halfSize = map.getSize().divideBy(scale * 2);
11091
11092                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
11093         },
11094
11095         // Private method to load tiles in the grid's active zoom level according to map bounds
11096         _update: function (center) {
11097                 var map = this._map;
11098                 if (!map) { return; }
11099                 var zoom = this._clampZoom(map.getZoom());
11100
11101                 if (center === undefined) { center = map.getCenter(); }
11102                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
11103
11104                 var pixelBounds = this._getTiledPixelBounds(center),
11105                     tileRange = this._pxBoundsToTileRange(pixelBounds),
11106                     tileCenter = tileRange.getCenter(),
11107                     queue = [],
11108                     margin = this.options.keepBuffer,
11109                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
11110                                               tileRange.getTopRight().add([margin, -margin]));
11111
11112                 // Sanity check: panic if the tile range contains Infinity somewhere.
11113                 if (!(isFinite(tileRange.min.x) &&
11114                       isFinite(tileRange.min.y) &&
11115                       isFinite(tileRange.max.x) &&
11116                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
11117
11118                 for (var key in this._tiles) {
11119                         var c = this._tiles[key].coords;
11120                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
11121                                 this._tiles[key].current = false;
11122                         }
11123                 }
11124
11125                 // _update just loads more tiles. If the tile zoom level differs too much
11126                 // from the map's, let _setView reset levels and prune old tiles.
11127                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
11128
11129                 // create a queue of coordinates to load tiles from
11130                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
11131                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
11132                                 var coords = new Point(i, j);
11133                                 coords.z = this._tileZoom;
11134
11135                                 if (!this._isValidTile(coords)) { continue; }
11136
11137                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
11138                                 if (tile) {
11139                                         tile.current = true;
11140                                 } else {
11141                                         queue.push(coords);
11142                                 }
11143                         }
11144                 }
11145
11146                 // sort tile queue to load tiles in order of their distance to center
11147                 queue.sort(function (a, b) {
11148                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
11149                 });
11150
11151                 if (queue.length !== 0) {
11152                         // if it's the first batch of tiles to load
11153                         if (!this._loading) {
11154                                 this._loading = true;
11155                                 // @event loading: Event
11156                                 // Fired when the grid layer starts loading tiles.
11157                                 this.fire('loading');
11158                         }
11159
11160                         // create DOM fragment to append tiles in one batch
11161                         var fragment = document.createDocumentFragment();
11162
11163                         for (i = 0; i < queue.length; i++) {
11164                                 this._addTile(queue[i], fragment);
11165                         }
11166
11167                         this._level.el.appendChild(fragment);
11168                 }
11169         },
11170
11171         _isValidTile: function (coords) {
11172                 var crs = this._map.options.crs;
11173
11174                 if (!crs.infinite) {
11175                         // don't load tile if it's out of bounds and not wrapped
11176                         var bounds = this._globalTileRange;
11177                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
11178                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
11179                 }
11180
11181                 if (!this.options.bounds) { return true; }
11182
11183                 // don't load tile if it doesn't intersect the bounds in options
11184                 var tileBounds = this._tileCoordsToBounds(coords);
11185                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
11186         },
11187
11188         _keyToBounds: function (key) {
11189                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
11190         },
11191
11192         _tileCoordsToNwSe: function (coords) {
11193                 var map = this._map,
11194                     tileSize = this.getTileSize(),
11195                     nwPoint = coords.scaleBy(tileSize),
11196                     sePoint = nwPoint.add(tileSize),
11197                     nw = map.unproject(nwPoint, coords.z),
11198                     se = map.unproject(sePoint, coords.z);
11199                 return [nw, se];
11200         },
11201
11202         // converts tile coordinates to its geographical bounds
11203         _tileCoordsToBounds: function (coords) {
11204                 var bp = this._tileCoordsToNwSe(coords),
11205                     bounds = new LatLngBounds(bp[0], bp[1]);
11206
11207                 if (!this.options.noWrap) {
11208                         bounds = this._map.wrapLatLngBounds(bounds);
11209                 }
11210                 return bounds;
11211         },
11212         // converts tile coordinates to key for the tile cache
11213         _tileCoordsToKey: function (coords) {
11214                 return coords.x + ':' + coords.y + ':' + coords.z;
11215         },
11216
11217         // converts tile cache key to coordinates
11218         _keyToTileCoords: function (key) {
11219                 var k = key.split(':'),
11220                     coords = new Point(+k[0], +k[1]);
11221                 coords.z = +k[2];
11222                 return coords;
11223         },
11224
11225         _removeTile: function (key) {
11226                 var tile = this._tiles[key];
11227                 if (!tile) { return; }
11228
11229                 remove(tile.el);
11230
11231                 delete this._tiles[key];
11232
11233                 // @event tileunload: TileEvent
11234                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11235                 this.fire('tileunload', {
11236                         tile: tile.el,
11237                         coords: this._keyToTileCoords(key)
11238                 });
11239         },
11240
11241         _initTile: function (tile) {
11242                 addClass(tile, 'leaflet-tile');
11243
11244                 var tileSize = this.getTileSize();
11245                 tile.style.width = tileSize.x + 'px';
11246                 tile.style.height = tileSize.y + 'px';
11247
11248                 tile.onselectstart = falseFn;
11249                 tile.onmousemove = falseFn;
11250
11251                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11252                 if (ielt9 && this.options.opacity < 1) {
11253                         setOpacity(tile, this.options.opacity);
11254                 }
11255
11256                 // without this hack, tiles disappear after zoom on Chrome for Android
11257                 // https://github.com/Leaflet/Leaflet/issues/2078
11258                 if (android && !android23) {
11259                         tile.style.WebkitBackfaceVisibility = 'hidden';
11260                 }
11261         },
11262
11263         _addTile: function (coords, container) {
11264                 var tilePos = this._getTilePos(coords),
11265                     key = this._tileCoordsToKey(coords);
11266
11267                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11268
11269                 this._initTile(tile);
11270
11271                 // if createTile is defined with a second argument ("done" callback),
11272                 // we know that tile is async and will be ready later; otherwise
11273                 if (this.createTile.length < 2) {
11274                         // mark tile as ready, but delay one frame for opacity animation to happen
11275                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11276                 }
11277
11278                 setPosition(tile, tilePos);
11279
11280                 // save tile in cache
11281                 this._tiles[key] = {
11282                         el: tile,
11283                         coords: coords,
11284                         current: true
11285                 };
11286
11287                 container.appendChild(tile);
11288                 // @event tileloadstart: TileEvent
11289                 // Fired when a tile is requested and starts loading.
11290                 this.fire('tileloadstart', {
11291                         tile: tile,
11292                         coords: coords
11293                 });
11294         },
11295
11296         _tileReady: function (coords, err, tile) {
11297                 if (err) {
11298                         // @event tileerror: TileErrorEvent
11299                         // Fired when there is an error loading a tile.
11300                         this.fire('tileerror', {
11301                                 error: err,
11302                                 tile: tile,
11303                                 coords: coords
11304                         });
11305                 }
11306
11307                 var key = this._tileCoordsToKey(coords);
11308
11309                 tile = this._tiles[key];
11310                 if (!tile) { return; }
11311
11312                 tile.loaded = +new Date();
11313                 if (this._map._fadeAnimated) {
11314                         setOpacity(tile.el, 0);
11315                         cancelAnimFrame(this._fadeFrame);
11316                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11317                 } else {
11318                         tile.active = true;
11319                         this._pruneTiles();
11320                 }
11321
11322                 if (!err) {
11323                         addClass(tile.el, 'leaflet-tile-loaded');
11324
11325                         // @event tileload: TileEvent
11326                         // Fired when a tile loads.
11327                         this.fire('tileload', {
11328                                 tile: tile.el,
11329                                 coords: coords
11330                         });
11331                 }
11332
11333                 if (this._noTilesToLoad()) {
11334                         this._loading = false;
11335                         // @event load: Event
11336                         // Fired when the grid layer loaded all visible tiles.
11337                         this.fire('load');
11338
11339                         if (ielt9 || !this._map._fadeAnimated) {
11340                                 requestAnimFrame(this._pruneTiles, this);
11341                         } else {
11342                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11343                                 // to trigger a pruning.
11344                                 setTimeout(bind(this._pruneTiles, this), 250);
11345                         }
11346                 }
11347         },
11348
11349         _getTilePos: function (coords) {
11350                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11351         },
11352
11353         _wrapCoords: function (coords) {
11354                 var newCoords = new Point(
11355                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11356                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11357                 newCoords.z = coords.z;
11358                 return newCoords;
11359         },
11360
11361         _pxBoundsToTileRange: function (bounds) {
11362                 var tileSize = this.getTileSize();
11363                 return new Bounds(
11364                         bounds.min.unscaleBy(tileSize).floor(),
11365                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11366         },
11367
11368         _noTilesToLoad: function () {
11369                 for (var key in this._tiles) {
11370                         if (!this._tiles[key].loaded) { return false; }
11371                 }
11372                 return true;
11373         }
11374 });
11375
11376 // @factory L.gridLayer(options?: GridLayer options)
11377 // Creates a new instance of GridLayer with the supplied options.
11378 function gridLayer(options) {
11379         return new GridLayer(options);
11380 }
11381
11382 /*
11383  * @class TileLayer
11384  * @inherits GridLayer
11385  * @aka L.TileLayer
11386  * Used to load and display tile layers on the map. Extends `GridLayer`.
11387  *
11388  * @example
11389  *
11390  * ```js
11391  * L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
11392  * ```
11393  *
11394  * @section URL template
11395  * @example
11396  *
11397  * A string of the following form:
11398  *
11399  * ```
11400  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11401  * ```
11402  *
11403  * `{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.
11404  *
11405  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11406  *
11407  * ```
11408  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11409  * ```
11410  */
11411
11412
11413 var TileLayer = GridLayer.extend({
11414
11415         // @section
11416         // @aka TileLayer options
11417         options: {
11418                 // @option minZoom: Number = 0
11419                 // The minimum zoom level down to which this layer will be displayed (inclusive).
11420                 minZoom: 0,
11421
11422                 // @option maxZoom: Number = 18
11423                 // The maximum zoom level up to which this layer will be displayed (inclusive).
11424                 maxZoom: 18,
11425
11426                 // @option subdomains: String|String[] = 'abc'
11427                 // 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.
11428                 subdomains: 'abc',
11429
11430                 // @option errorTileUrl: String = ''
11431                 // URL to the tile image to show in place of the tile that failed to load.
11432                 errorTileUrl: '',
11433
11434                 // @option zoomOffset: Number = 0
11435                 // The zoom number used in tile URLs will be offset with this value.
11436                 zoomOffset: 0,
11437
11438                 // @option tms: Boolean = false
11439                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11440                 tms: false,
11441
11442                 // @option zoomReverse: Boolean = false
11443                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11444                 zoomReverse: false,
11445
11446                 // @option detectRetina: Boolean = false
11447                 // 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.
11448                 detectRetina: false,
11449
11450                 // @option crossOrigin: Boolean|String = false
11451                 // Whether the crossOrigin attribute will be added to the tiles.
11452                 // 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.
11453                 // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
11454                 crossOrigin: false
11455         },
11456
11457         initialize: function (url, options) {
11458
11459                 this._url = url;
11460
11461                 options = setOptions(this, options);
11462
11463                 // detecting retina displays, adjusting tileSize and zoom levels
11464                 if (options.detectRetina && retina && options.maxZoom > 0) {
11465
11466                         options.tileSize = Math.floor(options.tileSize / 2);
11467
11468                         if (!options.zoomReverse) {
11469                                 options.zoomOffset++;
11470                                 options.maxZoom--;
11471                         } else {
11472                                 options.zoomOffset--;
11473                                 options.minZoom++;
11474                         }
11475
11476                         options.minZoom = Math.max(0, options.minZoom);
11477                 }
11478
11479                 if (typeof options.subdomains === 'string') {
11480                         options.subdomains = options.subdomains.split('');
11481                 }
11482
11483                 // for https://github.com/Leaflet/Leaflet/issues/137
11484                 if (!android) {
11485                         this.on('tileunload', this._onTileRemove);
11486                 }
11487         },
11488
11489         // @method setUrl(url: String, noRedraw?: Boolean): this
11490         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11491         setUrl: function (url, noRedraw) {
11492                 this._url = url;
11493
11494                 if (!noRedraw) {
11495                         this.redraw();
11496                 }
11497                 return this;
11498         },
11499
11500         // @method createTile(coords: Object, done?: Function): HTMLElement
11501         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11502         // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
11503         // callback is called when the tile has been loaded.
11504         createTile: function (coords, done) {
11505                 var tile = document.createElement('img');
11506
11507                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11508                 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11509
11510                 if (this.options.crossOrigin || this.options.crossOrigin === '') {
11511                         tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
11512                 }
11513
11514                 /*
11515                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11516                  http://www.w3.org/TR/WCAG20-TECHS/H67
11517                 */
11518                 tile.alt = '';
11519
11520                 /*
11521                  Set role="presentation" to force screen readers to ignore this
11522                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11523                 */
11524                 tile.setAttribute('role', 'presentation');
11525
11526                 tile.src = this.getTileUrl(coords);
11527
11528                 return tile;
11529         },
11530
11531         // @section Extension methods
11532         // @uninheritable
11533         // Layers extending `TileLayer` might reimplement the following method.
11534         // @method getTileUrl(coords: Object): String
11535         // Called only internally, returns the URL for a tile given its coordinates.
11536         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11537         getTileUrl: function (coords) {
11538                 var data = {
11539                         r: retina ? '@2x' : '',
11540                         s: this._getSubdomain(coords),
11541                         x: coords.x,
11542                         y: coords.y,
11543                         z: this._getZoomForUrl()
11544                 };
11545                 if (this._map && !this._map.options.crs.infinite) {
11546                         var invertedY = this._globalTileRange.max.y - coords.y;
11547                         if (this.options.tms) {
11548                                 data['y'] = invertedY;
11549                         }
11550                         data['-y'] = invertedY;
11551                 }
11552
11553                 return template(this._url, extend(data, this.options));
11554         },
11555
11556         _tileOnLoad: function (done, tile) {
11557                 // For https://github.com/Leaflet/Leaflet/issues/3332
11558                 if (ielt9) {
11559                         setTimeout(bind(done, this, null, tile), 0);
11560                 } else {
11561                         done(null, tile);
11562                 }
11563         },
11564
11565         _tileOnError: function (done, tile, e) {
11566                 var errorUrl = this.options.errorTileUrl;
11567                 if (errorUrl && tile.getAttribute('src') !== errorUrl) {
11568                         tile.src = errorUrl;
11569                 }
11570                 done(e, tile);
11571         },
11572
11573         _onTileRemove: function (e) {
11574                 e.tile.onload = null;
11575         },
11576
11577         _getZoomForUrl: function () {
11578                 var zoom = this._tileZoom,
11579                 maxZoom = this.options.maxZoom,
11580                 zoomReverse = this.options.zoomReverse,
11581                 zoomOffset = this.options.zoomOffset;
11582
11583                 if (zoomReverse) {
11584                         zoom = maxZoom - zoom;
11585                 }
11586
11587                 return zoom + zoomOffset;
11588         },
11589
11590         _getSubdomain: function (tilePoint) {
11591                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11592                 return this.options.subdomains[index];
11593         },
11594
11595         // stops loading all tiles in the background layer
11596         _abortLoading: function () {
11597                 var i, tile;
11598                 for (i in this._tiles) {
11599                         if (this._tiles[i].coords.z !== this._tileZoom) {
11600                                 tile = this._tiles[i].el;
11601
11602                                 tile.onload = falseFn;
11603                                 tile.onerror = falseFn;
11604
11605                                 if (!tile.complete) {
11606                                         tile.src = emptyImageUrl;
11607                                         remove(tile);
11608                                         delete this._tiles[i];
11609                                 }
11610                         }
11611                 }
11612         },
11613
11614         _removeTile: function (key) {
11615                 var tile = this._tiles[key];
11616                 if (!tile) { return; }
11617
11618                 // Cancels any pending http requests associated with the tile
11619                 // unless we're on Android's stock browser,
11620                 // see https://github.com/Leaflet/Leaflet/issues/137
11621                 if (!androidStock) {
11622                         tile.el.setAttribute('src', emptyImageUrl);
11623                 }
11624
11625                 return GridLayer.prototype._removeTile.call(this, key);
11626         },
11627
11628         _tileReady: function (coords, err, tile) {
11629                 if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
11630                         return;
11631                 }
11632
11633                 return GridLayer.prototype._tileReady.call(this, coords, err, tile);
11634         }
11635 });
11636
11637
11638 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11639 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11640
11641 function tileLayer(url, options) {
11642         return new TileLayer(url, options);
11643 }
11644
11645 /*
11646  * @class TileLayer.WMS
11647  * @inherits TileLayer
11648  * @aka L.TileLayer.WMS
11649  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11650  *
11651  * @example
11652  *
11653  * ```js
11654  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11655  *      layers: 'nexrad-n0r-900913',
11656  *      format: 'image/png',
11657  *      transparent: true,
11658  *      attribution: "Weather data © 2012 IEM Nexrad"
11659  * });
11660  * ```
11661  */
11662
11663 var TileLayerWMS = TileLayer.extend({
11664
11665         // @section
11666         // @aka TileLayer.WMS options
11667         // If any custom options not documented here are used, they will be sent to the
11668         // WMS server as extra parameters in each request URL. This can be useful for
11669         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11670         defaultWmsParams: {
11671                 service: 'WMS',
11672                 request: 'GetMap',
11673
11674                 // @option layers: String = ''
11675                 // **(required)** Comma-separated list of WMS layers to show.
11676                 layers: '',
11677
11678                 // @option styles: String = ''
11679                 // Comma-separated list of WMS styles.
11680                 styles: '',
11681
11682                 // @option format: String = 'image/jpeg'
11683                 // WMS image format (use `'image/png'` for layers with transparency).
11684                 format: 'image/jpeg',
11685
11686                 // @option transparent: Boolean = false
11687                 // If `true`, the WMS service will return images with transparency.
11688                 transparent: false,
11689
11690                 // @option version: String = '1.1.1'
11691                 // Version of the WMS service to use
11692                 version: '1.1.1'
11693         },
11694
11695         options: {
11696                 // @option crs: CRS = null
11697                 // Coordinate Reference System to use for the WMS requests, defaults to
11698                 // map CRS. Don't change this if you're not sure what it means.
11699                 crs: null,
11700
11701                 // @option uppercase: Boolean = false
11702                 // If `true`, WMS request parameter keys will be uppercase.
11703                 uppercase: false
11704         },
11705
11706         initialize: function (url, options) {
11707
11708                 this._url = url;
11709
11710                 var wmsParams = extend({}, this.defaultWmsParams);
11711
11712                 // all keys that are not TileLayer options go to WMS params
11713                 for (var i in options) {
11714                         if (!(i in this.options)) {
11715                                 wmsParams[i] = options[i];
11716                         }
11717                 }
11718
11719                 options = setOptions(this, options);
11720
11721                 var realRetina = options.detectRetina && retina ? 2 : 1;
11722                 var tileSize = this.getTileSize();
11723                 wmsParams.width = tileSize.x * realRetina;
11724                 wmsParams.height = tileSize.y * realRetina;
11725
11726                 this.wmsParams = wmsParams;
11727         },
11728
11729         onAdd: function (map) {
11730
11731                 this._crs = this.options.crs || map.options.crs;
11732                 this._wmsVersion = parseFloat(this.wmsParams.version);
11733
11734                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11735                 this.wmsParams[projectionKey] = this._crs.code;
11736
11737                 TileLayer.prototype.onAdd.call(this, map);
11738         },
11739
11740         getTileUrl: function (coords) {
11741
11742                 var tileBounds = this._tileCoordsToNwSe(coords),
11743                     crs = this._crs,
11744                     bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
11745                     min = bounds.min,
11746                     max = bounds.max,
11747                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11748                     [min.y, min.x, max.y, max.x] :
11749                     [min.x, min.y, max.x, max.y]).join(','),
11750                     url = TileLayer.prototype.getTileUrl.call(this, coords);
11751                 return url +
11752                         getParamString(this.wmsParams, url, this.options.uppercase) +
11753                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11754         },
11755
11756         // @method setParams(params: Object, noRedraw?: Boolean): this
11757         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11758         setParams: function (params, noRedraw) {
11759
11760                 extend(this.wmsParams, params);
11761
11762                 if (!noRedraw) {
11763                         this.redraw();
11764                 }
11765
11766                 return this;
11767         }
11768 });
11769
11770
11771 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11772 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11773 function tileLayerWMS(url, options) {
11774         return new TileLayerWMS(url, options);
11775 }
11776
11777 TileLayer.WMS = TileLayerWMS;
11778 tileLayer.wms = tileLayerWMS;
11779
11780 /*
11781  * @class Renderer
11782  * @inherits Layer
11783  * @aka L.Renderer
11784  *
11785  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11786  * DOM container of the renderer, its bounds, and its zoom animation.
11787  *
11788  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11789  * itself can be added or removed to the map. All paths use a renderer, which can
11790  * be implicit (the map will decide the type of renderer and use it automatically)
11791  * or explicit (using the [`renderer`](#path-renderer) option of the path).
11792  *
11793  * Do not use this class directly, use `SVG` and `Canvas` instead.
11794  *
11795  * @event update: Event
11796  * Fired when the renderer updates its bounds, center and zoom, for example when
11797  * its map has moved
11798  */
11799
11800 var Renderer = Layer.extend({
11801
11802         // @section
11803         // @aka Renderer options
11804         options: {
11805                 // @option padding: Number = 0.1
11806                 // How much to extend the clip area around the map view (relative to its size)
11807                 // e.g. 0.1 would be 10% of map view in each direction
11808                 padding: 0.1,
11809
11810                 // @option tolerance: Number = 0
11811                 // How much to extend click tolerance round a path/object on the map
11812                 tolerance : 0
11813         },
11814
11815         initialize: function (options) {
11816                 setOptions(this, options);
11817                 stamp(this);
11818                 this._layers = this._layers || {};
11819         },
11820
11821         onAdd: function () {
11822                 if (!this._container) {
11823                         this._initContainer(); // defined by renderer implementations
11824
11825                         if (this._zoomAnimated) {
11826                                 addClass(this._container, 'leaflet-zoom-animated');
11827                         }
11828                 }
11829
11830                 this.getPane().appendChild(this._container);
11831                 this._update();
11832                 this.on('update', this._updatePaths, this);
11833         },
11834
11835         onRemove: function () {
11836                 this.off('update', this._updatePaths, this);
11837                 this._destroyContainer();
11838         },
11839
11840         getEvents: function () {
11841                 var events = {
11842                         viewreset: this._reset,
11843                         zoom: this._onZoom,
11844                         moveend: this._update,
11845                         zoomend: this._onZoomEnd
11846                 };
11847                 if (this._zoomAnimated) {
11848                         events.zoomanim = this._onAnimZoom;
11849                 }
11850                 return events;
11851         },
11852
11853         _onAnimZoom: function (ev) {
11854                 this._updateTransform(ev.center, ev.zoom);
11855         },
11856
11857         _onZoom: function () {
11858                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11859         },
11860
11861         _updateTransform: function (center, zoom) {
11862                 var scale = this._map.getZoomScale(zoom, this._zoom),
11863                     position = getPosition(this._container),
11864                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11865                     currentCenterPoint = this._map.project(this._center, zoom),
11866                     destCenterPoint = this._map.project(center, zoom),
11867                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
11868
11869                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11870
11871                 if (any3d) {
11872                         setTransform(this._container, topLeftOffset, scale);
11873                 } else {
11874                         setPosition(this._container, topLeftOffset);
11875                 }
11876         },
11877
11878         _reset: function () {
11879                 this._update();
11880                 this._updateTransform(this._center, this._zoom);
11881
11882                 for (var id in this._layers) {
11883                         this._layers[id]._reset();
11884                 }
11885         },
11886
11887         _onZoomEnd: function () {
11888                 for (var id in this._layers) {
11889                         this._layers[id]._project();
11890                 }
11891         },
11892
11893         _updatePaths: function () {
11894                 for (var id in this._layers) {
11895                         this._layers[id]._update();
11896                 }
11897         },
11898
11899         _update: function () {
11900                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11901                 // Subclasses are responsible of firing the 'update' event.
11902                 var p = this.options.padding,
11903                     size = this._map.getSize(),
11904                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11905
11906                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11907
11908                 this._center = this._map.getCenter();
11909                 this._zoom = this._map.getZoom();
11910         }
11911 });
11912
11913 /*
11914  * @class Canvas
11915  * @inherits Renderer
11916  * @aka L.Canvas
11917  *
11918  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11919  * Inherits `Renderer`.
11920  *
11921  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11922  * available in all web browsers, notably IE8, and overlapping geometries might
11923  * not display properly in some edge cases.
11924  *
11925  * @example
11926  *
11927  * Use Canvas by default for all paths in the map:
11928  *
11929  * ```js
11930  * var map = L.map('map', {
11931  *      renderer: L.canvas()
11932  * });
11933  * ```
11934  *
11935  * Use a Canvas renderer with extra padding for specific vector geometries:
11936  *
11937  * ```js
11938  * var map = L.map('map');
11939  * var myRenderer = L.canvas({ padding: 0.5 });
11940  * var line = L.polyline( coordinates, { renderer: myRenderer } );
11941  * var circle = L.circle( center, { renderer: myRenderer } );
11942  * ```
11943  */
11944
11945 var Canvas = Renderer.extend({
11946         getEvents: function () {
11947                 var events = Renderer.prototype.getEvents.call(this);
11948                 events.viewprereset = this._onViewPreReset;
11949                 return events;
11950         },
11951
11952         _onViewPreReset: function () {
11953                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11954                 this._postponeUpdatePaths = true;
11955         },
11956
11957         onAdd: function () {
11958                 Renderer.prototype.onAdd.call(this);
11959
11960                 // Redraw vectors since canvas is cleared upon removal,
11961                 // in case of removing the renderer itself from the map.
11962                 this._draw();
11963         },
11964
11965         _initContainer: function () {
11966                 var container = this._container = document.createElement('canvas');
11967
11968                 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11969                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11970                 on(container, 'mouseout', this._handleMouseOut, this);
11971
11972                 this._ctx = container.getContext('2d');
11973         },
11974
11975         _destroyContainer: function () {
11976                 cancelAnimFrame(this._redrawRequest);
11977                 delete this._ctx;
11978                 remove(this._container);
11979                 off(this._container);
11980                 delete this._container;
11981         },
11982
11983         _updatePaths: function () {
11984                 if (this._postponeUpdatePaths) { return; }
11985
11986                 var layer;
11987                 this._redrawBounds = null;
11988                 for (var id in this._layers) {
11989                         layer = this._layers[id];
11990                         layer._update();
11991                 }
11992                 this._redraw();
11993         },
11994
11995         _update: function () {
11996                 if (this._map._animatingZoom && this._bounds) { return; }
11997
11998                 this._drawnLayers = {};
11999
12000                 Renderer.prototype._update.call(this);
12001
12002                 var b = this._bounds,
12003                     container = this._container,
12004                     size = b.getSize(),
12005                     m = retina ? 2 : 1;
12006
12007                 setPosition(container, b.min);
12008
12009                 // set canvas size (also clearing it); use double size on retina
12010                 container.width = m * size.x;
12011                 container.height = m * size.y;
12012                 container.style.width = size.x + 'px';
12013                 container.style.height = size.y + 'px';
12014
12015                 if (retina) {
12016                         this._ctx.scale(2, 2);
12017                 }
12018
12019                 // translate so we use the same path coordinates after canvas element moves
12020                 this._ctx.translate(-b.min.x, -b.min.y);
12021
12022                 // Tell paths to redraw themselves
12023                 this.fire('update');
12024         },
12025
12026         _reset: function () {
12027                 Renderer.prototype._reset.call(this);
12028
12029                 if (this._postponeUpdatePaths) {
12030                         this._postponeUpdatePaths = false;
12031                         this._updatePaths();
12032                 }
12033         },
12034
12035         _initPath: function (layer) {
12036                 this._updateDashArray(layer);
12037                 this._layers[stamp(layer)] = layer;
12038
12039                 var order = layer._order = {
12040                         layer: layer,
12041                         prev: this._drawLast,
12042                         next: null
12043                 };
12044                 if (this._drawLast) { this._drawLast.next = order; }
12045                 this._drawLast = order;
12046                 this._drawFirst = this._drawFirst || this._drawLast;
12047         },
12048
12049         _addPath: function (layer) {
12050                 this._requestRedraw(layer);
12051         },
12052
12053         _removePath: function (layer) {
12054                 var order = layer._order;
12055                 var next = order.next;
12056                 var prev = order.prev;
12057
12058                 if (next) {
12059                         next.prev = prev;
12060                 } else {
12061                         this._drawLast = prev;
12062                 }
12063                 if (prev) {
12064                         prev.next = next;
12065                 } else {
12066                         this._drawFirst = next;
12067                 }
12068
12069                 delete this._drawnLayers[layer._leaflet_id];
12070
12071                 delete layer._order;
12072
12073                 delete this._layers[stamp(layer)];
12074
12075                 this._requestRedraw(layer);
12076         },
12077
12078         _updatePath: function (layer) {
12079                 // Redraw the union of the layer's old pixel
12080                 // bounds and the new pixel bounds.
12081                 this._extendRedrawBounds(layer);
12082                 layer._project();
12083                 layer._update();
12084                 // The redraw will extend the redraw bounds
12085                 // with the new pixel bounds.
12086                 this._requestRedraw(layer);
12087         },
12088
12089         _updateStyle: function (layer) {
12090                 this._updateDashArray(layer);
12091                 this._requestRedraw(layer);
12092         },
12093
12094         _updateDashArray: function (layer) {
12095                 if (typeof layer.options.dashArray === 'string') {
12096                         var parts = layer.options.dashArray.split(/[, ]+/),
12097                             dashArray = [],
12098                             i;
12099                         for (i = 0; i < parts.length; i++) {
12100                                 dashArray.push(Number(parts[i]));
12101                         }
12102                         layer.options._dashArray = dashArray;
12103                 } else {
12104                         layer.options._dashArray = layer.options.dashArray;
12105                 }
12106         },
12107
12108         _requestRedraw: function (layer) {
12109                 if (!this._map) { return; }
12110
12111                 this._extendRedrawBounds(layer);
12112                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
12113         },
12114
12115         _extendRedrawBounds: function (layer) {
12116                 if (layer._pxBounds) {
12117                         var padding = (layer.options.weight || 0) + 1;
12118                         this._redrawBounds = this._redrawBounds || new Bounds();
12119                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
12120                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
12121                 }
12122         },
12123
12124         _redraw: function () {
12125                 this._redrawRequest = null;
12126
12127                 if (this._redrawBounds) {
12128                         this._redrawBounds.min._floor();
12129                         this._redrawBounds.max._ceil();
12130                 }
12131
12132                 this._clear(); // clear layers in redraw bounds
12133                 this._draw(); // draw layers
12134
12135                 this._redrawBounds = null;
12136         },
12137
12138         _clear: function () {
12139                 var bounds = this._redrawBounds;
12140                 if (bounds) {
12141                         var size = bounds.getSize();
12142                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
12143                 } else {
12144                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
12145                 }
12146         },
12147
12148         _draw: function () {
12149                 var layer, bounds = this._redrawBounds;
12150                 this._ctx.save();
12151                 if (bounds) {
12152                         var size = bounds.getSize();
12153                         this._ctx.beginPath();
12154                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
12155                         this._ctx.clip();
12156                 }
12157
12158                 this._drawing = true;
12159
12160                 for (var order = this._drawFirst; order; order = order.next) {
12161                         layer = order.layer;
12162                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
12163                                 layer._updatePath();
12164                         }
12165                 }
12166
12167                 this._drawing = false;
12168
12169                 this._ctx.restore();  // Restore state before clipping.
12170         },
12171
12172         _updatePoly: function (layer, closed) {
12173                 if (!this._drawing) { return; }
12174
12175                 var i, j, len2, p,
12176                     parts = layer._parts,
12177                     len = parts.length,
12178                     ctx = this._ctx;
12179
12180                 if (!len) { return; }
12181
12182                 this._drawnLayers[layer._leaflet_id] = layer;
12183
12184                 ctx.beginPath();
12185
12186                 for (i = 0; i < len; i++) {
12187                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
12188                                 p = parts[i][j];
12189                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
12190                         }
12191                         if (closed) {
12192                                 ctx.closePath();
12193                         }
12194                 }
12195
12196                 this._fillStroke(ctx, layer);
12197
12198                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
12199         },
12200
12201         _updateCircle: function (layer) {
12202
12203                 if (!this._drawing || layer._empty()) { return; }
12204
12205                 var p = layer._point,
12206                     ctx = this._ctx,
12207                     r = Math.max(Math.round(layer._radius), 1),
12208                     s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;
12209
12210                 this._drawnLayers[layer._leaflet_id] = layer;
12211
12212                 if (s !== 1) {
12213                         ctx.save();
12214                         ctx.scale(1, s);
12215                 }
12216
12217                 ctx.beginPath();
12218                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
12219
12220                 if (s !== 1) {
12221                         ctx.restore();
12222                 }
12223
12224                 this._fillStroke(ctx, layer);
12225         },
12226
12227         _fillStroke: function (ctx, layer) {
12228                 var options = layer.options;
12229
12230                 if (options.fill) {
12231                         ctx.globalAlpha = options.fillOpacity;
12232                         ctx.fillStyle = options.fillColor || options.color;
12233                         ctx.fill(options.fillRule || 'evenodd');
12234                 }
12235
12236                 if (options.stroke && options.weight !== 0) {
12237                         if (ctx.setLineDash) {
12238                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
12239                         }
12240                         ctx.globalAlpha = options.opacity;
12241                         ctx.lineWidth = options.weight;
12242                         ctx.strokeStyle = options.color;
12243                         ctx.lineCap = options.lineCap;
12244                         ctx.lineJoin = options.lineJoin;
12245                         ctx.stroke();
12246                 }
12247         },
12248
12249         // Canvas obviously doesn't have mouse events for individual drawn objects,
12250         // so we emulate that by calculating what's under the mouse on mousemove/click manually
12251
12252         _onClick: function (e) {
12253                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
12254
12255                 for (var order = this._drawFirst; order; order = order.next) {
12256                         layer = order.layer;
12257                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12258                                 clickedLayer = layer;
12259                         }
12260                 }
12261                 if (clickedLayer)  {
12262                         fakeStop(e);
12263                         this._fireEvent([clickedLayer], e);
12264                 }
12265         },
12266
12267         _onMouseMove: function (e) {
12268                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12269
12270                 var point = this._map.mouseEventToLayerPoint(e);
12271                 this._handleMouseHover(e, point);
12272         },
12273
12274
12275         _handleMouseOut: function (e) {
12276                 var layer = this._hoveredLayer;
12277                 if (layer) {
12278                         // if we're leaving the layer, fire mouseout
12279                         removeClass(this._container, 'leaflet-interactive');
12280                         this._fireEvent([layer], e, 'mouseout');
12281                         this._hoveredLayer = null;
12282                 }
12283         },
12284
12285         _handleMouseHover: function (e, point) {
12286                 var layer, candidateHoveredLayer;
12287
12288                 for (var order = this._drawFirst; order; order = order.next) {
12289                         layer = order.layer;
12290                         if (layer.options.interactive && layer._containsPoint(point)) {
12291                                 candidateHoveredLayer = layer;
12292                         }
12293                 }
12294
12295                 if (candidateHoveredLayer !== this._hoveredLayer) {
12296                         this._handleMouseOut(e);
12297
12298                         if (candidateHoveredLayer) {
12299                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12300                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12301                                 this._hoveredLayer = candidateHoveredLayer;
12302                         }
12303                 }
12304
12305                 if (this._hoveredLayer) {
12306                         this._fireEvent([this._hoveredLayer], e);
12307                 }
12308         },
12309
12310         _fireEvent: function (layers, e, type) {
12311                 this._map._fireDOMEvent(e, type || e.type, layers);
12312         },
12313
12314         _bringToFront: function (layer) {
12315                 var order = layer._order;
12316                 var next = order.next;
12317                 var prev = order.prev;
12318
12319                 if (next) {
12320                         next.prev = prev;
12321                 } else {
12322                         // Already last
12323                         return;
12324                 }
12325                 if (prev) {
12326                         prev.next = next;
12327                 } else if (next) {
12328                         // Update first entry unless this is the
12329                         // single entry
12330                         this._drawFirst = next;
12331                 }
12332
12333                 order.prev = this._drawLast;
12334                 this._drawLast.next = order;
12335
12336                 order.next = null;
12337                 this._drawLast = order;
12338
12339                 this._requestRedraw(layer);
12340         },
12341
12342         _bringToBack: function (layer) {
12343                 var order = layer._order;
12344                 var next = order.next;
12345                 var prev = order.prev;
12346
12347                 if (prev) {
12348                         prev.next = next;
12349                 } else {
12350                         // Already first
12351                         return;
12352                 }
12353                 if (next) {
12354                         next.prev = prev;
12355                 } else if (prev) {
12356                         // Update last entry unless this is the
12357                         // single entry
12358                         this._drawLast = prev;
12359                 }
12360
12361                 order.prev = null;
12362
12363                 order.next = this._drawFirst;
12364                 this._drawFirst.prev = order;
12365                 this._drawFirst = order;
12366
12367                 this._requestRedraw(layer);
12368         }
12369 });
12370
12371 // @factory L.canvas(options?: Renderer options)
12372 // Creates a Canvas renderer with the given options.
12373 function canvas$1(options) {
12374         return canvas ? new Canvas(options) : null;
12375 }
12376
12377 /*
12378  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12379  */
12380
12381
12382 var vmlCreate = (function () {
12383         try {
12384                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12385                 return function (name) {
12386                         return document.createElement('<lvml:' + name + ' class="lvml">');
12387                 };
12388         } catch (e) {
12389                 return function (name) {
12390                         return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12391                 };
12392         }
12393 })();
12394
12395
12396 /*
12397  * @class SVG
12398  *
12399  * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
12400  *
12401  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12402  * with old versions of Internet Explorer.
12403  */
12404
12405 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12406 var vmlMixin = {
12407
12408         _initContainer: function () {
12409                 this._container = create$1('div', 'leaflet-vml-container');
12410         },
12411
12412         _update: function () {
12413                 if (this._map._animatingZoom) { return; }
12414                 Renderer.prototype._update.call(this);
12415                 this.fire('update');
12416         },
12417
12418         _initPath: function (layer) {
12419                 var container = layer._container = vmlCreate('shape');
12420
12421                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12422
12423                 container.coordsize = '1 1';
12424
12425                 layer._path = vmlCreate('path');
12426                 container.appendChild(layer._path);
12427
12428                 this._updateStyle(layer);
12429                 this._layers[stamp(layer)] = layer;
12430         },
12431
12432         _addPath: function (layer) {
12433                 var container = layer._container;
12434                 this._container.appendChild(container);
12435
12436                 if (layer.options.interactive) {
12437                         layer.addInteractiveTarget(container);
12438                 }
12439         },
12440
12441         _removePath: function (layer) {
12442                 var container = layer._container;
12443                 remove(container);
12444                 layer.removeInteractiveTarget(container);
12445                 delete this._layers[stamp(layer)];
12446         },
12447
12448         _updateStyle: function (layer) {
12449                 var stroke = layer._stroke,
12450                     fill = layer._fill,
12451                     options = layer.options,
12452                     container = layer._container;
12453
12454                 container.stroked = !!options.stroke;
12455                 container.filled = !!options.fill;
12456
12457                 if (options.stroke) {
12458                         if (!stroke) {
12459                                 stroke = layer._stroke = vmlCreate('stroke');
12460                         }
12461                         container.appendChild(stroke);
12462                         stroke.weight = options.weight + 'px';
12463                         stroke.color = options.color;
12464                         stroke.opacity = options.opacity;
12465
12466                         if (options.dashArray) {
12467                                 stroke.dashStyle = isArray(options.dashArray) ?
12468                                     options.dashArray.join(' ') :
12469                                     options.dashArray.replace(/( *, *)/g, ' ');
12470                         } else {
12471                                 stroke.dashStyle = '';
12472                         }
12473                         stroke.endcap = options.lineCap.replace('butt', 'flat');
12474                         stroke.joinstyle = options.lineJoin;
12475
12476                 } else if (stroke) {
12477                         container.removeChild(stroke);
12478                         layer._stroke = null;
12479                 }
12480
12481                 if (options.fill) {
12482                         if (!fill) {
12483                                 fill = layer._fill = vmlCreate('fill');
12484                         }
12485                         container.appendChild(fill);
12486                         fill.color = options.fillColor || options.color;
12487                         fill.opacity = options.fillOpacity;
12488
12489                 } else if (fill) {
12490                         container.removeChild(fill);
12491                         layer._fill = null;
12492                 }
12493         },
12494
12495         _updateCircle: function (layer) {
12496                 var p = layer._point.round(),
12497                     r = Math.round(layer._radius),
12498                     r2 = Math.round(layer._radiusY || r);
12499
12500                 this._setPath(layer, layer._empty() ? 'M0 0' :
12501                         'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12502         },
12503
12504         _setPath: function (layer, path) {
12505                 layer._path.v = path;
12506         },
12507
12508         _bringToFront: function (layer) {
12509                 toFront(layer._container);
12510         },
12511
12512         _bringToBack: function (layer) {
12513                 toBack(layer._container);
12514         }
12515 };
12516
12517 var create$2 = vml ? vmlCreate : svgCreate;
12518
12519 /*
12520  * @class SVG
12521  * @inherits Renderer
12522  * @aka L.SVG
12523  *
12524  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12525  * Inherits `Renderer`.
12526  *
12527  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12528  * available in all web browsers, notably Android 2.x and 3.x.
12529  *
12530  * Although SVG is not available on IE7 and IE8, these browsers support
12531  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12532  * (a now deprecated technology), and the SVG renderer will fall back to VML in
12533  * this case.
12534  *
12535  * @example
12536  *
12537  * Use SVG by default for all paths in the map:
12538  *
12539  * ```js
12540  * var map = L.map('map', {
12541  *      renderer: L.svg()
12542  * });
12543  * ```
12544  *
12545  * Use a SVG renderer with extra padding for specific vector geometries:
12546  *
12547  * ```js
12548  * var map = L.map('map');
12549  * var myRenderer = L.svg({ padding: 0.5 });
12550  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12551  * var circle = L.circle( center, { renderer: myRenderer } );
12552  * ```
12553  */
12554
12555 var SVG = Renderer.extend({
12556
12557         getEvents: function () {
12558                 var events = Renderer.prototype.getEvents.call(this);
12559                 events.zoomstart = this._onZoomStart;
12560                 return events;
12561         },
12562
12563         _initContainer: function () {
12564                 this._container = create$2('svg');
12565
12566                 // makes it possible to click through svg root; we'll reset it back in individual paths
12567                 this._container.setAttribute('pointer-events', 'none');
12568
12569                 this._rootGroup = create$2('g');
12570                 this._container.appendChild(this._rootGroup);
12571         },
12572
12573         _destroyContainer: function () {
12574                 remove(this._container);
12575                 off(this._container);
12576                 delete this._container;
12577                 delete this._rootGroup;
12578                 delete this._svgSize;
12579         },
12580
12581         _onZoomStart: function () {
12582                 // Drag-then-pinch interactions might mess up the center and zoom.
12583                 // In this case, the easiest way to prevent this is re-do the renderer
12584                 //   bounds and padding when the zooming starts.
12585                 this._update();
12586         },
12587
12588         _update: function () {
12589                 if (this._map._animatingZoom && this._bounds) { return; }
12590
12591                 Renderer.prototype._update.call(this);
12592
12593                 var b = this._bounds,
12594                     size = b.getSize(),
12595                     container = this._container;
12596
12597                 // set size of svg-container if changed
12598                 if (!this._svgSize || !this._svgSize.equals(size)) {
12599                         this._svgSize = size;
12600                         container.setAttribute('width', size.x);
12601                         container.setAttribute('height', size.y);
12602                 }
12603
12604                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12605                 setPosition(container, b.min);
12606                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12607
12608                 this.fire('update');
12609         },
12610
12611         // methods below are called by vector layers implementations
12612
12613         _initPath: function (layer) {
12614                 var path = layer._path = create$2('path');
12615
12616                 // @namespace Path
12617                 // @option className: String = null
12618                 // Custom class name set on an element. Only for SVG renderer.
12619                 if (layer.options.className) {
12620                         addClass(path, layer.options.className);
12621                 }
12622
12623                 if (layer.options.interactive) {
12624                         addClass(path, 'leaflet-interactive');
12625                 }
12626
12627                 this._updateStyle(layer);
12628                 this._layers[stamp(layer)] = layer;
12629         },
12630
12631         _addPath: function (layer) {
12632                 if (!this._rootGroup) { this._initContainer(); }
12633                 this._rootGroup.appendChild(layer._path);
12634                 layer.addInteractiveTarget(layer._path);
12635         },
12636
12637         _removePath: function (layer) {
12638                 remove(layer._path);
12639                 layer.removeInteractiveTarget(layer._path);
12640                 delete this._layers[stamp(layer)];
12641         },
12642
12643         _updatePath: function (layer) {
12644                 layer._project();
12645                 layer._update();
12646         },
12647
12648         _updateStyle: function (layer) {
12649                 var path = layer._path,
12650                     options = layer.options;
12651
12652                 if (!path) { return; }
12653
12654                 if (options.stroke) {
12655                         path.setAttribute('stroke', options.color);
12656                         path.setAttribute('stroke-opacity', options.opacity);
12657                         path.setAttribute('stroke-width', options.weight);
12658                         path.setAttribute('stroke-linecap', options.lineCap);
12659                         path.setAttribute('stroke-linejoin', options.lineJoin);
12660
12661                         if (options.dashArray) {
12662                                 path.setAttribute('stroke-dasharray', options.dashArray);
12663                         } else {
12664                                 path.removeAttribute('stroke-dasharray');
12665                         }
12666
12667                         if (options.dashOffset) {
12668                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
12669                         } else {
12670                                 path.removeAttribute('stroke-dashoffset');
12671                         }
12672                 } else {
12673                         path.setAttribute('stroke', 'none');
12674                 }
12675
12676                 if (options.fill) {
12677                         path.setAttribute('fill', options.fillColor || options.color);
12678                         path.setAttribute('fill-opacity', options.fillOpacity);
12679                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12680                 } else {
12681                         path.setAttribute('fill', 'none');
12682                 }
12683         },
12684
12685         _updatePoly: function (layer, closed) {
12686                 this._setPath(layer, pointsToPath(layer._parts, closed));
12687         },
12688
12689         _updateCircle: function (layer) {
12690                 var p = layer._point,
12691                     r = Math.max(Math.round(layer._radius), 1),
12692                     r2 = Math.max(Math.round(layer._radiusY), 1) || r,
12693                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12694
12695                 // drawing a circle with two half-arcs
12696                 var d = layer._empty() ? 'M0 0' :
12697                         'M' + (p.x - r) + ',' + p.y +
12698                         arc + (r * 2) + ',0 ' +
12699                         arc + (-r * 2) + ',0 ';
12700
12701                 this._setPath(layer, d);
12702         },
12703
12704         _setPath: function (layer, path) {
12705                 layer._path.setAttribute('d', path);
12706         },
12707
12708         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12709         _bringToFront: function (layer) {
12710                 toFront(layer._path);
12711         },
12712
12713         _bringToBack: function (layer) {
12714                 toBack(layer._path);
12715         }
12716 });
12717
12718 if (vml) {
12719         SVG.include(vmlMixin);
12720 }
12721
12722 // @namespace SVG
12723 // @factory L.svg(options?: Renderer options)
12724 // Creates a SVG renderer with the given options.
12725 function svg$1(options) {
12726         return svg || vml ? new SVG(options) : null;
12727 }
12728
12729 Map.include({
12730         // @namespace Map; @method getRenderer(layer: Path): Renderer
12731         // Returns the instance of `Renderer` that should be used to render the given
12732         // `Path`. It will ensure that the `renderer` options of the map and paths
12733         // are respected, and that the renderers do exist on the map.
12734         getRenderer: function (layer) {
12735                 // @namespace Path; @option renderer: Renderer
12736                 // Use this specific instance of `Renderer` for this path. Takes
12737                 // precedence over the map's [default renderer](#map-renderer).
12738                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12739
12740                 if (!renderer) {
12741                         renderer = this._renderer = this._createRenderer();
12742                 }
12743
12744                 if (!this.hasLayer(renderer)) {
12745                         this.addLayer(renderer);
12746                 }
12747                 return renderer;
12748         },
12749
12750         _getPaneRenderer: function (name) {
12751                 if (name === 'overlayPane' || name === undefined) {
12752                         return false;
12753                 }
12754
12755                 var renderer = this._paneRenderers[name];
12756                 if (renderer === undefined) {
12757                         renderer = this._createRenderer({pane: name});
12758                         this._paneRenderers[name] = renderer;
12759                 }
12760                 return renderer;
12761         },
12762
12763         _createRenderer: function (options) {
12764                 // @namespace Map; @option preferCanvas: Boolean = false
12765                 // Whether `Path`s should be rendered on a `Canvas` renderer.
12766                 // By default, all `Path`s are rendered in a `SVG` renderer.
12767                 return (this.options.preferCanvas && canvas$1(options)) || svg$1(options);
12768         }
12769 });
12770
12771 /*
12772  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12773  */
12774
12775 /*
12776  * @class Rectangle
12777  * @aka L.Rectangle
12778  * @inherits Polygon
12779  *
12780  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12781  *
12782  * @example
12783  *
12784  * ```js
12785  * // define rectangle geographical bounds
12786  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12787  *
12788  * // create an orange rectangle
12789  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12790  *
12791  * // zoom the map to the rectangle bounds
12792  * map.fitBounds(bounds);
12793  * ```
12794  *
12795  */
12796
12797
12798 var Rectangle = Polygon.extend({
12799         initialize: function (latLngBounds, options) {
12800                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12801         },
12802
12803         // @method setBounds(latLngBounds: LatLngBounds): this
12804         // Redraws the rectangle with the passed bounds.
12805         setBounds: function (latLngBounds) {
12806                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12807         },
12808
12809         _boundsToLatLngs: function (latLngBounds) {
12810                 latLngBounds = toLatLngBounds(latLngBounds);
12811                 return [
12812                         latLngBounds.getSouthWest(),
12813                         latLngBounds.getNorthWest(),
12814                         latLngBounds.getNorthEast(),
12815                         latLngBounds.getSouthEast()
12816                 ];
12817         }
12818 });
12819
12820
12821 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12822 function rectangle(latLngBounds, options) {
12823         return new Rectangle(latLngBounds, options);
12824 }
12825
12826 SVG.create = create$2;
12827 SVG.pointsToPath = pointsToPath;
12828
12829 GeoJSON.geometryToLayer = geometryToLayer;
12830 GeoJSON.coordsToLatLng = coordsToLatLng;
12831 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12832 GeoJSON.latLngToCoords = latLngToCoords;
12833 GeoJSON.latLngsToCoords = latLngsToCoords;
12834 GeoJSON.getFeature = getFeature;
12835 GeoJSON.asFeature = asFeature;
12836
12837 /*
12838  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12839  * (zoom to a selected bounding box), enabled by default.
12840  */
12841
12842 // @namespace Map
12843 // @section Interaction Options
12844 Map.mergeOptions({
12845         // @option boxZoom: Boolean = true
12846         // Whether the map can be zoomed to a rectangular area specified by
12847         // dragging the mouse while pressing the shift key.
12848         boxZoom: true
12849 });
12850
12851 var BoxZoom = Handler.extend({
12852         initialize: function (map) {
12853                 this._map = map;
12854                 this._container = map._container;
12855                 this._pane = map._panes.overlayPane;
12856                 this._resetStateTimeout = 0;
12857                 map.on('unload', this._destroy, this);
12858         },
12859
12860         addHooks: function () {
12861                 on(this._container, 'mousedown', this._onMouseDown, this);
12862         },
12863
12864         removeHooks: function () {
12865                 off(this._container, 'mousedown', this._onMouseDown, this);
12866         },
12867
12868         moved: function () {
12869                 return this._moved;
12870         },
12871
12872         _destroy: function () {
12873                 remove(this._pane);
12874                 delete this._pane;
12875         },
12876
12877         _resetState: function () {
12878                 this._resetStateTimeout = 0;
12879                 this._moved = false;
12880         },
12881
12882         _clearDeferredResetState: function () {
12883                 if (this._resetStateTimeout !== 0) {
12884                         clearTimeout(this._resetStateTimeout);
12885                         this._resetStateTimeout = 0;
12886                 }
12887         },
12888
12889         _onMouseDown: function (e) {
12890                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12891
12892                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12893                 // will interrupt the interaction and orphan a box element in the container.
12894                 this._clearDeferredResetState();
12895                 this._resetState();
12896
12897                 disableTextSelection();
12898                 disableImageDrag();
12899
12900                 this._startPoint = this._map.mouseEventToContainerPoint(e);
12901
12902                 on(document, {
12903                         contextmenu: stop,
12904                         mousemove: this._onMouseMove,
12905                         mouseup: this._onMouseUp,
12906                         keydown: this._onKeyDown
12907                 }, this);
12908         },
12909
12910         _onMouseMove: function (e) {
12911                 if (!this._moved) {
12912                         this._moved = true;
12913
12914                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
12915                         addClass(this._container, 'leaflet-crosshair');
12916
12917                         this._map.fire('boxzoomstart');
12918                 }
12919
12920                 this._point = this._map.mouseEventToContainerPoint(e);
12921
12922                 var bounds = new Bounds(this._point, this._startPoint),
12923                     size = bounds.getSize();
12924
12925                 setPosition(this._box, bounds.min);
12926
12927                 this._box.style.width  = size.x + 'px';
12928                 this._box.style.height = size.y + 'px';
12929         },
12930
12931         _finish: function () {
12932                 if (this._moved) {
12933                         remove(this._box);
12934                         removeClass(this._container, 'leaflet-crosshair');
12935                 }
12936
12937                 enableTextSelection();
12938                 enableImageDrag();
12939
12940                 off(document, {
12941                         contextmenu: stop,
12942                         mousemove: this._onMouseMove,
12943                         mouseup: this._onMouseUp,
12944                         keydown: this._onKeyDown
12945                 }, this);
12946         },
12947
12948         _onMouseUp: function (e) {
12949                 if ((e.which !== 1) && (e.button !== 1)) { return; }
12950
12951                 this._finish();
12952
12953                 if (!this._moved) { return; }
12954                 // Postpone to next JS tick so internal click event handling
12955                 // still see it as "moved".
12956                 this._clearDeferredResetState();
12957                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12958
12959                 var bounds = new LatLngBounds(
12960                         this._map.containerPointToLatLng(this._startPoint),
12961                         this._map.containerPointToLatLng(this._point));
12962
12963                 this._map
12964                         .fitBounds(bounds)
12965                         .fire('boxzoomend', {boxZoomBounds: bounds});
12966         },
12967
12968         _onKeyDown: function (e) {
12969                 if (e.keyCode === 27) {
12970                         this._finish();
12971                 }
12972         }
12973 });
12974
12975 // @section Handlers
12976 // @property boxZoom: Handler
12977 // Box (shift-drag with mouse) zoom handler.
12978 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12979
12980 /*
12981  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12982  */
12983
12984 // @namespace Map
12985 // @section Interaction Options
12986
12987 Map.mergeOptions({
12988         // @option doubleClickZoom: Boolean|String = true
12989         // Whether the map can be zoomed in by double clicking on it and
12990         // zoomed out by double clicking while holding shift. If passed
12991         // `'center'`, double-click zoom will zoom to the center of the
12992         //  view regardless of where the mouse was.
12993         doubleClickZoom: true
12994 });
12995
12996 var DoubleClickZoom = Handler.extend({
12997         addHooks: function () {
12998                 this._map.on('dblclick', this._onDoubleClick, this);
12999         },
13000
13001         removeHooks: function () {
13002                 this._map.off('dblclick', this._onDoubleClick, this);
13003         },
13004
13005         _onDoubleClick: function (e) {
13006                 var map = this._map,
13007                     oldZoom = map.getZoom(),
13008                     delta = map.options.zoomDelta,
13009                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
13010
13011                 if (map.options.doubleClickZoom === 'center') {
13012                         map.setZoom(zoom);
13013                 } else {
13014                         map.setZoomAround(e.containerPoint, zoom);
13015                 }
13016         }
13017 });
13018
13019 // @section Handlers
13020 //
13021 // Map properties include interaction handlers that allow you to control
13022 // interaction behavior in runtime, enabling or disabling certain features such
13023 // as dragging or touch zoom (see `Handler` methods). For example:
13024 //
13025 // ```js
13026 // map.doubleClickZoom.disable();
13027 // ```
13028 //
13029 // @property doubleClickZoom: Handler
13030 // Double click zoom handler.
13031 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
13032
13033 /*
13034  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
13035  */
13036
13037 // @namespace Map
13038 // @section Interaction Options
13039 Map.mergeOptions({
13040         // @option dragging: Boolean = true
13041         // Whether the map be draggable with mouse/touch or not.
13042         dragging: true,
13043
13044         // @section Panning Inertia Options
13045         // @option inertia: Boolean = *
13046         // If enabled, panning of the map will have an inertia effect where
13047         // the map builds momentum while dragging and continues moving in
13048         // the same direction for some time. Feels especially nice on touch
13049         // devices. Enabled by default unless running on old Android devices.
13050         inertia: !android23,
13051
13052         // @option inertiaDeceleration: Number = 3000
13053         // The rate with which the inertial movement slows down, in pixels/second².
13054         inertiaDeceleration: 3400, // px/s^2
13055
13056         // @option inertiaMaxSpeed: Number = Infinity
13057         // Max speed of the inertial movement, in pixels/second.
13058         inertiaMaxSpeed: Infinity, // px/s
13059
13060         // @option easeLinearity: Number = 0.2
13061         easeLinearity: 0.2,
13062
13063         // TODO refactor, move to CRS
13064         // @option worldCopyJump: Boolean = false
13065         // With this option enabled, the map tracks when you pan to another "copy"
13066         // of the world and seamlessly jumps to the original one so that all overlays
13067         // like markers and vector layers are still visible.
13068         worldCopyJump: false,
13069
13070         // @option maxBoundsViscosity: Number = 0.0
13071         // If `maxBounds` is set, this option will control how solid the bounds
13072         // are when dragging the map around. The default value of `0.0` allows the
13073         // user to drag outside the bounds at normal speed, higher values will
13074         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
13075         // solid, preventing the user from dragging outside the bounds.
13076         maxBoundsViscosity: 0.0
13077 });
13078
13079 var Drag = Handler.extend({
13080         addHooks: function () {
13081                 if (!this._draggable) {
13082                         var map = this._map;
13083
13084                         this._draggable = new Draggable(map._mapPane, map._container);
13085
13086                         this._draggable.on({
13087                                 dragstart: this._onDragStart,
13088                                 drag: this._onDrag,
13089                                 dragend: this._onDragEnd
13090                         }, this);
13091
13092                         this._draggable.on('predrag', this._onPreDragLimit, this);
13093                         if (map.options.worldCopyJump) {
13094                                 this._draggable.on('predrag', this._onPreDragWrap, this);
13095                                 map.on('zoomend', this._onZoomEnd, this);
13096
13097                                 map.whenReady(this._onZoomEnd, this);
13098                         }
13099                 }
13100                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
13101                 this._draggable.enable();
13102                 this._positions = [];
13103                 this._times = [];
13104         },
13105
13106         removeHooks: function () {
13107                 removeClass(this._map._container, 'leaflet-grab');
13108                 removeClass(this._map._container, 'leaflet-touch-drag');
13109                 this._draggable.disable();
13110         },
13111
13112         moved: function () {
13113                 return this._draggable && this._draggable._moved;
13114         },
13115
13116         moving: function () {
13117                 return this._draggable && this._draggable._moving;
13118         },
13119
13120         _onDragStart: function () {
13121                 var map = this._map;
13122
13123                 map._stop();
13124                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
13125                         var bounds = toLatLngBounds(this._map.options.maxBounds);
13126
13127                         this._offsetLimit = toBounds(
13128                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
13129                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
13130                                         .add(this._map.getSize()));
13131
13132                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
13133                 } else {
13134                         this._offsetLimit = null;
13135                 }
13136
13137                 map
13138                     .fire('movestart')
13139                     .fire('dragstart');
13140
13141                 if (map.options.inertia) {
13142                         this._positions = [];
13143                         this._times = [];
13144                 }
13145         },
13146
13147         _onDrag: function (e) {
13148                 if (this._map.options.inertia) {
13149                         var time = this._lastTime = +new Date(),
13150                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
13151
13152                         this._positions.push(pos);
13153                         this._times.push(time);
13154
13155                         this._prunePositions(time);
13156                 }
13157
13158                 this._map
13159                     .fire('move', e)
13160                     .fire('drag', e);
13161         },
13162
13163         _prunePositions: function (time) {
13164                 while (this._positions.length > 1 && time - this._times[0] > 50) {
13165                         this._positions.shift();
13166                         this._times.shift();
13167                 }
13168         },
13169
13170         _onZoomEnd: function () {
13171                 var pxCenter = this._map.getSize().divideBy(2),
13172                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
13173
13174                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
13175                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
13176         },
13177
13178         _viscousLimit: function (value, threshold) {
13179                 return value - (value - threshold) * this._viscosity;
13180         },
13181
13182         _onPreDragLimit: function () {
13183                 if (!this._viscosity || !this._offsetLimit) { return; }
13184
13185                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
13186
13187                 var limit = this._offsetLimit;
13188                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
13189                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
13190                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
13191                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
13192
13193                 this._draggable._newPos = this._draggable._startPos.add(offset);
13194         },
13195
13196         _onPreDragWrap: function () {
13197                 // TODO refactor to be able to adjust map pane position after zoom
13198                 var worldWidth = this._worldWidth,
13199                     halfWidth = Math.round(worldWidth / 2),
13200                     dx = this._initialWorldOffset,
13201                     x = this._draggable._newPos.x,
13202                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
13203                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
13204                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
13205
13206                 this._draggable._absPos = this._draggable._newPos.clone();
13207                 this._draggable._newPos.x = newX;
13208         },
13209
13210         _onDragEnd: function (e) {
13211                 var map = this._map,
13212                     options = map.options,
13213
13214                     noInertia = !options.inertia || this._times.length < 2;
13215
13216                 map.fire('dragend', e);
13217
13218                 if (noInertia) {
13219                         map.fire('moveend');
13220
13221                 } else {
13222                         this._prunePositions(+new Date());
13223
13224                         var direction = this._lastPos.subtract(this._positions[0]),
13225                             duration = (this._lastTime - this._times[0]) / 1000,
13226                             ease = options.easeLinearity,
13227
13228                             speedVector = direction.multiplyBy(ease / duration),
13229                             speed = speedVector.distanceTo([0, 0]),
13230
13231                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
13232                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
13233
13234                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
13235                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
13236
13237                         if (!offset.x && !offset.y) {
13238                                 map.fire('moveend');
13239
13240                         } else {
13241                                 offset = map._limitOffset(offset, map.options.maxBounds);
13242
13243                                 requestAnimFrame(function () {
13244                                         map.panBy(offset, {
13245                                                 duration: decelerationDuration,
13246                                                 easeLinearity: ease,
13247                                                 noMoveStart: true,
13248                                                 animate: true
13249                                         });
13250                                 });
13251                         }
13252                 }
13253         }
13254 });
13255
13256 // @section Handlers
13257 // @property dragging: Handler
13258 // Map dragging handler (by both mouse and touch).
13259 Map.addInitHook('addHandler', 'dragging', Drag);
13260
13261 /*
13262  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
13263  */
13264
13265 // @namespace Map
13266 // @section Keyboard Navigation Options
13267 Map.mergeOptions({
13268         // @option keyboard: Boolean = true
13269         // Makes the map focusable and allows users to navigate the map with keyboard
13270         // arrows and `+`/`-` keys.
13271         keyboard: true,
13272
13273         // @option keyboardPanDelta: Number = 80
13274         // Amount of pixels to pan when pressing an arrow key.
13275         keyboardPanDelta: 80
13276 });
13277
13278 var Keyboard = Handler.extend({
13279
13280         keyCodes: {
13281                 left:    [37],
13282                 right:   [39],
13283                 down:    [40],
13284                 up:      [38],
13285                 zoomIn:  [187, 107, 61, 171],
13286                 zoomOut: [189, 109, 54, 173]
13287         },
13288
13289         initialize: function (map) {
13290                 this._map = map;
13291
13292                 this._setPanDelta(map.options.keyboardPanDelta);
13293                 this._setZoomDelta(map.options.zoomDelta);
13294         },
13295
13296         addHooks: function () {
13297                 var container = this._map._container;
13298
13299                 // make the container focusable by tabbing
13300                 if (container.tabIndex <= 0) {
13301                         container.tabIndex = '0';
13302                 }
13303
13304                 on(container, {
13305                         focus: this._onFocus,
13306                         blur: this._onBlur,
13307                         mousedown: this._onMouseDown
13308                 }, this);
13309
13310                 this._map.on({
13311                         focus: this._addHooks,
13312                         blur: this._removeHooks
13313                 }, this);
13314         },
13315
13316         removeHooks: function () {
13317                 this._removeHooks();
13318
13319                 off(this._map._container, {
13320                         focus: this._onFocus,
13321                         blur: this._onBlur,
13322                         mousedown: this._onMouseDown
13323                 }, this);
13324
13325                 this._map.off({
13326                         focus: this._addHooks,
13327                         blur: this._removeHooks
13328                 }, this);
13329         },
13330
13331         _onMouseDown: function () {
13332                 if (this._focused) { return; }
13333
13334                 var body = document.body,
13335                     docEl = document.documentElement,
13336                     top = body.scrollTop || docEl.scrollTop,
13337                     left = body.scrollLeft || docEl.scrollLeft;
13338
13339                 this._map._container.focus();
13340
13341                 window.scrollTo(left, top);
13342         },
13343
13344         _onFocus: function () {
13345                 this._focused = true;
13346                 this._map.fire('focus');
13347         },
13348
13349         _onBlur: function () {
13350                 this._focused = false;
13351                 this._map.fire('blur');
13352         },
13353
13354         _setPanDelta: function (panDelta) {
13355                 var keys = this._panKeys = {},
13356                     codes = this.keyCodes,
13357                     i, len;
13358
13359                 for (i = 0, len = codes.left.length; i < len; i++) {
13360                         keys[codes.left[i]] = [-1 * panDelta, 0];
13361                 }
13362                 for (i = 0, len = codes.right.length; i < len; i++) {
13363                         keys[codes.right[i]] = [panDelta, 0];
13364                 }
13365                 for (i = 0, len = codes.down.length; i < len; i++) {
13366                         keys[codes.down[i]] = [0, panDelta];
13367                 }
13368                 for (i = 0, len = codes.up.length; i < len; i++) {
13369                         keys[codes.up[i]] = [0, -1 * panDelta];
13370                 }
13371         },
13372
13373         _setZoomDelta: function (zoomDelta) {
13374                 var keys = this._zoomKeys = {},
13375                     codes = this.keyCodes,
13376                     i, len;
13377
13378                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13379                         keys[codes.zoomIn[i]] = zoomDelta;
13380                 }
13381                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13382                         keys[codes.zoomOut[i]] = -zoomDelta;
13383                 }
13384         },
13385
13386         _addHooks: function () {
13387                 on(document, 'keydown', this._onKeyDown, this);
13388         },
13389
13390         _removeHooks: function () {
13391                 off(document, 'keydown', this._onKeyDown, this);
13392         },
13393
13394         _onKeyDown: function (e) {
13395                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13396
13397                 var key = e.keyCode,
13398                     map = this._map,
13399                     offset;
13400
13401                 if (key in this._panKeys) {
13402                         if (!map._panAnim || !map._panAnim._inProgress) {
13403                                 offset = this._panKeys[key];
13404                                 if (e.shiftKey) {
13405                                         offset = toPoint(offset).multiplyBy(3);
13406                                 }
13407
13408                                 map.panBy(offset);
13409
13410                                 if (map.options.maxBounds) {
13411                                         map.panInsideBounds(map.options.maxBounds);
13412                                 }
13413                         }
13414                 } else if (key in this._zoomKeys) {
13415                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13416
13417                 } else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
13418                         map.closePopup();
13419
13420                 } else {
13421                         return;
13422                 }
13423
13424                 stop(e);
13425         }
13426 });
13427
13428 // @section Handlers
13429 // @section Handlers
13430 // @property keyboard: Handler
13431 // Keyboard navigation handler.
13432 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13433
13434 /*
13435  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13436  */
13437
13438 // @namespace Map
13439 // @section Interaction Options
13440 Map.mergeOptions({
13441         // @section Mousewheel options
13442         // @option scrollWheelZoom: Boolean|String = true
13443         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13444         // it will zoom to the center of the view regardless of where the mouse was.
13445         scrollWheelZoom: true,
13446
13447         // @option wheelDebounceTime: Number = 40
13448         // Limits the rate at which a wheel can fire (in milliseconds). By default
13449         // user can't zoom via wheel more often than once per 40 ms.
13450         wheelDebounceTime: 40,
13451
13452         // @option wheelPxPerZoomLevel: Number = 60
13453         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13454         // mean a change of one full zoom level. Smaller values will make wheel-zooming
13455         // faster (and vice versa).
13456         wheelPxPerZoomLevel: 60
13457 });
13458
13459 var ScrollWheelZoom = Handler.extend({
13460         addHooks: function () {
13461                 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13462
13463                 this._delta = 0;
13464         },
13465
13466         removeHooks: function () {
13467                 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13468         },
13469
13470         _onWheelScroll: function (e) {
13471                 var delta = getWheelDelta(e);
13472
13473                 var debounce = this._map.options.wheelDebounceTime;
13474
13475                 this._delta += delta;
13476                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13477
13478                 if (!this._startTime) {
13479                         this._startTime = +new Date();
13480                 }
13481
13482                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13483
13484                 clearTimeout(this._timer);
13485                 this._timer = setTimeout(bind(this._performZoom, this), left);
13486
13487                 stop(e);
13488         },
13489
13490         _performZoom: function () {
13491                 var map = this._map,
13492                     zoom = map.getZoom(),
13493                     snap = this._map.options.zoomSnap || 0;
13494
13495                 map._stop(); // stop panning and fly animations if any
13496
13497                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13498                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13499                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13500                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13501                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13502
13503                 this._delta = 0;
13504                 this._startTime = null;
13505
13506                 if (!delta) { return; }
13507
13508                 if (map.options.scrollWheelZoom === 'center') {
13509                         map.setZoom(zoom + delta);
13510                 } else {
13511                         map.setZoomAround(this._lastMousePos, zoom + delta);
13512                 }
13513         }
13514 });
13515
13516 // @section Handlers
13517 // @property scrollWheelZoom: Handler
13518 // Scroll wheel zoom handler.
13519 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13520
13521 /*
13522  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13523  */
13524
13525 // @namespace Map
13526 // @section Interaction Options
13527 Map.mergeOptions({
13528         // @section Touch interaction options
13529         // @option tap: Boolean = true
13530         // Enables mobile hacks for supporting instant taps (fixing 200ms click
13531         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13532         tap: true,
13533
13534         // @option tapTolerance: Number = 15
13535         // The max number of pixels a user can shift his finger during touch
13536         // for it to be considered a valid tap.
13537         tapTolerance: 15
13538 });
13539
13540 var Tap = Handler.extend({
13541         addHooks: function () {
13542                 on(this._map._container, 'touchstart', this._onDown, this);
13543         },
13544
13545         removeHooks: function () {
13546                 off(this._map._container, 'touchstart', this._onDown, this);
13547         },
13548
13549         _onDown: function (e) {
13550                 if (!e.touches) { return; }
13551
13552                 preventDefault(e);
13553
13554                 this._fireClick = true;
13555
13556                 // don't simulate click or track longpress if more than 1 touch
13557                 if (e.touches.length > 1) {
13558                         this._fireClick = false;
13559                         clearTimeout(this._holdTimeout);
13560                         return;
13561                 }
13562
13563                 var first = e.touches[0],
13564                     el = first.target;
13565
13566                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13567
13568                 // if touching a link, highlight it
13569                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13570                         addClass(el, 'leaflet-active');
13571                 }
13572
13573                 // simulate long hold but setting a timeout
13574                 this._holdTimeout = setTimeout(bind(function () {
13575                         if (this._isTapValid()) {
13576                                 this._fireClick = false;
13577                                 this._onUp();
13578                                 this._simulateEvent('contextmenu', first);
13579                         }
13580                 }, this), 1000);
13581
13582                 this._simulateEvent('mousedown', first);
13583
13584                 on(document, {
13585                         touchmove: this._onMove,
13586                         touchend: this._onUp
13587                 }, this);
13588         },
13589
13590         _onUp: function (e) {
13591                 clearTimeout(this._holdTimeout);
13592
13593                 off(document, {
13594                         touchmove: this._onMove,
13595                         touchend: this._onUp
13596                 }, this);
13597
13598                 if (this._fireClick && e && e.changedTouches) {
13599
13600                         var first = e.changedTouches[0],
13601                             el = first.target;
13602
13603                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13604                                 removeClass(el, 'leaflet-active');
13605                         }
13606
13607                         this._simulateEvent('mouseup', first);
13608
13609                         // simulate click if the touch didn't move too much
13610                         if (this._isTapValid()) {
13611                                 this._simulateEvent('click', first);
13612                         }
13613                 }
13614         },
13615
13616         _isTapValid: function () {
13617                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13618         },
13619
13620         _onMove: function (e) {
13621                 var first = e.touches[0];
13622                 this._newPos = new Point(first.clientX, first.clientY);
13623                 this._simulateEvent('mousemove', first);
13624         },
13625
13626         _simulateEvent: function (type, e) {
13627                 var simulatedEvent = document.createEvent('MouseEvents');
13628
13629                 simulatedEvent._simulated = true;
13630                 e.target._simulatedClick = true;
13631
13632                 simulatedEvent.initMouseEvent(
13633                         type, true, true, window, 1,
13634                         e.screenX, e.screenY,
13635                         e.clientX, e.clientY,
13636                         false, false, false, false, 0, null);
13637
13638                 e.target.dispatchEvent(simulatedEvent);
13639         }
13640 });
13641
13642 // @section Handlers
13643 // @property tap: Handler
13644 // Mobile touch hacks (quick tap and touch hold) handler.
13645 if (touch && !pointer) {
13646         Map.addInitHook('addHandler', 'tap', Tap);
13647 }
13648
13649 /*
13650  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13651  */
13652
13653 // @namespace Map
13654 // @section Interaction Options
13655 Map.mergeOptions({
13656         // @section Touch interaction options
13657         // @option touchZoom: Boolean|String = *
13658         // Whether the map can be zoomed by touch-dragging with two fingers. If
13659         // passed `'center'`, it will zoom to the center of the view regardless of
13660         // where the touch events (fingers) were. Enabled for touch-capable web
13661         // browsers except for old Androids.
13662         touchZoom: touch && !android23,
13663
13664         // @option bounceAtZoomLimits: Boolean = true
13665         // Set it to false if you don't want the map to zoom beyond min/max zoom
13666         // and then bounce back when pinch-zooming.
13667         bounceAtZoomLimits: true
13668 });
13669
13670 var TouchZoom = Handler.extend({
13671         addHooks: function () {
13672                 addClass(this._map._container, 'leaflet-touch-zoom');
13673                 on(this._map._container, 'touchstart', this._onTouchStart, this);
13674         },
13675
13676         removeHooks: function () {
13677                 removeClass(this._map._container, 'leaflet-touch-zoom');
13678                 off(this._map._container, 'touchstart', this._onTouchStart, this);
13679         },
13680
13681         _onTouchStart: function (e) {
13682                 var map = this._map;
13683                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13684
13685                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13686                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
13687
13688                 this._centerPoint = map.getSize()._divideBy(2);
13689                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13690                 if (map.options.touchZoom !== 'center') {
13691                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13692                 }
13693
13694                 this._startDist = p1.distanceTo(p2);
13695                 this._startZoom = map.getZoom();
13696
13697                 this._moved = false;
13698                 this._zooming = true;
13699
13700                 map._stop();
13701
13702                 on(document, 'touchmove', this._onTouchMove, this);
13703                 on(document, 'touchend', this._onTouchEnd, this);
13704
13705                 preventDefault(e);
13706         },
13707
13708         _onTouchMove: function (e) {
13709                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13710
13711                 var map = this._map,
13712                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
13713                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
13714                     scale = p1.distanceTo(p2) / this._startDist;
13715
13716                 this._zoom = map.getScaleZoom(scale, this._startZoom);
13717
13718                 if (!map.options.bounceAtZoomLimits && (
13719                         (this._zoom < map.getMinZoom() && scale < 1) ||
13720                         (this._zoom > map.getMaxZoom() && scale > 1))) {
13721                         this._zoom = map._limitZoom(this._zoom);
13722                 }
13723
13724                 if (map.options.touchZoom === 'center') {
13725                         this._center = this._startLatLng;
13726                         if (scale === 1) { return; }
13727                 } else {
13728                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13729                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13730                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13731                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13732                 }
13733
13734                 if (!this._moved) {
13735                         map._moveStart(true, false);
13736                         this._moved = true;
13737                 }
13738
13739                 cancelAnimFrame(this._animRequest);
13740
13741                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13742                 this._animRequest = requestAnimFrame(moveFn, this, true);
13743
13744                 preventDefault(e);
13745         },
13746
13747         _onTouchEnd: function () {
13748                 if (!this._moved || !this._zooming) {
13749                         this._zooming = false;
13750                         return;
13751                 }
13752
13753                 this._zooming = false;
13754                 cancelAnimFrame(this._animRequest);
13755
13756                 off(document, 'touchmove', this._onTouchMove);
13757                 off(document, 'touchend', this._onTouchEnd);
13758
13759                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13760                 if (this._map.options.zoomAnimation) {
13761                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13762                 } else {
13763                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13764                 }
13765         }
13766 });
13767
13768 // @section Handlers
13769 // @property touchZoom: Handler
13770 // Touch zoom handler.
13771 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13772
13773 Map.BoxZoom = BoxZoom;
13774 Map.DoubleClickZoom = DoubleClickZoom;
13775 Map.Drag = Drag;
13776 Map.Keyboard = Keyboard;
13777 Map.ScrollWheelZoom = ScrollWheelZoom;
13778 Map.Tap = Tap;
13779 Map.TouchZoom = TouchZoom;
13780
13781 Object.freeze = freeze;
13782
13783 exports.version = version;
13784 exports.Control = Control;
13785 exports.control = control;
13786 exports.Browser = Browser;
13787 exports.Evented = Evented;
13788 exports.Mixin = Mixin;
13789 exports.Util = Util;
13790 exports.Class = Class;
13791 exports.Handler = Handler;
13792 exports.extend = extend;
13793 exports.bind = bind;
13794 exports.stamp = stamp;
13795 exports.setOptions = setOptions;
13796 exports.DomEvent = DomEvent;
13797 exports.DomUtil = DomUtil;
13798 exports.PosAnimation = PosAnimation;
13799 exports.Draggable = Draggable;
13800 exports.LineUtil = LineUtil;
13801 exports.PolyUtil = PolyUtil;
13802 exports.Point = Point;
13803 exports.point = toPoint;
13804 exports.Bounds = Bounds;
13805 exports.bounds = toBounds;
13806 exports.Transformation = Transformation;
13807 exports.transformation = toTransformation;
13808 exports.Projection = index;
13809 exports.LatLng = LatLng;
13810 exports.latLng = toLatLng;
13811 exports.LatLngBounds = LatLngBounds;
13812 exports.latLngBounds = toLatLngBounds;
13813 exports.CRS = CRS;
13814 exports.GeoJSON = GeoJSON;
13815 exports.geoJSON = geoJSON;
13816 exports.geoJson = geoJson;
13817 exports.Layer = Layer;
13818 exports.LayerGroup = LayerGroup;
13819 exports.layerGroup = layerGroup;
13820 exports.FeatureGroup = FeatureGroup;
13821 exports.featureGroup = featureGroup;
13822 exports.ImageOverlay = ImageOverlay;
13823 exports.imageOverlay = imageOverlay;
13824 exports.VideoOverlay = VideoOverlay;
13825 exports.videoOverlay = videoOverlay;
13826 exports.DivOverlay = DivOverlay;
13827 exports.Popup = Popup;
13828 exports.popup = popup;
13829 exports.Tooltip = Tooltip;
13830 exports.tooltip = tooltip;
13831 exports.Icon = Icon;
13832 exports.icon = icon;
13833 exports.DivIcon = DivIcon;
13834 exports.divIcon = divIcon;
13835 exports.Marker = Marker;
13836 exports.marker = marker;
13837 exports.TileLayer = TileLayer;
13838 exports.tileLayer = tileLayer;
13839 exports.GridLayer = GridLayer;
13840 exports.gridLayer = gridLayer;
13841 exports.SVG = SVG;
13842 exports.svg = svg$1;
13843 exports.Renderer = Renderer;
13844 exports.Canvas = Canvas;
13845 exports.canvas = canvas$1;
13846 exports.Path = Path;
13847 exports.CircleMarker = CircleMarker;
13848 exports.circleMarker = circleMarker;
13849 exports.Circle = Circle;
13850 exports.circle = circle;
13851 exports.Polyline = Polyline;
13852 exports.polyline = polyline;
13853 exports.Polygon = Polygon;
13854 exports.polygon = polygon;
13855 exports.Rectangle = Rectangle;
13856 exports.rectangle = rectangle;
13857 exports.Map = Map;
13858 exports.map = createMap;
13859
13860 var oldL = window.L;
13861 exports.noConflict = function() {
13862         window.L = oldL;
13863         return this;
13864 }
13865
13866 // Always export us to window global (see #2364)
13867 window.L = exports;
13868
13869 })));
13870 //# sourceMappingURL=leaflet-src.js.map