]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Update to leaflet 1.2.0
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /* @preserve
2  * Leaflet 1.2.0, a JS library for interactive maps. http://leafletjs.com
3  * (c) 2010-2017 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4  */
5 (function (global, factory) {
6         typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
7         typeof define === 'function' && define.amd ? define(['exports'], factory) :
8         (factory((global.L = {})));
9 }(this, (function (exports) { 'use strict';
10
11 var version = "1.2.0";
12
13 /*
14  * @namespace Util
15  *
16  * Various utility functions, used by Leaflet internally.
17  */
18
19 var freeze = Object.freeze;
20 Object.freeze = function (obj) { return obj; };
21
22 // @function extend(dest: Object, src?: Object): Object
23 // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
24 function extend(dest) {
25         var i, j, len, src;
26
27         for (j = 1, len = arguments.length; j < len; j++) {
28                 src = arguments[j];
29                 for (i in src) {
30                         dest[i] = src[i];
31                 }
32         }
33         return dest;
34 }
35
36 // @function create(proto: Object, properties?: Object): Object
37 // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
38 var create = Object.create || (function () {
39         function F() {}
40         return function (proto) {
41                 F.prototype = proto;
42                 return new F();
43         };
44 })();
45
46 // @function bind(fn: Function, …): Function
47 // 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).
48 // Has a `L.bind()` shortcut.
49 function bind(fn, obj) {
50         var slice = Array.prototype.slice;
51
52         if (fn.bind) {
53                 return fn.bind.apply(fn, slice.call(arguments, 1));
54         }
55
56         var args = slice.call(arguments, 2);
57
58         return function () {
59                 return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
60         };
61 }
62
63 // @property lastId: Number
64 // Last unique ID used by [`stamp()`](#util-stamp)
65 var lastId = 0;
66
67 // @function stamp(obj: Object): Number
68 // Returns the unique ID of an object, assiging it one if it doesn't have it.
69 function stamp(obj) {
70         /*eslint-disable */
71         obj._leaflet_id = obj._leaflet_id || ++lastId;
72         return obj._leaflet_id;
73         /*eslint-enable */
74 }
75
76 // @function throttle(fn: Function, time: Number, context: Object): Function
77 // Returns a function which executes function `fn` with the given scope `context`
78 // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
79 // `fn` will be called no more than one time per given amount of `time`. The arguments
80 // received by the bound function will be any arguments passed when binding the
81 // function, followed by any arguments passed when invoking the bound function.
82 // Has an `L.throttle` shortcut.
83 function throttle(fn, time, context) {
84         var lock, args, wrapperFn, later;
85
86         later = function () {
87                 // reset lock and call if queued
88                 lock = false;
89                 if (args) {
90                         wrapperFn.apply(context, args);
91                         args = false;
92                 }
93         };
94
95         wrapperFn = function () {
96                 if (lock) {
97                         // called too soon, queue to call later
98                         args = arguments;
99
100                 } else {
101                         // call and lock until later
102                         fn.apply(context, arguments);
103                         setTimeout(later, time);
104                         lock = true;
105                 }
106         };
107
108         return wrapperFn;
109 }
110
111 // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
112 // Returns the number `num` modulo `range` in such a way so it lies within
113 // `range[0]` and `range[1]`. The returned value will be always smaller than
114 // `range[1]` unless `includeMax` is set to `true`.
115 function wrapNum(x, range, includeMax) {
116         var max = range[1],
117             min = range[0],
118             d = max - min;
119         return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
120 }
121
122 // @function falseFn(): Function
123 // Returns a function which always returns `false`.
124 function falseFn() { return false; }
125
126 // @function formatNum(num: Number, digits?: Number): Number
127 // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
128 function formatNum(num, digits) {
129         var pow = Math.pow(10, digits || 5);
130         return Math.round(num * pow) / pow;
131 }
132
133 // @function trim(str: String): String
134 // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
135 function trim(str) {
136         return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
137 }
138
139 // @function splitWords(str: String): String[]
140 // Trims and splits the string on whitespace and returns the array of parts.
141 function splitWords(str) {
142         return trim(str).split(/\s+/);
143 }
144
145 // @function setOptions(obj: Object, options: Object): Object
146 // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
147 function setOptions(obj, options) {
148         if (!obj.hasOwnProperty('options')) {
149                 obj.options = obj.options ? create(obj.options) : {};
150         }
151         for (var i in options) {
152                 obj.options[i] = options[i];
153         }
154         return obj.options;
155 }
156
157 // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
158 // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
159 // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
160 // be appended at the end. If `uppercase` is `true`, the parameter names will
161 // be uppercased (e.g. `'?A=foo&B=bar'`)
162 function getParamString(obj, existingUrl, uppercase) {
163         var params = [];
164         for (var i in obj) {
165                 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
166         }
167         return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
168 }
169
170 var templateRe = /\{ *([\w_\-]+) *\}/g;
171
172 // @function template(str: String, data: Object): String
173 // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
174 // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
175 // `('Hello foo, bar')`. You can also specify functions instead of strings for
176 // data values — they will be evaluated passing `data` as an argument.
177 function template(str, data) {
178         return str.replace(templateRe, function (str, key) {
179                 var value = data[key];
180
181                 if (value === undefined) {
182                         throw new Error('No value provided for variable ' + str);
183
184                 } else if (typeof value === 'function') {
185                         value = value(data);
186                 }
187                 return value;
188         });
189 }
190
191 // @function isArray(obj): Boolean
192 // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
193 var isArray = Array.isArray || function (obj) {
194         return (Object.prototype.toString.call(obj) === '[object Array]');
195 };
196
197 // @function indexOf(array: Array, el: Object): Number
198 // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
199 function indexOf(array, el) {
200         for (var i = 0; i < array.length; i++) {
201                 if (array[i] === el) { return i; }
202         }
203         return -1;
204 }
205
206 // @property emptyImageUrl: String
207 // Data URI string containing a base64-encoded empty GIF image.
208 // Used as a hack to free memory from unused images on WebKit-powered
209 // mobile devices (by setting image `src` to this string).
210 var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
211
212 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
213
214 function getPrefixed(name) {
215         return window['webkit' + name] || window['moz' + name] || window['ms' + name];
216 }
217
218 var lastTime = 0;
219
220 // fallback for IE 7-8
221 function timeoutDefer(fn) {
222         var time = +new Date(),
223             timeToCall = Math.max(0, 16 - (time - lastTime));
224
225         lastTime = time + timeToCall;
226         return window.setTimeout(fn, timeToCall);
227 }
228
229 var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
230 var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
231                 getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
232
233 // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
234 // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
235 // `context` if given. When `immediate` is set, `fn` is called immediately if
236 // the browser doesn't have native support for
237 // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
238 // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
239 function requestAnimFrame(fn, context, immediate) {
240         if (immediate && requestFn === timeoutDefer) {
241                 fn.call(context);
242         } else {
243                 return requestFn.call(window, bind(fn, context));
244         }
245 }
246
247 // @function cancelAnimFrame(id: Number): undefined
248 // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
249 function cancelAnimFrame(id) {
250         if (id) {
251                 cancelFn.call(window, id);
252         }
253 }
254
255
256 var Util = (Object.freeze || Object)({
257         freeze: freeze,
258         extend: extend,
259         create: create,
260         bind: bind,
261         lastId: lastId,
262         stamp: stamp,
263         throttle: throttle,
264         wrapNum: wrapNum,
265         falseFn: falseFn,
266         formatNum: formatNum,
267         trim: trim,
268         splitWords: splitWords,
269         setOptions: setOptions,
270         getParamString: getParamString,
271         template: template,
272         isArray: isArray,
273         indexOf: indexOf,
274         emptyImageUrl: emptyImageUrl,
275         requestFn: requestFn,
276         cancelFn: cancelFn,
277         requestAnimFrame: requestAnimFrame,
278         cancelAnimFrame: cancelAnimFrame
279 });
280
281 // @class Class
282 // @aka L.Class
283
284 // @section
285 // @uninheritable
286
287 // Thanks to John Resig and Dean Edwards for inspiration!
288
289 function Class() {}
290
291 Class.extend = function (props) {
292
293         // @function extend(props: Object): Function
294         // [Extends the current class](#class-inheritance) given the properties to be included.
295         // Returns a Javascript function that is a class constructor (to be called with `new`).
296         var NewClass = function () {
297
298                 // call the constructor
299                 if (this.initialize) {
300                         this.initialize.apply(this, arguments);
301                 }
302
303                 // call all constructor hooks
304                 this.callInitHooks();
305         };
306
307         var parentProto = NewClass.__super__ = this.prototype;
308
309         var proto = create(parentProto);
310         proto.constructor = NewClass;
311
312         NewClass.prototype = proto;
313
314         // inherit parent's statics
315         for (var i in this) {
316                 if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
317                         NewClass[i] = this[i];
318                 }
319         }
320
321         // mix static properties into the class
322         if (props.statics) {
323                 extend(NewClass, props.statics);
324                 delete props.statics;
325         }
326
327         // mix includes into the prototype
328         if (props.includes) {
329                 checkDeprecatedMixinEvents(props.includes);
330                 extend.apply(null, [proto].concat(props.includes));
331                 delete props.includes;
332         }
333
334         // merge options
335         if (proto.options) {
336                 props.options = extend(create(proto.options), props.options);
337         }
338
339         // mix given properties into the prototype
340         extend(proto, props);
341
342         proto._initHooks = [];
343
344         // add method for calling all hooks
345         proto.callInitHooks = function () {
346
347                 if (this._initHooksCalled) { return; }
348
349                 if (parentProto.callInitHooks) {
350                         parentProto.callInitHooks.call(this);
351                 }
352
353                 this._initHooksCalled = true;
354
355                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
356                         proto._initHooks[i].call(this);
357                 }
358         };
359
360         return NewClass;
361 };
362
363
364 // @function include(properties: Object): this
365 // [Includes a mixin](#class-includes) into the current class.
366 Class.include = function (props) {
367         extend(this.prototype, props);
368         return this;
369 };
370
371 // @function mergeOptions(options: Object): this
372 // [Merges `options`](#class-options) into the defaults of the class.
373 Class.mergeOptions = function (options) {
374         extend(this.prototype.options, options);
375         return this;
376 };
377
378 // @function addInitHook(fn: Function): this
379 // Adds a [constructor hook](#class-constructor-hooks) to the class.
380 Class.addInitHook = function (fn) { // (Function) || (String, args...)
381         var args = Array.prototype.slice.call(arguments, 1);
382
383         var init = typeof fn === 'function' ? fn : function () {
384                 this[fn].apply(this, args);
385         };
386
387         this.prototype._initHooks = this.prototype._initHooks || [];
388         this.prototype._initHooks.push(init);
389         return this;
390 };
391
392 function checkDeprecatedMixinEvents(includes) {
393         if (!L || !L.Mixin) { return; }
394
395         includes = isArray(includes) ? includes : [includes];
396
397         for (var i = 0; i < includes.length; i++) {
398                 if (includes[i] === L.Mixin.Events) {
399                         console.warn('Deprecated include of L.Mixin.Events: ' +
400                                 'this property will be removed in future releases, ' +
401                                 'please inherit from L.Evented instead.', new Error().stack);
402                 }
403         }
404 }
405
406 /*
407  * @class Evented
408  * @aka L.Evented
409  * @inherits Class
410  *
411  * 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).
412  *
413  * @example
414  *
415  * ```js
416  * map.on('click', function(e) {
417  *      alert(e.latlng);
418  * } );
419  * ```
420  *
421  * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
422  *
423  * ```js
424  * function onClick(e) { ... }
425  *
426  * map.on('click', onClick);
427  * map.off('click', onClick);
428  * ```
429  */
430
431 var Events = {
432         /* @method on(type: String, fn: Function, context?: Object): this
433          * 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'`).
434          *
435          * @alternative
436          * @method on(eventMap: Object): this
437          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
438          */
439         on: function (types, fn, context) {
440
441                 // types can be a map of types/handlers
442                 if (typeof types === 'object') {
443                         for (var type in types) {
444                                 // we don't process space-separated events here for performance;
445                                 // it's a hot path since Layer uses the on(obj) syntax
446                                 this._on(type, types[type], fn);
447                         }
448
449                 } else {
450                         // types can be a string of space-separated words
451                         types = splitWords(types);
452
453                         for (var i = 0, len = types.length; i < len; i++) {
454                                 this._on(types[i], fn, context);
455                         }
456                 }
457
458                 return this;
459         },
460
461         /* @method off(type: String, fn?: Function, context?: Object): this
462          * 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.
463          *
464          * @alternative
465          * @method off(eventMap: Object): this
466          * Removes a set of type/listener pairs.
467          *
468          * @alternative
469          * @method off: this
470          * Removes all listeners to all events on the object.
471          */
472         off: function (types, fn, context) {
473
474                 if (!types) {
475                         // clear all listeners if called without arguments
476                         delete this._events;
477
478                 } else if (typeof types === 'object') {
479                         for (var type in types) {
480                                 this._off(type, types[type], fn);
481                         }
482
483                 } else {
484                         types = splitWords(types);
485
486                         for (var i = 0, len = types.length; i < len; i++) {
487                                 this._off(types[i], fn, context);
488                         }
489                 }
490
491                 return this;
492         },
493
494         // attach listener (without syntactic sugar now)
495         _on: function (type, fn, context) {
496                 this._events = this._events || {};
497
498                 /* get/init listeners for type */
499                 var typeListeners = this._events[type];
500                 if (!typeListeners) {
501                         typeListeners = [];
502                         this._events[type] = typeListeners;
503                 }
504
505                 if (context === this) {
506                         // Less memory footprint.
507                         context = undefined;
508                 }
509                 var newListener = {fn: fn, ctx: context},
510                     listeners = typeListeners;
511
512                 // check if fn already there
513                 for (var i = 0, len = listeners.length; i < len; i++) {
514                         if (listeners[i].fn === fn && listeners[i].ctx === context) {
515                                 return;
516                         }
517                 }
518
519                 listeners.push(newListener);
520         },
521
522         _off: function (type, fn, context) {
523                 var listeners,
524                     i,
525                     len;
526
527                 if (!this._events) { return; }
528
529                 listeners = this._events[type];
530
531                 if (!listeners) {
532                         return;
533                 }
534
535                 if (!fn) {
536                         // Set all removed listeners to noop so they are not called if remove happens in fire
537                         for (i = 0, len = listeners.length; i < len; i++) {
538                                 listeners[i].fn = falseFn;
539                         }
540                         // clear all listeners for a type if function isn't specified
541                         delete this._events[type];
542                         return;
543                 }
544
545                 if (context === this) {
546                         context = undefined;
547                 }
548
549                 if (listeners) {
550
551                         // find fn and remove it
552                         for (i = 0, len = listeners.length; i < len; i++) {
553                                 var l = listeners[i];
554                                 if (l.ctx !== context) { continue; }
555                                 if (l.fn === fn) {
556
557                                         // set the removed listener to noop so that's not called if remove happens in fire
558                                         l.fn = falseFn;
559
560                                         if (this._firingCount) {
561                                                 /* copy array in case events are being fired */
562                                                 this._events[type] = listeners = listeners.slice();
563                                         }
564                                         listeners.splice(i, 1);
565
566                                         return;
567                                 }
568                         }
569                 }
570         },
571
572         // @method fire(type: String, data?: Object, propagate?: Boolean): this
573         // Fires an event of the specified type. You can optionally provide an data
574         // object — the first argument of the listener function will contain its
575         // properties. The event can optionally be propagated to event parents.
576         fire: function (type, data, propagate) {
577                 if (!this.listens(type, propagate)) { return this; }
578
579                 var event = extend({}, data, {type: type, target: this});
580
581                 if (this._events) {
582                         var listeners = this._events[type];
583
584                         if (listeners) {
585                                 this._firingCount = (this._firingCount + 1) || 1;
586                                 for (var i = 0, len = listeners.length; i < len; i++) {
587                                         var l = listeners[i];
588                                         l.fn.call(l.ctx || this, event);
589                                 }
590
591                                 this._firingCount--;
592                         }
593                 }
594
595                 if (propagate) {
596                         // propagate the event to parents (set with addEventParent)
597                         this._propagateEvent(event);
598                 }
599
600                 return this;
601         },
602
603         // @method listens(type: String): Boolean
604         // Returns `true` if a particular event type has any listeners attached to it.
605         listens: function (type, propagate) {
606                 var listeners = this._events && this._events[type];
607                 if (listeners && listeners.length) { return true; }
608
609                 if (propagate) {
610                         // also check parents for listeners if event propagates
611                         for (var id in this._eventParents) {
612                                 if (this._eventParents[id].listens(type, propagate)) { return true; }
613                         }
614                 }
615                 return false;
616         },
617
618         // @method once(…): this
619         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620         once: function (types, fn, context) {
621
622                 if (typeof types === 'object') {
623                         for (var type in types) {
624                                 this.once(type, types[type], fn);
625                         }
626                         return this;
627                 }
628
629                 var handler = bind(function () {
630                         this
631                             .off(types, fn, context)
632                             .off(types, handler, context);
633                 }, this);
634
635                 // add a listener that's executed once and removed after that
636                 return this
637                     .on(types, fn, context)
638                     .on(types, handler, context);
639         },
640
641         // @method addEventParent(obj: Evented): this
642         // Adds an event parent - an `Evented` that will receive propagated events
643         addEventParent: function (obj) {
644                 this._eventParents = this._eventParents || {};
645                 this._eventParents[stamp(obj)] = obj;
646                 return this;
647         },
648
649         // @method removeEventParent(obj: Evented): this
650         // Removes an event parent, so it will stop receiving propagated events
651         removeEventParent: function (obj) {
652                 if (this._eventParents) {
653                         delete this._eventParents[stamp(obj)];
654                 }
655                 return this;
656         },
657
658         _propagateEvent: function (e) {
659                 for (var id in this._eventParents) {
660                         this._eventParents[id].fire(e.type, extend({layer: e.target}, e), true);
661                 }
662         }
663 };
664
665 // aliases; we should ditch those eventually
666
667 // @method addEventListener(…): this
668 // Alias to [`on(…)`](#evented-on)
669 Events.addEventListener = Events.on;
670
671 // @method removeEventListener(…): this
672 // Alias to [`off(…)`](#evented-off)
673
674 // @method clearAllEventListeners(…): this
675 // Alias to [`off()`](#evented-off)
676 Events.removeEventListener = Events.clearAllEventListeners = Events.off;
677
678 // @method addOneTimeEventListener(…): this
679 // Alias to [`once(…)`](#evented-once)
680 Events.addOneTimeEventListener = Events.once;
681
682 // @method fireEvent(…): this
683 // Alias to [`fire(…)`](#evented-fire)
684 Events.fireEvent = Events.fire;
685
686 // @method hasEventListeners(…): Boolean
687 // Alias to [`listens(…)`](#evented-listens)
688 Events.hasEventListeners = Events.listens;
689
690 var Evented = Class.extend(Events);
691
692 /*
693  * @class Point
694  * @aka L.Point
695  *
696  * Represents a point with `x` and `y` coordinates in pixels.
697  *
698  * @example
699  *
700  * ```js
701  * var point = L.point(200, 300);
702  * ```
703  *
704  * 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:
705  *
706  * ```js
707  * map.panBy([200, 300]);
708  * map.panBy(L.point(200, 300));
709  * ```
710  */
711
712 function Point(x, y, round) {
713         // @property x: Number; The `x` coordinate of the point
714         this.x = (round ? Math.round(x) : x);
715         // @property y: Number; The `y` coordinate of the point
716         this.y = (round ? Math.round(y) : y);
717 }
718
719 Point.prototype = {
720
721         // @method clone(): Point
722         // Returns a copy of the current point.
723         clone: function () {
724                 return new Point(this.x, this.y);
725         },
726
727         // @method add(otherPoint: Point): Point
728         // Returns the result of addition of the current and the given points.
729         add: function (point) {
730                 // non-destructive, returns a new point
731                 return this.clone()._add(toPoint(point));
732         },
733
734         _add: function (point) {
735                 // destructive, used directly for performance in situations where it's safe to modify existing point
736                 this.x += point.x;
737                 this.y += point.y;
738                 return this;
739         },
740
741         // @method subtract(otherPoint: Point): Point
742         // Returns the result of subtraction of the given point from the current.
743         subtract: function (point) {
744                 return this.clone()._subtract(toPoint(point));
745         },
746
747         _subtract: function (point) {
748                 this.x -= point.x;
749                 this.y -= point.y;
750                 return this;
751         },
752
753         // @method divideBy(num: Number): Point
754         // Returns the result of division of the current point by the given number.
755         divideBy: function (num) {
756                 return this.clone()._divideBy(num);
757         },
758
759         _divideBy: function (num) {
760                 this.x /= num;
761                 this.y /= num;
762                 return this;
763         },
764
765         // @method multiplyBy(num: Number): Point
766         // Returns the result of multiplication of the current point by the given number.
767         multiplyBy: function (num) {
768                 return this.clone()._multiplyBy(num);
769         },
770
771         _multiplyBy: function (num) {
772                 this.x *= num;
773                 this.y *= num;
774                 return this;
775         },
776
777         // @method scaleBy(scale: Point): Point
778         // Multiply each coordinate of the current point by each coordinate of
779         // `scale`. In linear algebra terms, multiply the point by the
780         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
781         // defined by `scale`.
782         scaleBy: function (point) {
783                 return new Point(this.x * point.x, this.y * point.y);
784         },
785
786         // @method unscaleBy(scale: Point): Point
787         // Inverse of `scaleBy`. Divide each coordinate of the current point by
788         // each coordinate of `scale`.
789         unscaleBy: function (point) {
790                 return new Point(this.x / point.x, this.y / point.y);
791         },
792
793         // @method round(): Point
794         // Returns a copy of the current point with rounded coordinates.
795         round: function () {
796                 return this.clone()._round();
797         },
798
799         _round: function () {
800                 this.x = Math.round(this.x);
801                 this.y = Math.round(this.y);
802                 return this;
803         },
804
805         // @method floor(): Point
806         // Returns a copy of the current point with floored coordinates (rounded down).
807         floor: function () {
808                 return this.clone()._floor();
809         },
810
811         _floor: function () {
812                 this.x = Math.floor(this.x);
813                 this.y = Math.floor(this.y);
814                 return this;
815         },
816
817         // @method ceil(): Point
818         // Returns a copy of the current point with ceiled coordinates (rounded up).
819         ceil: function () {
820                 return this.clone()._ceil();
821         },
822
823         _ceil: function () {
824                 this.x = Math.ceil(this.x);
825                 this.y = Math.ceil(this.y);
826                 return this;
827         },
828
829         // @method distanceTo(otherPoint: Point): Number
830         // Returns the cartesian distance between the current and the given points.
831         distanceTo: function (point) {
832                 point = toPoint(point);
833
834                 var x = point.x - this.x,
835                     y = point.y - this.y;
836
837                 return Math.sqrt(x * x + y * y);
838         },
839
840         // @method equals(otherPoint: Point): Boolean
841         // Returns `true` if the given point has the same coordinates.
842         equals: function (point) {
843                 point = toPoint(point);
844
845                 return point.x === this.x &&
846                        point.y === this.y;
847         },
848
849         // @method contains(otherPoint: Point): Boolean
850         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
851         contains: function (point) {
852                 point = toPoint(point);
853
854                 return Math.abs(point.x) <= Math.abs(this.x) &&
855                        Math.abs(point.y) <= Math.abs(this.y);
856         },
857
858         // @method toString(): String
859         // Returns a string representation of the point for debugging purposes.
860         toString: function () {
861                 return 'Point(' +
862                         formatNum(this.x) + ', ' +
863                         formatNum(this.y) + ')';
864         }
865 };
866
867 // @factory L.point(x: Number, y: Number, round?: Boolean)
868 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
869
870 // @alternative
871 // @factory L.point(coords: Number[])
872 // Expects an array of the form `[x, y]` instead.
873
874 // @alternative
875 // @factory L.point(coords: Object)
876 // Expects a plain object of the form `{x: Number, y: Number}` instead.
877 function toPoint(x, y, round) {
878         if (x instanceof Point) {
879                 return x;
880         }
881         if (isArray(x)) {
882                 return new Point(x[0], x[1]);
883         }
884         if (x === undefined || x === null) {
885                 return x;
886         }
887         if (typeof x === 'object' && 'x' in x && 'y' in x) {
888                 return new Point(x.x, x.y);
889         }
890         return new Point(x, y, round);
891 }
892
893 /*
894  * @class Bounds
895  * @aka L.Bounds
896  *
897  * Represents a rectangular area in pixel coordinates.
898  *
899  * @example
900  *
901  * ```js
902  * var p1 = L.point(10, 10),
903  * p2 = L.point(40, 60),
904  * bounds = L.bounds(p1, p2);
905  * ```
906  *
907  * 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:
908  *
909  * ```js
910  * otherBounds.intersects([[10, 10], [40, 60]]);
911  * ```
912  */
913
914 function Bounds(a, b) {
915         if (!a) { return; }
916
917         var points = b ? [a, b] : a;
918
919         for (var i = 0, len = points.length; i < len; i++) {
920                 this.extend(points[i]);
921         }
922 }
923
924 Bounds.prototype = {
925         // @method extend(point: Point): this
926         // Extends the bounds to contain the given point.
927         extend: function (point) { // (Point)
928                 point = toPoint(point);
929
930                 // @property min: Point
931                 // The top left corner of the rectangle.
932                 // @property max: Point
933                 // The bottom right corner of the rectangle.
934                 if (!this.min && !this.max) {
935                         this.min = point.clone();
936                         this.max = point.clone();
937                 } else {
938                         this.min.x = Math.min(point.x, this.min.x);
939                         this.max.x = Math.max(point.x, this.max.x);
940                         this.min.y = Math.min(point.y, this.min.y);
941                         this.max.y = Math.max(point.y, this.max.y);
942                 }
943                 return this;
944         },
945
946         // @method getCenter(round?: Boolean): Point
947         // Returns the center point of the bounds.
948         getCenter: function (round) {
949                 return new Point(
950                         (this.min.x + this.max.x) / 2,
951                         (this.min.y + this.max.y) / 2, round);
952         },
953
954         // @method getBottomLeft(): Point
955         // Returns the bottom-left point of the bounds.
956         getBottomLeft: function () {
957                 return new Point(this.min.x, this.max.y);
958         },
959
960         // @method getTopRight(): Point
961         // Returns the top-right point of the bounds.
962         getTopRight: function () { // -> Point
963                 return new Point(this.max.x, this.min.y);
964         },
965
966         // @method getTopLeft(): Point
967         // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
968         getTopLeft: function () {
969                 return this.min; // left, top
970         },
971
972         // @method getBottomRight(): Point
973         // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
974         getBottomRight: function () {
975                 return this.max; // right, bottom
976         },
977
978         // @method getSize(): Point
979         // Returns the size of the given bounds
980         getSize: function () {
981                 return this.max.subtract(this.min);
982         },
983
984         // @method contains(otherBounds: Bounds): Boolean
985         // Returns `true` if the rectangle contains the given one.
986         // @alternative
987         // @method contains(point: Point): Boolean
988         // Returns `true` if the rectangle contains the given point.
989         contains: function (obj) {
990                 var min, max;
991
992                 if (typeof obj[0] === 'number' || obj instanceof Point) {
993                         obj = toPoint(obj);
994                 } else {
995                         obj = toBounds(obj);
996                 }
997
998                 if (obj instanceof Bounds) {
999                         min = obj.min;
1000                         max = obj.max;
1001                 } else {
1002                         min = max = obj;
1003                 }
1004
1005                 return (min.x >= this.min.x) &&
1006                        (max.x <= this.max.x) &&
1007                        (min.y >= this.min.y) &&
1008                        (max.y <= this.max.y);
1009         },
1010
1011         // @method intersects(otherBounds: Bounds): Boolean
1012         // Returns `true` if the rectangle intersects the given bounds. Two bounds
1013         // intersect if they have at least one point in common.
1014         intersects: function (bounds) { // (Bounds) -> Boolean
1015                 bounds = toBounds(bounds);
1016
1017                 var min = this.min,
1018                     max = this.max,
1019                     min2 = bounds.min,
1020                     max2 = bounds.max,
1021                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1022                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1023
1024                 return xIntersects && yIntersects;
1025         },
1026
1027         // @method overlaps(otherBounds: Bounds): Boolean
1028         // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1029         // overlap if their intersection is an area.
1030         overlaps: function (bounds) { // (Bounds) -> Boolean
1031                 bounds = toBounds(bounds);
1032
1033                 var min = this.min,
1034                     max = this.max,
1035                     min2 = bounds.min,
1036                     max2 = bounds.max,
1037                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1038                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1039
1040                 return xOverlaps && yOverlaps;
1041         },
1042
1043         isValid: function () {
1044                 return !!(this.min && this.max);
1045         }
1046 };
1047
1048
1049 // @factory L.bounds(corner1: Point, corner2: Point)
1050 // Creates a Bounds object from two corners coordinate pairs.
1051 // @alternative
1052 // @factory L.bounds(points: Point[])
1053 // Creates a Bounds object from the given array of points.
1054 function toBounds(a, b) {
1055         if (!a || a instanceof Bounds) {
1056                 return a;
1057         }
1058         return new Bounds(a, b);
1059 }
1060
1061 /*
1062  * @class LatLngBounds
1063  * @aka L.LatLngBounds
1064  *
1065  * Represents a rectangular geographical area on a map.
1066  *
1067  * @example
1068  *
1069  * ```js
1070  * var corner1 = L.latLng(40.712, -74.227),
1071  * corner2 = L.latLng(40.774, -74.125),
1072  * bounds = L.latLngBounds(corner1, corner2);
1073  * ```
1074  *
1075  * 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:
1076  *
1077  * ```js
1078  * map.fitBounds([
1079  *      [40.712, -74.227],
1080  *      [40.774, -74.125]
1081  * ]);
1082  * ```
1083  *
1084  * 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.
1085  */
1086
1087 function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1088         if (!corner1) { return; }
1089
1090         var latlngs = corner2 ? [corner1, corner2] : corner1;
1091
1092         for (var i = 0, len = latlngs.length; i < len; i++) {
1093                 this.extend(latlngs[i]);
1094         }
1095 }
1096
1097 LatLngBounds.prototype = {
1098
1099         // @method extend(latlng: LatLng): this
1100         // Extend the bounds to contain the given point
1101
1102         // @alternative
1103         // @method extend(otherBounds: LatLngBounds): this
1104         // Extend the bounds to contain the given bounds
1105         extend: function (obj) {
1106                 var sw = this._southWest,
1107                     ne = this._northEast,
1108                     sw2, ne2;
1109
1110                 if (obj instanceof LatLng) {
1111                         sw2 = obj;
1112                         ne2 = obj;
1113
1114                 } else if (obj instanceof LatLngBounds) {
1115                         sw2 = obj._southWest;
1116                         ne2 = obj._northEast;
1117
1118                         if (!sw2 || !ne2) { return this; }
1119
1120                 } else {
1121                         return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
1122                 }
1123
1124                 if (!sw && !ne) {
1125                         this._southWest = new LatLng(sw2.lat, sw2.lng);
1126                         this._northEast = new LatLng(ne2.lat, ne2.lng);
1127                 } else {
1128                         sw.lat = Math.min(sw2.lat, sw.lat);
1129                         sw.lng = Math.min(sw2.lng, sw.lng);
1130                         ne.lat = Math.max(ne2.lat, ne.lat);
1131                         ne.lng = Math.max(ne2.lng, ne.lng);
1132                 }
1133
1134                 return this;
1135         },
1136
1137         // @method pad(bufferRatio: Number): LatLngBounds
1138         // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1139         pad: function (bufferRatio) {
1140                 var sw = this._southWest,
1141                     ne = this._northEast,
1142                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1143                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1144
1145                 return new LatLngBounds(
1146                         new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1147                         new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1148         },
1149
1150         // @method getCenter(): LatLng
1151         // Returns the center point of the bounds.
1152         getCenter: function () {
1153                 return new LatLng(
1154                         (this._southWest.lat + this._northEast.lat) / 2,
1155                         (this._southWest.lng + this._northEast.lng) / 2);
1156         },
1157
1158         // @method getSouthWest(): LatLng
1159         // Returns the south-west point of the bounds.
1160         getSouthWest: function () {
1161                 return this._southWest;
1162         },
1163
1164         // @method getNorthEast(): LatLng
1165         // Returns the north-east point of the bounds.
1166         getNorthEast: function () {
1167                 return this._northEast;
1168         },
1169
1170         // @method getNorthWest(): LatLng
1171         // Returns the north-west point of the bounds.
1172         getNorthWest: function () {
1173                 return new LatLng(this.getNorth(), this.getWest());
1174         },
1175
1176         // @method getSouthEast(): LatLng
1177         // Returns the south-east point of the bounds.
1178         getSouthEast: function () {
1179                 return new LatLng(this.getSouth(), this.getEast());
1180         },
1181
1182         // @method getWest(): Number
1183         // Returns the west longitude of the bounds
1184         getWest: function () {
1185                 return this._southWest.lng;
1186         },
1187
1188         // @method getSouth(): Number
1189         // Returns the south latitude of the bounds
1190         getSouth: function () {
1191                 return this._southWest.lat;
1192         },
1193
1194         // @method getEast(): Number
1195         // Returns the east longitude of the bounds
1196         getEast: function () {
1197                 return this._northEast.lng;
1198         },
1199
1200         // @method getNorth(): Number
1201         // Returns the north latitude of the bounds
1202         getNorth: function () {
1203                 return this._northEast.lat;
1204         },
1205
1206         // @method contains(otherBounds: LatLngBounds): Boolean
1207         // Returns `true` if the rectangle contains the given one.
1208
1209         // @alternative
1210         // @method contains (latlng: LatLng): Boolean
1211         // Returns `true` if the rectangle contains the given point.
1212         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1213                 if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
1214                         obj = toLatLng(obj);
1215                 } else {
1216                         obj = toLatLngBounds(obj);
1217                 }
1218
1219                 var sw = this._southWest,
1220                     ne = this._northEast,
1221                     sw2, ne2;
1222
1223                 if (obj instanceof LatLngBounds) {
1224                         sw2 = obj.getSouthWest();
1225                         ne2 = obj.getNorthEast();
1226                 } else {
1227                         sw2 = ne2 = obj;
1228                 }
1229
1230                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1231                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1232         },
1233
1234         // @method intersects(otherBounds: LatLngBounds): Boolean
1235         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1236         intersects: function (bounds) {
1237                 bounds = toLatLngBounds(bounds);
1238
1239                 var sw = this._southWest,
1240                     ne = this._northEast,
1241                     sw2 = bounds.getSouthWest(),
1242                     ne2 = bounds.getNorthEast(),
1243
1244                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1245                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1246
1247                 return latIntersects && lngIntersects;
1248         },
1249
1250         // @method overlaps(otherBounds: Bounds): Boolean
1251         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1252         overlaps: function (bounds) {
1253                 bounds = toLatLngBounds(bounds);
1254
1255                 var sw = this._southWest,
1256                     ne = this._northEast,
1257                     sw2 = bounds.getSouthWest(),
1258                     ne2 = bounds.getNorthEast(),
1259
1260                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1261                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1262
1263                 return latOverlaps && lngOverlaps;
1264         },
1265
1266         // @method toBBoxString(): String
1267         // 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.
1268         toBBoxString: function () {
1269                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1270         },
1271
1272         // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
1273         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overriden by setting `maxMargin` to a small number.
1274         equals: function (bounds, maxMargin) {
1275                 if (!bounds) { return false; }
1276
1277                 bounds = toLatLngBounds(bounds);
1278
1279                 return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
1280                        this._northEast.equals(bounds.getNorthEast(), maxMargin);
1281         },
1282
1283         // @method isValid(): Boolean
1284         // Returns `true` if the bounds are properly initialized.
1285         isValid: function () {
1286                 return !!(this._southWest && this._northEast);
1287         }
1288 };
1289
1290 // TODO International date line?
1291
1292 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1293 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1294
1295 // @alternative
1296 // @factory L.latLngBounds(latlngs: LatLng[])
1297 // 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).
1298 function toLatLngBounds(a, b) {
1299         if (a instanceof LatLngBounds) {
1300                 return a;
1301         }
1302         return new LatLngBounds(a, b);
1303 }
1304
1305 /* @class LatLng
1306  * @aka L.LatLng
1307  *
1308  * Represents a geographical point with a certain latitude and longitude.
1309  *
1310  * @example
1311  *
1312  * ```
1313  * var latlng = L.latLng(50.5, 30.5);
1314  * ```
1315  *
1316  * 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:
1317  *
1318  * ```
1319  * map.panTo([50, 30]);
1320  * map.panTo({lon: 30, lat: 50});
1321  * map.panTo({lat: 50, lng: 30});
1322  * map.panTo(L.latLng(50, 30));
1323  * ```
1324  */
1325
1326 function LatLng(lat, lng, alt) {
1327         if (isNaN(lat) || isNaN(lng)) {
1328                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1329         }
1330
1331         // @property lat: Number
1332         // Latitude in degrees
1333         this.lat = +lat;
1334
1335         // @property lng: Number
1336         // Longitude in degrees
1337         this.lng = +lng;
1338
1339         // @property alt: Number
1340         // Altitude in meters (optional)
1341         if (alt !== undefined) {
1342                 this.alt = +alt;
1343         }
1344 }
1345
1346 LatLng.prototype = {
1347         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1348         // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
1349         equals: function (obj, maxMargin) {
1350                 if (!obj) { return false; }
1351
1352                 obj = toLatLng(obj);
1353
1354                 var margin = Math.max(
1355                         Math.abs(this.lat - obj.lat),
1356                         Math.abs(this.lng - obj.lng));
1357
1358                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1359         },
1360
1361         // @method toString(): String
1362         // Returns a string representation of the point (for debugging purposes).
1363         toString: function (precision) {
1364                 return 'LatLng(' +
1365                         formatNum(this.lat, precision) + ', ' +
1366                         formatNum(this.lng, precision) + ')';
1367         },
1368
1369         // @method distanceTo(otherLatLng: LatLng): Number
1370         // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1371         distanceTo: function (other) {
1372                 return Earth.distance(this, toLatLng(other));
1373         },
1374
1375         // @method wrap(): LatLng
1376         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1377         wrap: function () {
1378                 return Earth.wrapLatLng(this);
1379         },
1380
1381         // @method toBounds(sizeInMeters: Number): LatLngBounds
1382         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1383         toBounds: function (sizeInMeters) {
1384                 var latAccuracy = 180 * sizeInMeters / 40075017,
1385                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1386
1387                 return toLatLngBounds(
1388                         [this.lat - latAccuracy, this.lng - lngAccuracy],
1389                         [this.lat + latAccuracy, this.lng + lngAccuracy]);
1390         },
1391
1392         clone: function () {
1393                 return new LatLng(this.lat, this.lng, this.alt);
1394         }
1395 };
1396
1397
1398
1399 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1400 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1401
1402 // @alternative
1403 // @factory L.latLng(coords: Array): LatLng
1404 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1405
1406 // @alternative
1407 // @factory L.latLng(coords: Object): LatLng
1408 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1409
1410 function toLatLng(a, b, c) {
1411         if (a instanceof LatLng) {
1412                 return a;
1413         }
1414         if (isArray(a) && typeof a[0] !== 'object') {
1415                 if (a.length === 3) {
1416                         return new LatLng(a[0], a[1], a[2]);
1417                 }
1418                 if (a.length === 2) {
1419                         return new LatLng(a[0], a[1]);
1420                 }
1421                 return null;
1422         }
1423         if (a === undefined || a === null) {
1424                 return a;
1425         }
1426         if (typeof a === 'object' && 'lat' in a) {
1427                 return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1428         }
1429         if (b === undefined) {
1430                 return null;
1431         }
1432         return new LatLng(a, b, c);
1433 }
1434
1435 /*
1436  * @namespace CRS
1437  * @crs L.CRS.Base
1438  * Object that defines coordinate reference systems for projecting
1439  * geographical points into pixel (screen) coordinates and back (and to
1440  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
1441  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
1442  *
1443  * Leaflet defines the most usual CRSs by default. If you want to use a
1444  * CRS not defined by default, take a look at the
1445  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
1446  */
1447
1448 var CRS = {
1449         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
1450         // Projects geographical coordinates into pixel coordinates for a given zoom.
1451         latLngToPoint: function (latlng, zoom) {
1452                 var projectedPoint = this.projection.project(latlng),
1453                     scale = this.scale(zoom);
1454
1455                 return this.transformation._transform(projectedPoint, scale);
1456         },
1457
1458         // @method pointToLatLng(point: Point, zoom: Number): LatLng
1459         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
1460         // zoom into geographical coordinates.
1461         pointToLatLng: function (point, zoom) {
1462                 var scale = this.scale(zoom),
1463                     untransformedPoint = this.transformation.untransform(point, scale);
1464
1465                 return this.projection.unproject(untransformedPoint);
1466         },
1467
1468         // @method project(latlng: LatLng): Point
1469         // Projects geographical coordinates into coordinates in units accepted for
1470         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
1471         project: function (latlng) {
1472                 return this.projection.project(latlng);
1473         },
1474
1475         // @method unproject(point: Point): LatLng
1476         // Given a projected coordinate returns the corresponding LatLng.
1477         // The inverse of `project`.
1478         unproject: function (point) {
1479                 return this.projection.unproject(point);
1480         },
1481
1482         // @method scale(zoom: Number): Number
1483         // Returns the scale used when transforming projected coordinates into
1484         // pixel coordinates for a particular zoom. For example, it returns
1485         // `256 * 2^zoom` for Mercator-based CRS.
1486         scale: function (zoom) {
1487                 return 256 * Math.pow(2, zoom);
1488         },
1489
1490         // @method zoom(scale: Number): Number
1491         // Inverse of `scale()`, returns the zoom level corresponding to a scale
1492         // factor of `scale`.
1493         zoom: function (scale) {
1494                 return Math.log(scale / 256) / Math.LN2;
1495         },
1496
1497         // @method getProjectedBounds(zoom: Number): Bounds
1498         // Returns the projection's bounds scaled and transformed for the provided `zoom`.
1499         getProjectedBounds: function (zoom) {
1500                 if (this.infinite) { return null; }
1501
1502                 var b = this.projection.bounds,
1503                     s = this.scale(zoom),
1504                     min = this.transformation.transform(b.min, s),
1505                     max = this.transformation.transform(b.max, s);
1506
1507                 return new Bounds(min, max);
1508         },
1509
1510         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
1511         // Returns the distance between two geographical coordinates.
1512
1513         // @property code: String
1514         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
1515         //
1516         // @property wrapLng: Number[]
1517         // An array of two numbers defining whether the longitude (horizontal) coordinate
1518         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
1519         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
1520         //
1521         // @property wrapLat: Number[]
1522         // Like `wrapLng`, but for the latitude (vertical) axis.
1523
1524         // wrapLng: [min, max],
1525         // wrapLat: [min, max],
1526
1527         // @property infinite: Boolean
1528         // If true, the coordinate space will be unbounded (infinite in both axes)
1529         infinite: false,
1530
1531         // @method wrapLatLng(latlng: LatLng): LatLng
1532         // Returns a `LatLng` where lat and lng has been wrapped according to the
1533         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
1534         wrapLatLng: function (latlng) {
1535                 var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
1536                     lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
1537                     alt = latlng.alt;
1538
1539                 return new LatLng(lat, lng, alt);
1540         },
1541
1542         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
1543         // Returns a `LatLngBounds` with the same size as the given one, ensuring
1544         // that its center is within the CRS's bounds.
1545         // Only accepts actual `L.LatLngBounds` instances, not arrays.
1546         wrapLatLngBounds: function (bounds) {
1547                 var center = bounds.getCenter(),
1548                     newCenter = this.wrapLatLng(center),
1549                     latShift = center.lat - newCenter.lat,
1550                     lngShift = center.lng - newCenter.lng;
1551
1552                 if (latShift === 0 && lngShift === 0) {
1553                         return bounds;
1554                 }
1555
1556                 var sw = bounds.getSouthWest(),
1557                     ne = bounds.getNorthEast(),
1558                     newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
1559                     newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);
1560
1561                 return new LatLngBounds(newSw, newNe);
1562         }
1563 };
1564
1565 /*
1566  * @namespace CRS
1567  * @crs L.CRS.Earth
1568  *
1569  * Serves as the base for CRS that are global such that they cover the earth.
1570  * Can only be used as the base for other CRS and cannot be used directly,
1571  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
1572  * meters.
1573  */
1574
1575 var Earth = extend({}, CRS, {
1576         wrapLng: [-180, 180],
1577
1578         // Mean Earth Radius, as recommended for use by
1579         // the International Union of Geodesy and Geophysics,
1580         // see http://rosettacode.org/wiki/Haversine_formula
1581         R: 6371000,
1582
1583         // distance between two geographical points using spherical law of cosines approximation
1584         distance: function (latlng1, latlng2) {
1585                 var rad = Math.PI / 180,
1586                     lat1 = latlng1.lat * rad,
1587                     lat2 = latlng2.lat * rad,
1588                     a = Math.sin(lat1) * Math.sin(lat2) +
1589                         Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
1590
1591                 return this.R * Math.acos(Math.min(a, 1));
1592         }
1593 });
1594
1595 /*
1596  * @namespace Projection
1597  * @projection L.Projection.SphericalMercator
1598  *
1599  * Spherical Mercator projection — the most common projection for online maps,
1600  * used by almost all free and commercial tile providers. Assumes that Earth is
1601  * a sphere. Used by the `EPSG:3857` CRS.
1602  */
1603
1604 var SphericalMercator = {
1605
1606         R: 6378137,
1607         MAX_LATITUDE: 85.0511287798,
1608
1609         project: function (latlng) {
1610                 var d = Math.PI / 180,
1611                     max = this.MAX_LATITUDE,
1612                     lat = Math.max(Math.min(max, latlng.lat), -max),
1613                     sin = Math.sin(lat * d);
1614
1615                 return new Point(
1616                                 this.R * latlng.lng * d,
1617                                 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
1618         },
1619
1620         unproject: function (point) {
1621                 var d = 180 / Math.PI;
1622
1623                 return new LatLng(
1624                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
1625                         point.x * d / this.R);
1626         },
1627
1628         bounds: (function () {
1629                 var d = 6378137 * Math.PI;
1630                 return new Bounds([-d, -d], [d, d]);
1631         })()
1632 };
1633
1634 /*
1635  * @class Transformation
1636  * @aka L.Transformation
1637  *
1638  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1639  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1640  * the reverse. Used by Leaflet in its projections code.
1641  *
1642  * @example
1643  *
1644  * ```js
1645  * var transformation = L.transformation(2, 5, -1, 10),
1646  *      p = L.point(1, 2),
1647  *      p2 = transformation.transform(p), //  L.point(7, 8)
1648  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1649  * ```
1650  */
1651
1652
1653 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1654 // Creates a `Transformation` object with the given coefficients.
1655 function Transformation(a, b, c, d) {
1656         if (isArray(a)) {
1657                 // use array properties
1658                 this._a = a[0];
1659                 this._b = a[1];
1660                 this._c = a[2];
1661                 this._d = a[3];
1662                 return;
1663         }
1664         this._a = a;
1665         this._b = b;
1666         this._c = c;
1667         this._d = d;
1668 }
1669
1670 Transformation.prototype = {
1671         // @method transform(point: Point, scale?: Number): Point
1672         // Returns a transformed point, optionally multiplied by the given scale.
1673         // Only accepts actual `L.Point` instances, not arrays.
1674         transform: function (point, scale) { // (Point, Number) -> Point
1675                 return this._transform(point.clone(), scale);
1676         },
1677
1678         // destructive transform (faster)
1679         _transform: function (point, scale) {
1680                 scale = scale || 1;
1681                 point.x = scale * (this._a * point.x + this._b);
1682                 point.y = scale * (this._c * point.y + this._d);
1683                 return point;
1684         },
1685
1686         // @method untransform(point: Point, scale?: Number): Point
1687         // Returns the reverse transformation of the given point, optionally divided
1688         // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1689         untransform: function (point, scale) {
1690                 scale = scale || 1;
1691                 return new Point(
1692                         (point.x / scale - this._b) / this._a,
1693                         (point.y / scale - this._d) / this._c);
1694         }
1695 };
1696
1697 // factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1698
1699 // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
1700 // Instantiates a Transformation object with the given coefficients.
1701
1702 // @alternative
1703 // @factory L.transformation(coefficients: Array): Transformation
1704 // Expects an coeficients array of the form
1705 // `[a: Number, b: Number, c: Number, d: Number]`.
1706
1707 function toTransformation(a, b, c, d) {
1708         return new Transformation(a, b, c, d);
1709 }
1710
1711 /*
1712  * @namespace CRS
1713  * @crs L.CRS.EPSG3857
1714  *
1715  * The most common CRS for online maps, used by almost all free and commercial
1716  * tile providers. Uses Spherical Mercator projection. Set in by default in
1717  * Map's `crs` option.
1718  */
1719
1720 var EPSG3857 = extend({}, Earth, {
1721         code: 'EPSG:3857',
1722         projection: SphericalMercator,
1723
1724         transformation: (function () {
1725                 var scale = 0.5 / (Math.PI * SphericalMercator.R);
1726                 return toTransformation(scale, 0.5, -scale, 0.5);
1727         }())
1728 });
1729
1730 var EPSG900913 = extend({}, EPSG3857, {
1731         code: 'EPSG:900913'
1732 });
1733
1734 // @namespace SVG; @section
1735 // There are several static functions which can be called without instantiating L.SVG:
1736
1737 // @function create(name: String): SVGElement
1738 // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
1739 // corresponding to the class name passed. For example, using 'line' will return
1740 // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
1741 function svgCreate(name) {
1742         return document.createElementNS('http://www.w3.org/2000/svg', name);
1743 }
1744
1745 // @function pointsToPath(rings: Point[], closed: Boolean): String
1746 // Generates a SVG path string for multiple rings, with each ring turning
1747 // into "M..L..L.." instructions
1748 function pointsToPath(rings, closed) {
1749         var str = '',
1750         i, j, len, len2, points, p;
1751
1752         for (i = 0, len = rings.length; i < len; i++) {
1753                 points = rings[i];
1754
1755                 for (j = 0, len2 = points.length; j < len2; j++) {
1756                         p = points[j];
1757                         str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
1758                 }
1759
1760                 // closes the ring for polygons; "x" is VML syntax
1761                 str += closed ? (svg ? 'z' : 'x') : '';
1762         }
1763
1764         // SVG complains about empty path strings
1765         return str || 'M0 0';
1766 }
1767
1768 /*
1769  * @namespace Browser
1770  * @aka L.Browser
1771  *
1772  * A namespace with static properties for browser/feature detection used by Leaflet internally.
1773  *
1774  * @example
1775  *
1776  * ```js
1777  * if (L.Browser.ielt9) {
1778  *   alert('Upgrade your browser, dude!');
1779  * }
1780  * ```
1781  */
1782
1783 var style$1 = document.documentElement.style;
1784
1785 // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
1786 var ie = 'ActiveXObject' in window;
1787
1788 // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
1789 var ielt9 = ie && !document.addEventListener;
1790
1791 // @property edge: Boolean; `true` for the Edge web browser.
1792 var edge = 'msLaunchUri' in navigator && !('documentMode' in document);
1793
1794 // @property webkit: Boolean;
1795 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
1796 var webkit = userAgentContains('webkit');
1797
1798 // @property android: Boolean
1799 // `true` for any browser running on an Android platform.
1800 var android = userAgentContains('android');
1801
1802 // @property android23: Boolean; `true` for browsers running on Android 2 or Android 3.
1803 var android23 = userAgentContains('android 2') || userAgentContains('android 3');
1804
1805 // @property opera: Boolean; `true` for the Opera browser
1806 var opera = !!window.opera;
1807
1808 // @property chrome: Boolean; `true` for the Chrome browser.
1809 var chrome = userAgentContains('chrome');
1810
1811 // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
1812 var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;
1813
1814 // @property safari: Boolean; `true` for the Safari browser.
1815 var safari = !chrome && userAgentContains('safari');
1816
1817 var phantom = userAgentContains('phantom');
1818
1819 // @property opera12: Boolean
1820 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
1821 var opera12 = 'OTransition' in style$1;
1822
1823 // @property win: Boolean; `true` when the browser is running in a Windows platform
1824 var win = navigator.platform.indexOf('Win') === 0;
1825
1826 // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
1827 var ie3d = ie && ('transition' in style$1);
1828
1829 // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
1830 var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;
1831
1832 // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
1833 var gecko3d = 'MozPerspective' in style$1;
1834
1835 // @property any3d: Boolean
1836 // `true` for all browsers supporting CSS transforms.
1837 var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;
1838
1839 // @property mobile: Boolean; `true` for all browsers running in a mobile device.
1840 var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');
1841
1842 // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
1843 var mobileWebkit = mobile && webkit;
1844
1845 // @property mobileWebkit3d: Boolean
1846 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
1847 var mobileWebkit3d = mobile && webkit3d;
1848
1849 // @property msPointer: Boolean
1850 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
1851 var msPointer = !window.PointerEvent && window.MSPointerEvent;
1852
1853 // @property pointer: Boolean
1854 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
1855 var pointer = !!(window.PointerEvent || msPointer);
1856
1857 // @property touch: Boolean
1858 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
1859 // This does not necessarily mean that the browser is running in a computer with
1860 // a touchscreen, it only means that the browser is capable of understanding
1861 // touch events.
1862 var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
1863                 (window.DocumentTouch && document instanceof window.DocumentTouch));
1864
1865 // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
1866 var mobileOpera = mobile && opera;
1867
1868 // @property mobileGecko: Boolean
1869 // `true` for gecko-based browsers running in a mobile device.
1870 var mobileGecko = mobile && gecko;
1871
1872 // @property retina: Boolean
1873 // `true` for browsers on a high-resolution "retina" screen.
1874 var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;
1875
1876
1877 // @property canvas: Boolean
1878 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
1879 var canvas = (function () {
1880         return !!document.createElement('canvas').getContext;
1881 }());
1882
1883 // @property svg: Boolean
1884 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
1885 var svg = !!(document.createElementNS && svgCreate('svg').createSVGRect);
1886
1887 // @property vml: Boolean
1888 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
1889 var vml = !svg && (function () {
1890         try {
1891                 var div = document.createElement('div');
1892                 div.innerHTML = '<v:shape adj="1"/>';
1893
1894                 var shape = div.firstChild;
1895                 shape.style.behavior = 'url(#default#VML)';
1896
1897                 return shape && (typeof shape.adj === 'object');
1898
1899         } catch (e) {
1900                 return false;
1901         }
1902 }());
1903
1904
1905 function userAgentContains(str) {
1906         return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
1907 }
1908
1909
1910 var Browser = (Object.freeze || Object)({
1911         ie: ie,
1912         ielt9: ielt9,
1913         edge: edge,
1914         webkit: webkit,
1915         android: android,
1916         android23: android23,
1917         opera: opera,
1918         chrome: chrome,
1919         gecko: gecko,
1920         safari: safari,
1921         phantom: phantom,
1922         opera12: opera12,
1923         win: win,
1924         ie3d: ie3d,
1925         webkit3d: webkit3d,
1926         gecko3d: gecko3d,
1927         any3d: any3d,
1928         mobile: mobile,
1929         mobileWebkit: mobileWebkit,
1930         mobileWebkit3d: mobileWebkit3d,
1931         msPointer: msPointer,
1932         pointer: pointer,
1933         touch: touch,
1934         mobileOpera: mobileOpera,
1935         mobileGecko: mobileGecko,
1936         retina: retina,
1937         canvas: canvas,
1938         svg: svg,
1939         vml: vml
1940 });
1941
1942 /*
1943  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
1944  */
1945
1946
1947 var POINTER_DOWN =   msPointer ? 'MSPointerDown'   : 'pointerdown';
1948 var POINTER_MOVE =   msPointer ? 'MSPointerMove'   : 'pointermove';
1949 var POINTER_UP =     msPointer ? 'MSPointerUp'     : 'pointerup';
1950 var POINTER_CANCEL = msPointer ? 'MSPointerCancel' : 'pointercancel';
1951 var TAG_WHITE_LIST = ['INPUT', 'SELECT', 'OPTION'];
1952 var _pointers = {};
1953 var _pointerDocListener = false;
1954
1955 // DomEvent.DoubleTap needs to know about this
1956 var _pointersCount = 0;
1957
1958 // Provides a touch events wrapper for (ms)pointer events.
1959 // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
1960
1961 function addPointerListener(obj, type, handler, id) {
1962         if (type === 'touchstart') {
1963                 _addPointerStart(obj, handler, id);
1964
1965         } else if (type === 'touchmove') {
1966                 _addPointerMove(obj, handler, id);
1967
1968         } else if (type === 'touchend') {
1969                 _addPointerEnd(obj, handler, id);
1970         }
1971
1972         return this;
1973 }
1974
1975 function removePointerListener(obj, type, id) {
1976         var handler = obj['_leaflet_' + type + id];
1977
1978         if (type === 'touchstart') {
1979                 obj.removeEventListener(POINTER_DOWN, handler, false);
1980
1981         } else if (type === 'touchmove') {
1982                 obj.removeEventListener(POINTER_MOVE, handler, false);
1983
1984         } else if (type === 'touchend') {
1985                 obj.removeEventListener(POINTER_UP, handler, false);
1986                 obj.removeEventListener(POINTER_CANCEL, handler, false);
1987         }
1988
1989         return this;
1990 }
1991
1992 function _addPointerStart(obj, handler, id) {
1993         var onDown = bind(function (e) {
1994                 if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
1995                         // In IE11, some touch events needs to fire for form controls, or
1996                         // the controls will stop working. We keep a whitelist of tag names that
1997                         // need these events. For other target tags, we prevent default on the event.
1998                         if (TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
1999                                 preventDefault(e);
2000                         } else {
2001                                 return;
2002                         }
2003                 }
2004
2005                 _handlePointer(e, handler);
2006         });
2007
2008         obj['_leaflet_touchstart' + id] = onDown;
2009         obj.addEventListener(POINTER_DOWN, onDown, false);
2010
2011         // need to keep track of what pointers and how many are active to provide e.touches emulation
2012         if (!_pointerDocListener) {
2013                 // we listen documentElement as any drags that end by moving the touch off the screen get fired there
2014                 document.documentElement.addEventListener(POINTER_DOWN, _globalPointerDown, true);
2015                 document.documentElement.addEventListener(POINTER_MOVE, _globalPointerMove, true);
2016                 document.documentElement.addEventListener(POINTER_UP, _globalPointerUp, true);
2017                 document.documentElement.addEventListener(POINTER_CANCEL, _globalPointerUp, true);
2018
2019                 _pointerDocListener = true;
2020         }
2021 }
2022
2023 function _globalPointerDown(e) {
2024         _pointers[e.pointerId] = e;
2025         _pointersCount++;
2026 }
2027
2028 function _globalPointerMove(e) {
2029         if (_pointers[e.pointerId]) {
2030                 _pointers[e.pointerId] = e;
2031         }
2032 }
2033
2034 function _globalPointerUp(e) {
2035         delete _pointers[e.pointerId];
2036         _pointersCount--;
2037 }
2038
2039 function _handlePointer(e, handler) {
2040         e.touches = [];
2041         for (var i in _pointers) {
2042                 e.touches.push(_pointers[i]);
2043         }
2044         e.changedTouches = [e];
2045
2046         handler(e);
2047 }
2048
2049 function _addPointerMove(obj, handler, id) {
2050         var onMove = function (e) {
2051                 // don't fire touch moves when mouse isn't down
2052                 if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
2053
2054                 _handlePointer(e, handler);
2055         };
2056
2057         obj['_leaflet_touchmove' + id] = onMove;
2058         obj.addEventListener(POINTER_MOVE, onMove, false);
2059 }
2060
2061 function _addPointerEnd(obj, handler, id) {
2062         var onUp = function (e) {
2063                 _handlePointer(e, handler);
2064         };
2065
2066         obj['_leaflet_touchend' + id] = onUp;
2067         obj.addEventListener(POINTER_UP, onUp, false);
2068         obj.addEventListener(POINTER_CANCEL, onUp, false);
2069 }
2070
2071 /*
2072  * Extends the event handling code with double tap support for mobile browsers.
2073  */
2074
2075 var _touchstart = msPointer ? 'MSPointerDown' : pointer ? 'pointerdown' : 'touchstart';
2076 var _touchend = msPointer ? 'MSPointerUp' : pointer ? 'pointerup' : 'touchend';
2077 var _pre = '_leaflet_';
2078
2079 // inspired by Zepto touch code by Thomas Fuchs
2080 function addDoubleTapListener(obj, handler, id) {
2081         var last, touch$$1,
2082             doubleTap = false,
2083             delay = 250;
2084
2085         function onTouchStart(e) {
2086                 var count;
2087
2088                 if (pointer) {
2089                         if ((!edge) || e.pointerType === 'mouse') { return; }
2090                         count = _pointersCount;
2091                 } else {
2092                         count = e.touches.length;
2093                 }
2094
2095                 if (count > 1) { return; }
2096
2097                 var now = Date.now(),
2098                     delta = now - (last || now);
2099
2100                 touch$$1 = e.touches ? e.touches[0] : e;
2101                 doubleTap = (delta > 0 && delta <= delay);
2102                 last = now;
2103         }
2104
2105         function onTouchEnd(e) {
2106                 if (doubleTap && !touch$$1.cancelBubble) {
2107                         if (pointer) {
2108                                 if ((!edge) || e.pointerType === 'mouse') { return; }
2109                                 // work around .type being readonly with MSPointer* events
2110                                 var newTouch = {},
2111                                     prop, i;
2112
2113                                 for (i in touch$$1) {
2114                                         prop = touch$$1[i];
2115                                         newTouch[i] = prop && prop.bind ? prop.bind(touch$$1) : prop;
2116                                 }
2117                                 touch$$1 = newTouch;
2118                         }
2119                         touch$$1.type = 'dblclick';
2120                         handler(touch$$1);
2121                         last = null;
2122                 }
2123         }
2124
2125         obj[_pre + _touchstart + id] = onTouchStart;
2126         obj[_pre + _touchend + id] = onTouchEnd;
2127         obj[_pre + 'dblclick' + id] = handler;
2128
2129         obj.addEventListener(_touchstart, onTouchStart, false);
2130         obj.addEventListener(_touchend, onTouchEnd, false);
2131
2132         // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
2133         // the browser doesn't fire touchend/pointerup events but does fire
2134         // native dblclicks. See #4127.
2135         // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
2136         obj.addEventListener('dblclick', handler, false);
2137
2138         return this;
2139 }
2140
2141 function removeDoubleTapListener(obj, id) {
2142         var touchstart = obj[_pre + _touchstart + id],
2143             touchend = obj[_pre + _touchend + id],
2144             dblclick = obj[_pre + 'dblclick' + id];
2145
2146         obj.removeEventListener(_touchstart, touchstart, false);
2147         obj.removeEventListener(_touchend, touchend, false);
2148         if (!edge) {
2149                 obj.removeEventListener('dblclick', dblclick, false);
2150         }
2151
2152         return this;
2153 }
2154
2155 /*
2156  * @namespace DomEvent
2157  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
2158  */
2159
2160 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
2161
2162 // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
2163 // Adds a listener function (`fn`) to a particular DOM event type of the
2164 // element `el`. You can optionally specify the context of the listener
2165 // (object the `this` keyword will point to). You can also pass several
2166 // space-separated types (e.g. `'click dblclick'`).
2167
2168 // @alternative
2169 // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
2170 // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2171 function on(obj, types, fn, context) {
2172
2173         if (typeof types === 'object') {
2174                 for (var type in types) {
2175                         addOne(obj, type, types[type], fn);
2176                 }
2177         } else {
2178                 types = splitWords(types);
2179
2180                 for (var i = 0, len = types.length; i < len; i++) {
2181                         addOne(obj, types[i], fn, context);
2182                 }
2183         }
2184
2185         return this;
2186 }
2187
2188 var eventsKey = '_leaflet_events';
2189
2190 // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
2191 // Removes a previously added listener function. If no function is specified,
2192 // it will remove all the listeners of that particular DOM event from the element.
2193 // Note that if you passed a custom context to on, you must pass the same
2194 // context to `off` in order to remove the listener.
2195
2196 // @alternative
2197 // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
2198 // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
2199
2200 // @alternative
2201 // @function off(el: HTMLElement): this
2202 // Removes all known event listeners
2203 function off(obj, types, fn, context) {
2204
2205         if (typeof types === 'object') {
2206                 for (var type in types) {
2207                         removeOne(obj, type, types[type], fn);
2208                 }
2209         } else if (types) {
2210                 types = splitWords(types);
2211
2212                 for (var i = 0, len = types.length; i < len; i++) {
2213                         removeOne(obj, types[i], fn, context);
2214                 }
2215         } else {
2216                 for (var j in obj[eventsKey]) {
2217                         removeOne(obj, j, obj[eventsKey][j]);
2218                 }
2219                 delete obj[eventsKey];
2220         }
2221
2222         return this;
2223 }
2224
2225 function addOne(obj, type, fn, context) {
2226         var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');
2227
2228         if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
2229
2230         var handler = function (e) {
2231                 return fn.call(context || obj, e || window.event);
2232         };
2233
2234         var originalHandler = handler;
2235
2236         if (pointer && type.indexOf('touch') === 0) {
2237                 // Needs DomEvent.Pointer.js
2238                 addPointerListener(obj, type, handler, id);
2239
2240         } else if (touch && (type === 'dblclick') && addDoubleTapListener &&
2241                    !(pointer && chrome)) {
2242                 // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
2243                 // See #5180
2244                 addDoubleTapListener(obj, handler, id);
2245
2246         } else if ('addEventListener' in obj) {
2247
2248                 if (type === 'mousewheel') {
2249                         obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2250
2251                 } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
2252                         handler = function (e) {
2253                                 e = e || window.event;
2254                                 if (isExternalTarget(obj, e)) {
2255                                         originalHandler(e);
2256                                 }
2257                         };
2258                         obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
2259
2260                 } else {
2261                         if (type === 'click' && android) {
2262                                 handler = function (e) {
2263                                         filterClick(e, originalHandler);
2264                                 };
2265                         }
2266                         obj.addEventListener(type, handler, false);
2267                 }
2268
2269         } else if ('attachEvent' in obj) {
2270                 obj.attachEvent('on' + type, handler);
2271         }
2272
2273         obj[eventsKey] = obj[eventsKey] || {};
2274         obj[eventsKey][id] = handler;
2275 }
2276
2277 function removeOne(obj, type, fn, context) {
2278
2279         var id = type + stamp(fn) + (context ? '_' + stamp(context) : ''),
2280             handler = obj[eventsKey] && obj[eventsKey][id];
2281
2282         if (!handler) { return this; }
2283
2284         if (pointer && type.indexOf('touch') === 0) {
2285                 removePointerListener(obj, type, id);
2286
2287         } else if (touch && (type === 'dblclick') && removeDoubleTapListener) {
2288                 removeDoubleTapListener(obj, id);
2289
2290         } else if ('removeEventListener' in obj) {
2291
2292                 if (type === 'mousewheel') {
2293                         obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
2294
2295                 } else {
2296                         obj.removeEventListener(
2297                                 type === 'mouseenter' ? 'mouseover' :
2298                                 type === 'mouseleave' ? 'mouseout' : type, handler, false);
2299                 }
2300
2301         } else if ('detachEvent' in obj) {
2302                 obj.detachEvent('on' + type, handler);
2303         }
2304
2305         obj[eventsKey][id] = null;
2306 }
2307
2308 // @function stopPropagation(ev: DOMEvent): this
2309 // Stop the given event from propagation to parent elements. Used inside the listener functions:
2310 // ```js
2311 // L.DomEvent.on(div, 'click', function (ev) {
2312 //      L.DomEvent.stopPropagation(ev);
2313 // });
2314 // ```
2315 function stopPropagation(e) {
2316
2317         if (e.stopPropagation) {
2318                 e.stopPropagation();
2319         } else if (e.originalEvent) {  // In case of Leaflet event.
2320                 e.originalEvent._stopped = true;
2321         } else {
2322                 e.cancelBubble = true;
2323         }
2324         skipped(e);
2325
2326         return this;
2327 }
2328
2329 // @function disableScrollPropagation(el: HTMLElement): this
2330 // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
2331 function disableScrollPropagation(el) {
2332         addOne(el, 'mousewheel', stopPropagation);
2333         return this;
2334 }
2335
2336 // @function disableClickPropagation(el: HTMLElement): this
2337 // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
2338 // `'mousedown'` and `'touchstart'` events (plus browser variants).
2339 function disableClickPropagation(el) {
2340         on(el, 'mousedown touchstart dblclick', stopPropagation);
2341         addOne(el, 'click', fakeStop);
2342         return this;
2343 }
2344
2345 // @function preventDefault(ev: DOMEvent): this
2346 // Prevents the default action of the DOM Event `ev` from happening (such as
2347 // following a link in the href of the a element, or doing a POST request
2348 // with page reload when a `<form>` is submitted).
2349 // Use it inside listener functions.
2350 function preventDefault(e) {
2351         if (e.preventDefault) {
2352                 e.preventDefault();
2353         } else {
2354                 e.returnValue = false;
2355         }
2356         return this;
2357 }
2358
2359 // @function stop(ev): this
2360 // Does `stopPropagation` and `preventDefault` at the same time.
2361 function stop(e) {
2362         preventDefault(e);
2363         stopPropagation(e);
2364         return this;
2365 }
2366
2367 // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
2368 // Gets normalized mouse position from a DOM event relative to the
2369 // `container` or to the whole page if not specified.
2370 function getMousePosition(e, container) {
2371         if (!container) {
2372                 return new Point(e.clientX, e.clientY);
2373         }
2374
2375         var rect = container.getBoundingClientRect();
2376
2377         return new Point(
2378                 e.clientX - rect.left - container.clientLeft,
2379                 e.clientY - rect.top - container.clientTop);
2380 }
2381
2382 // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
2383 // and Firefox scrolls device pixels, not CSS pixels
2384 var wheelPxFactor =
2385         (win && chrome) ? 2 * window.devicePixelRatio :
2386         gecko ? window.devicePixelRatio : 1;
2387
2388 // @function getWheelDelta(ev: DOMEvent): Number
2389 // Gets normalized wheel delta from a mousewheel DOM event, in vertical
2390 // pixels scrolled (negative if scrolling down).
2391 // Events from pointing devices without precise scrolling are mapped to
2392 // a best guess of 60 pixels.
2393 function getWheelDelta(e) {
2394         return (edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
2395                (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
2396                (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
2397                (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
2398                (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
2399                e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
2400                (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
2401                e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
2402                0;
2403 }
2404
2405 var skipEvents = {};
2406
2407 function fakeStop(e) {
2408         // fakes stopPropagation by setting a special event flag, checked/reset with skipped(e)
2409         skipEvents[e.type] = true;
2410 }
2411
2412 function skipped(e) {
2413         var events = skipEvents[e.type];
2414         // reset when checking, as it's only used in map container and propagates outside of the map
2415         skipEvents[e.type] = false;
2416         return events;
2417 }
2418
2419 // check if element really left/entered the event target (for mouseenter/mouseleave)
2420 function isExternalTarget(el, e) {
2421
2422         var related = e.relatedTarget;
2423
2424         if (!related) { return true; }
2425
2426         try {
2427                 while (related && (related !== el)) {
2428                         related = related.parentNode;
2429                 }
2430         } catch (err) {
2431                 return false;
2432         }
2433         return (related !== el);
2434 }
2435
2436 var lastClick;
2437
2438 // this is a horrible workaround for a bug in Android where a single touch triggers two click events
2439 function filterClick(e, handler) {
2440         var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
2441             elapsed = lastClick && (timeStamp - lastClick);
2442
2443         // are they closer together than 500ms yet more than 100ms?
2444         // Android typically triggers them ~300ms apart while multiple listeners
2445         // on the same event should be triggered far faster;
2446         // or check if click is simulated on the element, and if it is, reject any non-simulated events
2447
2448         if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
2449                 stop(e);
2450                 return;
2451         }
2452         lastClick = timeStamp;
2453
2454         handler(e);
2455 }
2456
2457
2458
2459
2460 var DomEvent = (Object.freeze || Object)({
2461         on: on,
2462         off: off,
2463         stopPropagation: stopPropagation,
2464         disableScrollPropagation: disableScrollPropagation,
2465         disableClickPropagation: disableClickPropagation,
2466         preventDefault: preventDefault,
2467         stop: stop,
2468         getMousePosition: getMousePosition,
2469         getWheelDelta: getWheelDelta,
2470         fakeStop: fakeStop,
2471         skipped: skipped,
2472         isExternalTarget: isExternalTarget,
2473         addListener: on,
2474         removeListener: off
2475 });
2476
2477 /*
2478  * @namespace DomUtil
2479  *
2480  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
2481  * tree, used by Leaflet internally.
2482  *
2483  * Most functions expecting or returning a `HTMLElement` also work for
2484  * SVG elements. The only difference is that classes refer to CSS classes
2485  * in HTML and SVG classes in SVG.
2486  */
2487
2488
2489 // @property TRANSFORM: String
2490 // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
2491 var TRANSFORM = testProp(
2492         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2493
2494 // webkitTransition comes first because some browser versions that drop vendor prefix don't do
2495 // the same for the transitionend event, in particular the Android 4.1 stock browser
2496
2497 // @property TRANSITION: String
2498 // Vendor-prefixed transition style name.
2499 var TRANSITION = testProp(
2500         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2501
2502 // @property TRANSITION_END: String
2503 // Vendor-prefixed transitionend event name.
2504 var TRANSITION_END =
2505         TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';
2506
2507
2508 // @function get(id: String|HTMLElement): HTMLElement
2509 // Returns an element given its DOM id, or returns the element itself
2510 // if it was passed directly.
2511 function get(id) {
2512         return typeof id === 'string' ? document.getElementById(id) : id;
2513 }
2514
2515 // @function getStyle(el: HTMLElement, styleAttrib: String): String
2516 // Returns the value for a certain style attribute on an element,
2517 // including computed values or values set through CSS.
2518 function getStyle(el, style) {
2519         var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
2520
2521         if ((!value || value === 'auto') && document.defaultView) {
2522                 var css = document.defaultView.getComputedStyle(el, null);
2523                 value = css ? css[style] : null;
2524         }
2525         return value === 'auto' ? null : value;
2526 }
2527
2528 // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
2529 // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
2530 function create$1(tagName, className, container) {
2531         var el = document.createElement(tagName);
2532         el.className = className || '';
2533
2534         if (container) {
2535                 container.appendChild(el);
2536         }
2537         return el;
2538 }
2539
2540 // @function remove(el: HTMLElement)
2541 // Removes `el` from its parent element
2542 function remove(el) {
2543         var parent = el.parentNode;
2544         if (parent) {
2545                 parent.removeChild(el);
2546         }
2547 }
2548
2549 // @function empty(el: HTMLElement)
2550 // Removes all of `el`'s children elements from `el`
2551 function empty(el) {
2552         while (el.firstChild) {
2553                 el.removeChild(el.firstChild);
2554         }
2555 }
2556
2557 // @function toFront(el: HTMLElement)
2558 // Makes `el` the last child of its parent, so it renders in front of the other children.
2559 function toFront(el) {
2560         var parent = el.parentNode;
2561         if (parent.lastChild !== el) {
2562                 parent.appendChild(el);
2563         }
2564 }
2565
2566 // @function toBack(el: HTMLElement)
2567 // Makes `el` the first child of its parent, so it renders behind the other children.
2568 function toBack(el) {
2569         var parent = el.parentNode;
2570         if (parent.firstChild !== el) {
2571                 parent.insertBefore(el, parent.firstChild);
2572         }
2573 }
2574
2575 // @function hasClass(el: HTMLElement, name: String): Boolean
2576 // Returns `true` if the element's class attribute contains `name`.
2577 function hasClass(el, name) {
2578         if (el.classList !== undefined) {
2579                 return el.classList.contains(name);
2580         }
2581         var className = getClass(el);
2582         return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2583 }
2584
2585 // @function addClass(el: HTMLElement, name: String)
2586 // Adds `name` to the element's class attribute.
2587 function addClass(el, name) {
2588         if (el.classList !== undefined) {
2589                 var classes = splitWords(name);
2590                 for (var i = 0, len = classes.length; i < len; i++) {
2591                         el.classList.add(classes[i]);
2592                 }
2593         } else if (!hasClass(el, name)) {
2594                 var className = getClass(el);
2595                 setClass(el, (className ? className + ' ' : '') + name);
2596         }
2597 }
2598
2599 // @function removeClass(el: HTMLElement, name: String)
2600 // Removes `name` from the element's class attribute.
2601 function removeClass(el, name) {
2602         if (el.classList !== undefined) {
2603                 el.classList.remove(name);
2604         } else {
2605                 setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2606         }
2607 }
2608
2609 // @function setClass(el: HTMLElement, name: String)
2610 // Sets the element's class.
2611 function setClass(el, name) {
2612         if (el.className.baseVal === undefined) {
2613                 el.className = name;
2614         } else {
2615                 // in case of SVG element
2616                 el.className.baseVal = name;
2617         }
2618 }
2619
2620 // @function getClass(el: HTMLElement): String
2621 // Returns the element's class.
2622 function getClass(el) {
2623         return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2624 }
2625
2626 // @function setOpacity(el: HTMLElement, opacity: Number)
2627 // Set the opacity of an element (including old IE support).
2628 // `opacity` must be a number from `0` to `1`.
2629 function setOpacity(el, value) {
2630         if ('opacity' in el.style) {
2631                 el.style.opacity = value;
2632         } else if ('filter' in el.style) {
2633                 _setOpacityIE(el, value);
2634         }
2635 }
2636
2637 function _setOpacityIE(el, value) {
2638         var filter = false,
2639             filterName = 'DXImageTransform.Microsoft.Alpha';
2640
2641         // filters collection throws an error if we try to retrieve a filter that doesn't exist
2642         try {
2643                 filter = el.filters.item(filterName);
2644         } catch (e) {
2645                 // don't set opacity to 1 if we haven't already set an opacity,
2646                 // it isn't needed and breaks transparent pngs.
2647                 if (value === 1) { return; }
2648         }
2649
2650         value = Math.round(value * 100);
2651
2652         if (filter) {
2653                 filter.Enabled = (value !== 100);
2654                 filter.Opacity = value;
2655         } else {
2656                 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2657         }
2658 }
2659
2660 // @function testProp(props: String[]): String|false
2661 // Goes through the array of style names and returns the first name
2662 // that is a valid style name for an element. If no such name is found,
2663 // it returns false. Useful for vendor-prefixed styles like `transform`.
2664 function testProp(props) {
2665         var style = document.documentElement.style;
2666
2667         for (var i = 0; i < props.length; i++) {
2668                 if (props[i] in style) {
2669                         return props[i];
2670                 }
2671         }
2672         return false;
2673 }
2674
2675 // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
2676 // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
2677 // and optionally scaled by `scale`. Does not have an effect if the
2678 // browser doesn't support 3D CSS transforms.
2679 function setTransform(el, offset, scale) {
2680         var pos = offset || new Point(0, 0);
2681
2682         el.style[TRANSFORM] =
2683                 (ie3d ?
2684                         'translate(' + pos.x + 'px,' + pos.y + 'px)' :
2685                         'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
2686                 (scale ? ' scale(' + scale + ')' : '');
2687 }
2688
2689 // @function setPosition(el: HTMLElement, position: Point)
2690 // Sets the position of `el` to coordinates specified by `position`,
2691 // using CSS translate or top/left positioning depending on the browser
2692 // (used by Leaflet internally to position its layers).
2693 function setPosition(el, point) {
2694
2695         /*eslint-disable */
2696         el._leaflet_pos = point;
2697         /*eslint-enable */
2698
2699         if (any3d) {
2700                 setTransform(el, point);
2701         } else {
2702                 el.style.left = point.x + 'px';
2703                 el.style.top = point.y + 'px';
2704         }
2705 }
2706
2707 // @function getPosition(el: HTMLElement): Point
2708 // Returns the coordinates of an element previously positioned with setPosition.
2709 function getPosition(el) {
2710         // this method is only used for elements previously positioned using setPosition,
2711         // so it's safe to cache the position for performance
2712
2713         return el._leaflet_pos || new Point(0, 0);
2714 }
2715
2716 // @function disableTextSelection()
2717 // Prevents the user from generating `selectstart` DOM events, usually generated
2718 // when the user drags the mouse through a page with text. Used internally
2719 // by Leaflet to override the behaviour of any click-and-drag interaction on
2720 // the map. Affects drag interactions on the whole document.
2721
2722 // @function enableTextSelection()
2723 // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
2724 var disableTextSelection;
2725 var enableTextSelection;
2726 var _userSelect;
2727 if ('onselectstart' in document) {
2728         disableTextSelection = function () {
2729                 on(window, 'selectstart', preventDefault);
2730         };
2731         enableTextSelection = function () {
2732                 off(window, 'selectstart', preventDefault);
2733         };
2734 } else {
2735         var userSelectProperty = testProp(
2736                 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2737
2738         disableTextSelection = function () {
2739                 if (userSelectProperty) {
2740                         var style = document.documentElement.style;
2741                         _userSelect = style[userSelectProperty];
2742                         style[userSelectProperty] = 'none';
2743                 }
2744         };
2745         enableTextSelection = function () {
2746                 if (userSelectProperty) {
2747                         document.documentElement.style[userSelectProperty] = _userSelect;
2748                         _userSelect = undefined;
2749                 }
2750         };
2751 }
2752
2753 // @function disableImageDrag()
2754 // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
2755 // for `dragstart` DOM events, usually generated when the user drags an image.
2756 function disableImageDrag() {
2757         on(window, 'dragstart', preventDefault);
2758 }
2759
2760 // @function enableImageDrag()
2761 // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
2762 function enableImageDrag() {
2763         off(window, 'dragstart', preventDefault);
2764 }
2765
2766 var _outlineElement;
2767 var _outlineStyle;
2768 // @function preventOutline(el: HTMLElement)
2769 // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
2770 // of the element `el` invisible. Used internally by Leaflet to prevent
2771 // focusable elements from displaying an outline when the user performs a
2772 // drag interaction on them.
2773 function preventOutline(element) {
2774         while (element.tabIndex === -1) {
2775                 element = element.parentNode;
2776         }
2777         if (!element.style) { return; }
2778         restoreOutline();
2779         _outlineElement = element;
2780         _outlineStyle = element.style.outline;
2781         element.style.outline = 'none';
2782         on(window, 'keydown', restoreOutline);
2783 }
2784
2785 // @function restoreOutline()
2786 // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
2787 function restoreOutline() {
2788         if (!_outlineElement) { return; }
2789         _outlineElement.style.outline = _outlineStyle;
2790         _outlineElement = undefined;
2791         _outlineStyle = undefined;
2792         off(window, 'keydown', restoreOutline);
2793 }
2794
2795
2796 var DomUtil = (Object.freeze || Object)({
2797         TRANSFORM: TRANSFORM,
2798         TRANSITION: TRANSITION,
2799         TRANSITION_END: TRANSITION_END,
2800         get: get,
2801         getStyle: getStyle,
2802         create: create$1,
2803         remove: remove,
2804         empty: empty,
2805         toFront: toFront,
2806         toBack: toBack,
2807         hasClass: hasClass,
2808         addClass: addClass,
2809         removeClass: removeClass,
2810         setClass: setClass,
2811         getClass: getClass,
2812         setOpacity: setOpacity,
2813         testProp: testProp,
2814         setTransform: setTransform,
2815         setPosition: setPosition,
2816         getPosition: getPosition,
2817         disableTextSelection: disableTextSelection,
2818         enableTextSelection: enableTextSelection,
2819         disableImageDrag: disableImageDrag,
2820         enableImageDrag: enableImageDrag,
2821         preventOutline: preventOutline,
2822         restoreOutline: restoreOutline
2823 });
2824
2825 /*
2826  * @class PosAnimation
2827  * @aka L.PosAnimation
2828  * @inherits Evented
2829  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
2830  *
2831  * @example
2832  * ```js
2833  * var fx = new L.PosAnimation();
2834  * fx.run(el, [300, 500], 0.5);
2835  * ```
2836  *
2837  * @constructor L.PosAnimation()
2838  * Creates a `PosAnimation` object.
2839  *
2840  */
2841
2842 var PosAnimation = Evented.extend({
2843
2844         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
2845         // Run an animation of a given element to a new position, optionally setting
2846         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
2847         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
2848         // `0.5` by default).
2849         run: function (el, newPos, duration, easeLinearity) {
2850                 this.stop();
2851
2852                 this._el = el;
2853                 this._inProgress = true;
2854                 this._duration = duration || 0.25;
2855                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
2856
2857                 this._startPos = getPosition(el);
2858                 this._offset = newPos.subtract(this._startPos);
2859                 this._startTime = +new Date();
2860
2861                 // @event start: Event
2862                 // Fired when the animation starts
2863                 this.fire('start');
2864
2865                 this._animate();
2866         },
2867
2868         // @method stop()
2869         // Stops the animation (if currently running).
2870         stop: function () {
2871                 if (!this._inProgress) { return; }
2872
2873                 this._step(true);
2874                 this._complete();
2875         },
2876
2877         _animate: function () {
2878                 // animation loop
2879                 this._animId = requestAnimFrame(this._animate, this);
2880                 this._step();
2881         },
2882
2883         _step: function (round) {
2884                 var elapsed = (+new Date()) - this._startTime,
2885                     duration = this._duration * 1000;
2886
2887                 if (elapsed < duration) {
2888                         this._runFrame(this._easeOut(elapsed / duration), round);
2889                 } else {
2890                         this._runFrame(1);
2891                         this._complete();
2892                 }
2893         },
2894
2895         _runFrame: function (progress, round) {
2896                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
2897                 if (round) {
2898                         pos._round();
2899                 }
2900                 setPosition(this._el, pos);
2901
2902                 // @event step: Event
2903                 // Fired continuously during the animation.
2904                 this.fire('step');
2905         },
2906
2907         _complete: function () {
2908                 cancelAnimFrame(this._animId);
2909
2910                 this._inProgress = false;
2911                 // @event end: Event
2912                 // Fired when the animation ends.
2913                 this.fire('end');
2914         },
2915
2916         _easeOut: function (t) {
2917                 return 1 - Math.pow(1 - t, this._easeOutPower);
2918         }
2919 });
2920
2921 /*
2922  * @class Map
2923  * @aka L.Map
2924  * @inherits Evented
2925  *
2926  * The central class of the API — it is used to create a map on a page and manipulate it.
2927  *
2928  * @example
2929  *
2930  * ```js
2931  * // initialize the map on the "map" div with a given center and zoom
2932  * var map = L.map('map', {
2933  *      center: [51.505, -0.09],
2934  *      zoom: 13
2935  * });
2936  * ```
2937  *
2938  */
2939
2940 var Map = Evented.extend({
2941
2942         options: {
2943                 // @section Map State Options
2944                 // @option crs: CRS = L.CRS.EPSG3857
2945                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2946                 // sure what it means.
2947                 crs: EPSG3857,
2948
2949                 // @option center: LatLng = undefined
2950                 // Initial geographic center of the map
2951                 center: undefined,
2952
2953                 // @option zoom: Number = undefined
2954                 // Initial map zoom level
2955                 zoom: undefined,
2956
2957                 // @option minZoom: Number = *
2958                 // Minimum zoom level of the map.
2959                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2960                 // the lowest of their `minZoom` options will be used instead.
2961                 minZoom: undefined,
2962
2963                 // @option maxZoom: Number = *
2964                 // Maximum zoom level of the map.
2965                 // If not specified and at least one `GridLayer` or `TileLayer` is in the map,
2966                 // the highest of their `maxZoom` options will be used instead.
2967                 maxZoom: undefined,
2968
2969                 // @option layers: Layer[] = []
2970                 // Array of layers that will be added to the map initially
2971                 layers: [],
2972
2973                 // @option maxBounds: LatLngBounds = null
2974                 // When this option is set, the map restricts the view to the given
2975                 // geographical bounds, bouncing the user back if the user tries to pan
2976                 // outside the view. To set the restriction dynamically, use
2977                 // [`setMaxBounds`](#map-setmaxbounds) method.
2978                 maxBounds: undefined,
2979
2980                 // @option renderer: Renderer = *
2981                 // The default method for drawing vector layers on the map. `L.SVG`
2982                 // or `L.Canvas` by default depending on browser support.
2983                 renderer: undefined,
2984
2985
2986                 // @section Animation Options
2987                 // @option zoomAnimation: Boolean = true
2988                 // Whether the map zoom animation is enabled. By default it's enabled
2989                 // in all browsers that support CSS3 Transitions except Android.
2990                 zoomAnimation: true,
2991
2992                 // @option zoomAnimationThreshold: Number = 4
2993                 // Won't animate zoom if the zoom difference exceeds this value.
2994                 zoomAnimationThreshold: 4,
2995
2996                 // @option fadeAnimation: Boolean = true
2997                 // Whether the tile fade animation is enabled. By default it's enabled
2998                 // in all browsers that support CSS3 Transitions except Android.
2999                 fadeAnimation: true,
3000
3001                 // @option markerZoomAnimation: Boolean = true
3002                 // Whether markers animate their zoom with the zoom animation, if disabled
3003                 // they will disappear for the length of the animation. By default it's
3004                 // enabled in all browsers that support CSS3 Transitions except Android.
3005                 markerZoomAnimation: true,
3006
3007                 // @option transform3DLimit: Number = 2^23
3008                 // Defines the maximum size of a CSS translation transform. The default
3009                 // value should not be changed unless a web browser positions layers in
3010                 // the wrong place after doing a large `panBy`.
3011                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
3012
3013                 // @section Interaction Options
3014                 // @option zoomSnap: Number = 1
3015                 // Forces the map's zoom level to always be a multiple of this, particularly
3016                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
3017                 // By default, the zoom level snaps to the nearest integer; lower values
3018                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
3019                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
3020                 zoomSnap: 1,
3021
3022                 // @option zoomDelta: Number = 1
3023                 // Controls how much the map's zoom level will change after a
3024                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
3025                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
3026                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
3027                 zoomDelta: 1,
3028
3029                 // @option trackResize: Boolean = true
3030                 // Whether the map automatically handles browser window resize to update itself.
3031                 trackResize: true
3032         },
3033
3034         initialize: function (id, options) { // (HTMLElement or String, Object)
3035                 options = setOptions(this, options);
3036
3037                 this._initContainer(id);
3038                 this._initLayout();
3039
3040                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
3041                 this._onResize = bind(this._onResize, this);
3042
3043                 this._initEvents();
3044
3045                 if (options.maxBounds) {
3046                         this.setMaxBounds(options.maxBounds);
3047                 }
3048
3049                 if (options.zoom !== undefined) {
3050                         this._zoom = this._limitZoom(options.zoom);
3051                 }
3052
3053                 if (options.center && options.zoom !== undefined) {
3054                         this.setView(toLatLng(options.center), options.zoom, {reset: true});
3055                 }
3056
3057                 this._handlers = [];
3058                 this._layers = {};
3059                 this._zoomBoundLayers = {};
3060                 this._sizeChanged = true;
3061
3062                 this.callInitHooks();
3063
3064                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
3065                 this._zoomAnimated = TRANSITION && any3d && !mobileOpera &&
3066                                 this.options.zoomAnimation;
3067
3068                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
3069                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
3070                 if (this._zoomAnimated) {
3071                         this._createAnimProxy();
3072                         on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
3073                 }
3074
3075                 this._addLayers(this.options.layers);
3076         },
3077
3078
3079         // @section Methods for modifying map state
3080
3081         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
3082         // Sets the view of the map (geographical center and zoom) with the given
3083         // animation options.
3084         setView: function (center, zoom, options) {
3085
3086                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
3087                 center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
3088                 options = options || {};
3089
3090                 this._stop();
3091
3092                 if (this._loaded && !options.reset && options !== true) {
3093
3094                         if (options.animate !== undefined) {
3095                                 options.zoom = extend({animate: options.animate}, options.zoom);
3096                                 options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
3097                         }
3098
3099                         // try animating pan or zoom
3100                         var moved = (this._zoom !== zoom) ?
3101                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
3102                                 this._tryAnimatedPan(center, options.pan);
3103
3104                         if (moved) {
3105                                 // prevent resize handler call, the view will refresh after animation anyway
3106                                 clearTimeout(this._sizeTimer);
3107                                 return this;
3108                         }
3109                 }
3110
3111                 // animation didn't start, just reset the map view
3112                 this._resetView(center, zoom);
3113
3114                 return this;
3115         },
3116
3117         // @method setZoom(zoom: Number, options?: Zoom/pan options): this
3118         // Sets the zoom of the map.
3119         setZoom: function (zoom, options) {
3120                 if (!this._loaded) {
3121                         this._zoom = zoom;
3122                         return this;
3123                 }
3124                 return this.setView(this.getCenter(), zoom, {zoom: options});
3125         },
3126
3127         // @method zoomIn(delta?: Number, options?: Zoom options): this
3128         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3129         zoomIn: function (delta, options) {
3130                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3131                 return this.setZoom(this._zoom + delta, options);
3132         },
3133
3134         // @method zoomOut(delta?: Number, options?: Zoom options): this
3135         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
3136         zoomOut: function (delta, options) {
3137                 delta = delta || (any3d ? this.options.zoomDelta : 1);
3138                 return this.setZoom(this._zoom - delta, options);
3139         },
3140
3141         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
3142         // Zooms the map while keeping a specified geographical point on the map
3143         // stationary (e.g. used internally for scroll zoom and double-click zoom).
3144         // @alternative
3145         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
3146         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
3147         setZoomAround: function (latlng, zoom, options) {
3148                 var scale = this.getZoomScale(zoom),
3149                     viewHalf = this.getSize().divideBy(2),
3150                     containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),
3151
3152                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3153                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3154
3155                 return this.setView(newCenter, zoom, {zoom: options});
3156         },
3157
3158         _getBoundsCenterZoom: function (bounds, options) {
3159
3160                 options = options || {};
3161                 bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);
3162
3163                 var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
3164                     paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
3165
3166                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3167
3168                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
3169
3170                 if (zoom === Infinity) {
3171                         return {
3172                                 center: bounds.getCenter(),
3173                                 zoom: zoom
3174                         };
3175                 }
3176
3177                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3178
3179                     swPoint = this.project(bounds.getSouthWest(), zoom),
3180                     nePoint = this.project(bounds.getNorthEast(), zoom),
3181                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3182
3183                 return {
3184                         center: center,
3185                         zoom: zoom
3186                 };
3187         },
3188
3189         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
3190         // Sets a map view that contains the given geographical bounds with the
3191         // maximum zoom level possible.
3192         fitBounds: function (bounds, options) {
3193
3194                 bounds = toLatLngBounds(bounds);
3195
3196                 if (!bounds.isValid()) {
3197                         throw new Error('Bounds are not valid.');
3198                 }
3199
3200                 var target = this._getBoundsCenterZoom(bounds, options);
3201                 return this.setView(target.center, target.zoom, options);
3202         },
3203
3204         // @method fitWorld(options?: fitBounds options): this
3205         // Sets a map view that mostly contains the whole world with the maximum
3206         // zoom level possible.
3207         fitWorld: function (options) {
3208                 return this.fitBounds([[-90, -180], [90, 180]], options);
3209         },
3210
3211         // @method panTo(latlng: LatLng, options?: Pan options): this
3212         // Pans the map to a given center.
3213         panTo: function (center, options) { // (LatLng)
3214                 return this.setView(center, this._zoom, {pan: options});
3215         },
3216
3217         // @method panBy(offset: Point, options?: Pan options): this
3218         // Pans the map by a given number of pixels (animated).
3219         panBy: function (offset, options) {
3220                 offset = toPoint(offset).round();
3221                 options = options || {};
3222
3223                 if (!offset.x && !offset.y) {
3224                         return this.fire('moveend');
3225                 }
3226                 // If we pan too far, Chrome gets issues with tiles
3227                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
3228                 if (options.animate !== true && !this.getSize().contains(offset)) {
3229                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
3230                         return this;
3231                 }
3232
3233                 if (!this._panAnim) {
3234                         this._panAnim = new PosAnimation();
3235
3236                         this._panAnim.on({
3237                                 'step': this._onPanTransitionStep,
3238                                 'end': this._onPanTransitionEnd
3239                         }, this);
3240                 }
3241
3242                 // don't fire movestart if animating inertia
3243                 if (!options.noMoveStart) {
3244                         this.fire('movestart');
3245                 }
3246
3247                 // animate pan unless animate: false specified
3248                 if (options.animate !== false) {
3249                         addClass(this._mapPane, 'leaflet-pan-anim');
3250
3251                         var newPos = this._getMapPanePos().subtract(offset).round();
3252                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
3253                 } else {
3254                         this._rawPanBy(offset);
3255                         this.fire('move').fire('moveend');
3256                 }
3257
3258                 return this;
3259         },
3260
3261         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
3262         // Sets the view of the map (geographical center and zoom) performing a smooth
3263         // pan-zoom animation.
3264         flyTo: function (targetCenter, targetZoom, options) {
3265
3266                 options = options || {};
3267                 if (options.animate === false || !any3d) {
3268                         return this.setView(targetCenter, targetZoom, options);
3269                 }
3270
3271                 this._stop();
3272
3273                 var from = this.project(this.getCenter()),
3274                     to = this.project(targetCenter),
3275                     size = this.getSize(),
3276                     startZoom = this._zoom;
3277
3278                 targetCenter = toLatLng(targetCenter);
3279                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
3280
3281                 var w0 = Math.max(size.x, size.y),
3282                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
3283                     u1 = (to.distanceTo(from)) || 1,
3284                     rho = 1.42,
3285                     rho2 = rho * rho;
3286
3287                 function r(i) {
3288                         var s1 = i ? -1 : 1,
3289                             s2 = i ? w1 : w0,
3290                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
3291                             b1 = 2 * s2 * rho2 * u1,
3292                             b = t1 / b1,
3293                             sq = Math.sqrt(b * b + 1) - b;
3294
3295                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
3296                             // thus triggering an infinite loop in flyTo
3297                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
3298
3299                         return log;
3300                 }
3301
3302                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
3303                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
3304                 function tanh(n) { return sinh(n) / cosh(n); }
3305
3306                 var r0 = r(0);
3307
3308                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
3309                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
3310
3311                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
3312
3313                 var start = Date.now(),
3314                     S = (r(1) - r0) / rho,
3315                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
3316
3317                 function frame() {
3318                         var t = (Date.now() - start) / duration,
3319                             s = easeOut(t) * S;
3320
3321                         if (t <= 1) {
3322                                 this._flyToFrame = requestAnimFrame(frame, this);
3323
3324                                 this._move(
3325                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
3326                                         this.getScaleZoom(w0 / w(s), startZoom),
3327                                         {flyTo: true});
3328
3329                         } else {
3330                                 this
3331                                         ._move(targetCenter, targetZoom)
3332                                         ._moveEnd(true);
3333                         }
3334                 }
3335
3336                 this._moveStart(true);
3337
3338                 frame.call(this);
3339                 return this;
3340         },
3341
3342         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
3343         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
3344         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
3345         flyToBounds: function (bounds, options) {
3346                 var target = this._getBoundsCenterZoom(bounds, options);
3347                 return this.flyTo(target.center, target.zoom, options);
3348         },
3349
3350         // @method setMaxBounds(bounds: Bounds): this
3351         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
3352         setMaxBounds: function (bounds) {
3353                 bounds = toLatLngBounds(bounds);
3354
3355                 if (!bounds.isValid()) {
3356                         this.options.maxBounds = null;
3357                         return this.off('moveend', this._panInsideMaxBounds);
3358                 } else if (this.options.maxBounds) {
3359                         this.off('moveend', this._panInsideMaxBounds);
3360                 }
3361
3362                 this.options.maxBounds = bounds;
3363
3364                 if (this._loaded) {
3365                         this._panInsideMaxBounds();
3366                 }
3367
3368                 return this.on('moveend', this._panInsideMaxBounds);
3369         },
3370
3371         // @method setMinZoom(zoom: Number): this
3372         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
3373         setMinZoom: function (zoom) {
3374                 this.options.minZoom = zoom;
3375
3376                 if (this._loaded && this.getZoom() < this.options.minZoom) {
3377                         return this.setZoom(zoom);
3378                 }
3379
3380                 return this;
3381         },
3382
3383         // @method setMaxZoom(zoom: Number): this
3384         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
3385         setMaxZoom: function (zoom) {
3386                 this.options.maxZoom = zoom;
3387
3388                 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
3389                         return this.setZoom(zoom);
3390                 }
3391
3392                 return this;
3393         },
3394
3395         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
3396         // 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.
3397         panInsideBounds: function (bounds, options) {
3398                 this._enforcingBounds = true;
3399                 var center = this.getCenter(),
3400                     newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));
3401
3402                 if (!center.equals(newCenter)) {
3403                         this.panTo(newCenter, options);
3404                 }
3405
3406                 this._enforcingBounds = false;
3407                 return this;
3408         },
3409
3410         // @method invalidateSize(options: Zoom/Pan options): this
3411         // Checks if the map container size changed and updates the map if so —
3412         // call it after you've changed the map size dynamically, also animating
3413         // pan by default. If `options.pan` is `false`, panning will not occur.
3414         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
3415         // that it doesn't happen often even if the method is called many
3416         // times in a row.
3417
3418         // @alternative
3419         // @method invalidateSize(animate: Boolean): this
3420         // Checks if the map container size changed and updates the map if so —
3421         // call it after you've changed the map size dynamically, also animating
3422         // pan by default.
3423         invalidateSize: function (options) {
3424                 if (!this._loaded) { return this; }
3425
3426                 options = extend({
3427                         animate: false,
3428                         pan: true
3429                 }, options === true ? {animate: true} : options);
3430
3431                 var oldSize = this.getSize();
3432                 this._sizeChanged = true;
3433                 this._lastCenter = null;
3434
3435                 var newSize = this.getSize(),
3436                     oldCenter = oldSize.divideBy(2).round(),
3437                     newCenter = newSize.divideBy(2).round(),
3438                     offset = oldCenter.subtract(newCenter);
3439
3440                 if (!offset.x && !offset.y) { return this; }
3441
3442                 if (options.animate && options.pan) {
3443                         this.panBy(offset);
3444
3445                 } else {
3446                         if (options.pan) {
3447                                 this._rawPanBy(offset);
3448                         }
3449
3450                         this.fire('move');
3451
3452                         if (options.debounceMoveend) {
3453                                 clearTimeout(this._sizeTimer);
3454                                 this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
3455                         } else {
3456                                 this.fire('moveend');
3457                         }
3458                 }
3459
3460                 // @section Map state change events
3461                 // @event resize: ResizeEvent
3462                 // Fired when the map is resized.
3463                 return this.fire('resize', {
3464                         oldSize: oldSize,
3465                         newSize: newSize
3466                 });
3467         },
3468
3469         // @section Methods for modifying map state
3470         // @method stop(): this
3471         // Stops the currently running `panTo` or `flyTo` animation, if any.
3472         stop: function () {
3473                 this.setZoom(this._limitZoom(this._zoom));
3474                 if (!this.options.zoomSnap) {
3475                         this.fire('viewreset');
3476                 }
3477                 return this._stop();
3478         },
3479
3480         // @section Geolocation methods
3481         // @method locate(options?: Locate options): this
3482         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
3483         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
3484         // and optionally sets the map view to the user's location with respect to
3485         // detection accuracy (or to the world view if geolocation failed).
3486         // Note that, if your page doesn't use HTTPS, this method will fail in
3487         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
3488         // See `Locate options` for more details.
3489         locate: function (options) {
3490
3491                 options = this._locateOptions = extend({
3492                         timeout: 10000,
3493                         watch: false
3494                         // setView: false
3495                         // maxZoom: <Number>
3496                         // maximumAge: 0
3497                         // enableHighAccuracy: false
3498                 }, options);
3499
3500                 if (!('geolocation' in navigator)) {
3501                         this._handleGeolocationError({
3502                                 code: 0,
3503                                 message: 'Geolocation not supported.'
3504                         });
3505                         return this;
3506                 }
3507
3508                 var onResponse = bind(this._handleGeolocationResponse, this),
3509                     onError = bind(this._handleGeolocationError, this);
3510
3511                 if (options.watch) {
3512                         this._locationWatchId =
3513                                 navigator.geolocation.watchPosition(onResponse, onError, options);
3514                 } else {
3515                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
3516                 }
3517                 return this;
3518         },
3519
3520         // @method stopLocate(): this
3521         // Stops watching location previously initiated by `map.locate({watch: true})`
3522         // and aborts resetting the map view if map.locate was called with
3523         // `{setView: true}`.
3524         stopLocate: function () {
3525                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
3526                         navigator.geolocation.clearWatch(this._locationWatchId);
3527                 }
3528                 if (this._locateOptions) {
3529                         this._locateOptions.setView = false;
3530                 }
3531                 return this;
3532         },
3533
3534         _handleGeolocationError: function (error) {
3535                 var c = error.code,
3536                     message = error.message ||
3537                             (c === 1 ? 'permission denied' :
3538                             (c === 2 ? 'position unavailable' : 'timeout'));
3539
3540                 if (this._locateOptions.setView && !this._loaded) {
3541                         this.fitWorld();
3542                 }
3543
3544                 // @section Location events
3545                 // @event locationerror: ErrorEvent
3546                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
3547                 this.fire('locationerror', {
3548                         code: c,
3549                         message: 'Geolocation error: ' + message + '.'
3550                 });
3551         },
3552
3553         _handleGeolocationResponse: function (pos) {
3554                 var lat = pos.coords.latitude,
3555                     lng = pos.coords.longitude,
3556                     latlng = new LatLng(lat, lng),
3557                     bounds = latlng.toBounds(pos.coords.accuracy),
3558                     options = this._locateOptions;
3559
3560                 if (options.setView) {
3561                         var zoom = this.getBoundsZoom(bounds);
3562                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
3563                 }
3564
3565                 var data = {
3566                         latlng: latlng,
3567                         bounds: bounds,
3568                         timestamp: pos.timestamp
3569                 };
3570
3571                 for (var i in pos.coords) {
3572                         if (typeof pos.coords[i] === 'number') {
3573                                 data[i] = pos.coords[i];
3574                         }
3575                 }
3576
3577                 // @event locationfound: LocationEvent
3578                 // Fired when geolocation (using the [`locate`](#map-locate) method)
3579                 // went successfully.
3580                 this.fire('locationfound', data);
3581         },
3582
3583         // TODO handler.addTo
3584         // TODO Appropiate docs section?
3585         // @section Other Methods
3586         // @method addHandler(name: String, HandlerClass: Function): this
3587         // Adds a new `Handler` to the map, given its name and constructor function.
3588         addHandler: function (name, HandlerClass) {
3589                 if (!HandlerClass) { return this; }
3590
3591                 var handler = this[name] = new HandlerClass(this);
3592
3593                 this._handlers.push(handler);
3594
3595                 if (this.options[name]) {
3596                         handler.enable();
3597                 }
3598
3599                 return this;
3600         },
3601
3602         // @method remove(): this
3603         // Destroys the map and clears all related event listeners.
3604         remove: function () {
3605
3606                 this._initEvents(true);
3607
3608                 if (this._containerId !== this._container._leaflet_id) {
3609                         throw new Error('Map container is being reused by another instance');
3610                 }
3611
3612                 try {
3613                         // throws error in IE6-8
3614                         delete this._container._leaflet_id;
3615                         delete this._containerId;
3616                 } catch (e) {
3617                         /*eslint-disable */
3618                         this._container._leaflet_id = undefined;
3619                         /*eslint-enable */
3620                         this._containerId = undefined;
3621                 }
3622
3623                 remove(this._mapPane);
3624
3625                 if (this._clearControlPos) {
3626                         this._clearControlPos();
3627                 }
3628
3629                 this._clearHandlers();
3630
3631                 if (this._loaded) {
3632                         // @section Map state change events
3633                         // @event unload: Event
3634                         // Fired when the map is destroyed with [remove](#map-remove) method.
3635                         this.fire('unload');
3636                 }
3637
3638                 var i;
3639                 for (i in this._layers) {
3640                         this._layers[i].remove();
3641                 }
3642                 for (i in this._panes) {
3643                         remove(this._panes[i]);
3644                 }
3645
3646                 this._layers = [];
3647                 this._panes = [];
3648                 delete this._mapPane;
3649                 delete this._renderer;
3650
3651                 return this;
3652         },
3653
3654         // @section Other Methods
3655         // @method createPane(name: String, container?: HTMLElement): HTMLElement
3656         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
3657         // then returns it. The pane is created as a child of `container`, or
3658         // as a child of the main map pane if not set.
3659         createPane: function (name, container) {
3660                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3661                     pane = create$1('div', className, container || this._mapPane);
3662
3663                 if (name) {
3664                         this._panes[name] = pane;
3665                 }
3666                 return pane;
3667         },
3668
3669         // @section Methods for Getting Map State
3670
3671         // @method getCenter(): LatLng
3672         // Returns the geographical center of the map view
3673         getCenter: function () {
3674                 this._checkIfLoaded();
3675
3676                 if (this._lastCenter && !this._moved()) {
3677                         return this._lastCenter;
3678                 }
3679                 return this.layerPointToLatLng(this._getCenterLayerPoint());
3680         },
3681
3682         // @method getZoom(): Number
3683         // Returns the current zoom level of the map view
3684         getZoom: function () {
3685                 return this._zoom;
3686         },
3687
3688         // @method getBounds(): LatLngBounds
3689         // Returns the geographical bounds visible in the current map view
3690         getBounds: function () {
3691                 var bounds = this.getPixelBounds(),
3692                     sw = this.unproject(bounds.getBottomLeft()),
3693                     ne = this.unproject(bounds.getTopRight());
3694
3695                 return new LatLngBounds(sw, ne);
3696         },
3697
3698         // @method getMinZoom(): Number
3699         // 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.
3700         getMinZoom: function () {
3701                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3702         },
3703
3704         // @method getMaxZoom(): Number
3705         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3706         getMaxZoom: function () {
3707                 return this.options.maxZoom === undefined ?
3708                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3709                         this.options.maxZoom;
3710         },
3711
3712         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3713         // Returns the maximum zoom level on which the given bounds fit to the map
3714         // view in its entirety. If `inside` (optional) is set to `true`, the method
3715         // instead returns the minimum zoom level on which the map view fits into
3716         // the given bounds in its entirety.
3717         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3718                 bounds = toLatLngBounds(bounds);
3719                 padding = toPoint(padding || [0, 0]);
3720
3721                 var zoom = this.getZoom() || 0,
3722                     min = this.getMinZoom(),
3723                     max = this.getMaxZoom(),
3724                     nw = bounds.getNorthWest(),
3725                     se = bounds.getSouthEast(),
3726                     size = this.getSize().subtract(padding),
3727                     boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3728                     snap = any3d ? this.options.zoomSnap : 1,
3729                     scalex = size.x / boundsSize.x,
3730                     scaley = size.y / boundsSize.y,
3731                     scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);
3732
3733                 zoom = this.getScaleZoom(scale, zoom);
3734
3735                 if (snap) {
3736                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3737                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3738                 }
3739
3740                 return Math.max(min, Math.min(max, zoom));
3741         },
3742
3743         // @method getSize(): Point
3744         // Returns the current size of the map container (in pixels).
3745         getSize: function () {
3746                 if (!this._size || this._sizeChanged) {
3747                         this._size = new Point(
3748                                 this._container.clientWidth || 0,
3749                                 this._container.clientHeight || 0);
3750
3751                         this._sizeChanged = false;
3752                 }
3753                 return this._size.clone();
3754         },
3755
3756         // @method getPixelBounds(): Bounds
3757         // Returns the bounds of the current map view in projected pixel
3758         // coordinates (sometimes useful in layer and overlay implementations).
3759         getPixelBounds: function (center, zoom) {
3760                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3761                 return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3762         },
3763
3764         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3765         // the map pane? "left point of the map layer" can be confusing, specially
3766         // since there can be negative offsets.
3767         // @method getPixelOrigin(): Point
3768         // Returns the projected pixel coordinates of the top left point of
3769         // the map layer (useful in custom layer and overlay implementations).
3770         getPixelOrigin: function () {
3771                 this._checkIfLoaded();
3772                 return this._pixelOrigin;
3773         },
3774
3775         // @method getPixelWorldBounds(zoom?: Number): Bounds
3776         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3777         // If `zoom` is omitted, the map's current zoom level is used.
3778         getPixelWorldBounds: function (zoom) {
3779                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3780         },
3781
3782         // @section Other Methods
3783
3784         // @method getPane(pane: String|HTMLElement): HTMLElement
3785         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3786         getPane: function (pane) {
3787                 return typeof pane === 'string' ? this._panes[pane] : pane;
3788         },
3789
3790         // @method getPanes(): Object
3791         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3792         // the panes as values.
3793         getPanes: function () {
3794                 return this._panes;
3795         },
3796
3797         // @method getContainer: HTMLElement
3798         // Returns the HTML element that contains the map.
3799         getContainer: function () {
3800                 return this._container;
3801         },
3802
3803
3804         // @section Conversion Methods
3805
3806         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3807         // Returns the scale factor to be applied to a map transition from zoom level
3808         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3809         getZoomScale: function (toZoom, fromZoom) {
3810                 // TODO replace with universal implementation after refactoring projections
3811                 var crs = this.options.crs;
3812                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3813                 return crs.scale(toZoom) / crs.scale(fromZoom);
3814         },
3815
3816         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3817         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3818         // level and everything is scaled by a factor of `scale`. Inverse of
3819         // [`getZoomScale`](#map-getZoomScale).
3820         getScaleZoom: function (scale, fromZoom) {
3821                 var crs = this.options.crs;
3822                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3823                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3824                 return isNaN(zoom) ? Infinity : zoom;
3825         },
3826
3827         // @method project(latlng: LatLng, zoom: Number): Point
3828         // Projects a geographical coordinate `LatLng` according to the projection
3829         // of the map's CRS, then scales it according to `zoom` and the CRS's
3830         // `Transformation`. The result is pixel coordinate relative to
3831         // the CRS origin.
3832         project: function (latlng, zoom) {
3833                 zoom = zoom === undefined ? this._zoom : zoom;
3834                 return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
3835         },
3836
3837         // @method unproject(point: Point, zoom: Number): LatLng
3838         // Inverse of [`project`](#map-project).
3839         unproject: function (point, zoom) {
3840                 zoom = zoom === undefined ? this._zoom : zoom;
3841                 return this.options.crs.pointToLatLng(toPoint(point), zoom);
3842         },
3843
3844         // @method layerPointToLatLng(point: Point): LatLng
3845         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3846         // returns the corresponding geographical coordinate (for the current zoom level).
3847         layerPointToLatLng: function (point) {
3848                 var projectedPoint = toPoint(point).add(this.getPixelOrigin());
3849                 return this.unproject(projectedPoint);
3850         },
3851
3852         // @method latLngToLayerPoint(latlng: LatLng): Point
3853         // Given a geographical coordinate, returns the corresponding pixel coordinate
3854         // relative to the [origin pixel](#map-getpixelorigin).
3855         latLngToLayerPoint: function (latlng) {
3856                 var projectedPoint = this.project(toLatLng(latlng))._round();
3857                 return projectedPoint._subtract(this.getPixelOrigin());
3858         },
3859
3860         // @method wrapLatLng(latlng: LatLng): LatLng
3861         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3862         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3863         // CRS's bounds.
3864         // By default this means longitude is wrapped around the dateline so its
3865         // value is between -180 and +180 degrees.
3866         wrapLatLng: function (latlng) {
3867                 return this.options.crs.wrapLatLng(toLatLng(latlng));
3868         },
3869
3870         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3871         // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3872         // its center is within the CRS's bounds.
3873         // By default this means the center longitude is wrapped around the dateline so its
3874         // value is between -180 and +180 degrees, and the majority of the bounds
3875         // overlaps the CRS's bounds.
3876         wrapLatLngBounds: function (latlng) {
3877                 return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
3878         },
3879
3880         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3881         // Returns the distance between two geographical coordinates according to
3882         // the map's CRS. By default this measures distance in meters.
3883         distance: function (latlng1, latlng2) {
3884                 return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
3885         },
3886
3887         // @method containerPointToLayerPoint(point: Point): Point
3888         // Given a pixel coordinate relative to the map container, returns the corresponding
3889         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3890         containerPointToLayerPoint: function (point) { // (Point)
3891                 return toPoint(point).subtract(this._getMapPanePos());
3892         },
3893
3894         // @method layerPointToContainerPoint(point: Point): Point
3895         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3896         // returns the corresponding pixel coordinate relative to the map container.
3897         layerPointToContainerPoint: function (point) { // (Point)
3898                 return toPoint(point).add(this._getMapPanePos());
3899         },
3900
3901         // @method containerPointToLatLng(point: Point): LatLng
3902         // Given a pixel coordinate relative to the map container, returns
3903         // the corresponding geographical coordinate (for the current zoom level).
3904         containerPointToLatLng: function (point) {
3905                 var layerPoint = this.containerPointToLayerPoint(toPoint(point));
3906                 return this.layerPointToLatLng(layerPoint);
3907         },
3908
3909         // @method latLngToContainerPoint(latlng: LatLng): Point
3910         // Given a geographical coordinate, returns the corresponding pixel coordinate
3911         // relative to the map container.
3912         latLngToContainerPoint: function (latlng) {
3913                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
3914         },
3915
3916         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3917         // Given a MouseEvent object, returns the pixel coordinate relative to the
3918         // map container where the event took place.
3919         mouseEventToContainerPoint: function (e) {
3920                 return getMousePosition(e, this._container);
3921         },
3922
3923         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3924         // Given a MouseEvent object, returns the pixel coordinate relative to
3925         // the [origin pixel](#map-getpixelorigin) where the event took place.
3926         mouseEventToLayerPoint: function (e) {
3927                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3928         },
3929
3930         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3931         // Given a MouseEvent object, returns geographical coordinate where the
3932         // event took place.
3933         mouseEventToLatLng: function (e) { // (MouseEvent)
3934                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3935         },
3936
3937
3938         // map initialization methods
3939
3940         _initContainer: function (id) {
3941                 var container = this._container = get(id);
3942
3943                 if (!container) {
3944                         throw new Error('Map container not found.');
3945                 } else if (container._leaflet_id) {
3946                         throw new Error('Map container is already initialized.');
3947                 }
3948
3949                 on(container, 'scroll', this._onScroll, this);
3950                 this._containerId = stamp(container);
3951         },
3952
3953         _initLayout: function () {
3954                 var container = this._container;
3955
3956                 this._fadeAnimated = this.options.fadeAnimation && any3d;
3957
3958                 addClass(container, 'leaflet-container' +
3959                         (touch ? ' leaflet-touch' : '') +
3960                         (retina ? ' leaflet-retina' : '') +
3961                         (ielt9 ? ' leaflet-oldie' : '') +
3962                         (safari ? ' leaflet-safari' : '') +
3963                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3964
3965                 var position = getStyle(container, 'position');
3966
3967                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3968                         container.style.position = 'relative';
3969                 }
3970
3971                 this._initPanes();
3972
3973                 if (this._initControlPos) {
3974                         this._initControlPos();
3975                 }
3976         },
3977
3978         _initPanes: function () {
3979                 var panes = this._panes = {};
3980                 this._paneRenderers = {};
3981
3982                 // @section
3983                 //
3984                 // Panes are DOM elements used to control the ordering of layers on the map. You
3985                 // can access panes with [`map.getPane`](#map-getpane) or
3986                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3987                 // [`map.createPane`](#map-createpane) method.
3988                 //
3989                 // Every map has the following default panes that differ only in zIndex.
3990                 //
3991                 // @pane mapPane: HTMLElement = 'auto'
3992                 // Pane that contains all other map panes
3993
3994                 this._mapPane = this.createPane('mapPane', this._container);
3995                 setPosition(this._mapPane, new Point(0, 0));
3996
3997                 // @pane tilePane: HTMLElement = 200
3998                 // Pane for `GridLayer`s and `TileLayer`s
3999                 this.createPane('tilePane');
4000                 // @pane overlayPane: HTMLElement = 400
4001                 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
4002                 this.createPane('shadowPane');
4003                 // @pane shadowPane: HTMLElement = 500
4004                 // Pane for overlay shadows (e.g. `Marker` shadows)
4005                 this.createPane('overlayPane');
4006                 // @pane markerPane: HTMLElement = 600
4007                 // Pane for `Icon`s of `Marker`s
4008                 this.createPane('markerPane');
4009                 // @pane tooltipPane: HTMLElement = 650
4010                 // Pane for tooltip.
4011                 this.createPane('tooltipPane');
4012                 // @pane popupPane: HTMLElement = 700
4013                 // Pane for `Popup`s.
4014                 this.createPane('popupPane');
4015
4016                 if (!this.options.markerZoomAnimation) {
4017                         addClass(panes.markerPane, 'leaflet-zoom-hide');
4018                         addClass(panes.shadowPane, 'leaflet-zoom-hide');
4019                 }
4020         },
4021
4022
4023         // private methods that modify map state
4024
4025         // @section Map state change events
4026         _resetView: function (center, zoom) {
4027                 setPosition(this._mapPane, new Point(0, 0));
4028
4029                 var loading = !this._loaded;
4030                 this._loaded = true;
4031                 zoom = this._limitZoom(zoom);
4032
4033                 this.fire('viewprereset');
4034
4035                 var zoomChanged = this._zoom !== zoom;
4036                 this
4037                         ._moveStart(zoomChanged)
4038                         ._move(center, zoom)
4039                         ._moveEnd(zoomChanged);
4040
4041                 // @event viewreset: Event
4042                 // Fired when the map needs to redraw its content (this usually happens
4043                 // on map zoom or load). Very useful for creating custom overlays.
4044                 this.fire('viewreset');
4045
4046                 // @event load: Event
4047                 // Fired when the map is initialized (when its center and zoom are set
4048                 // for the first time).
4049                 if (loading) {
4050                         this.fire('load');
4051                 }
4052         },
4053
4054         _moveStart: function (zoomChanged) {
4055                 // @event zoomstart: Event
4056                 // Fired when the map zoom is about to change (e.g. before zoom animation).
4057                 // @event movestart: Event
4058                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
4059                 if (zoomChanged) {
4060                         this.fire('zoomstart');
4061                 }
4062                 return this.fire('movestart');
4063         },
4064
4065         _move: function (center, zoom, data) {
4066                 if (zoom === undefined) {
4067                         zoom = this._zoom;
4068                 }
4069                 var zoomChanged = this._zoom !== zoom;
4070
4071                 this._zoom = zoom;
4072                 this._lastCenter = center;
4073                 this._pixelOrigin = this._getNewPixelOrigin(center);
4074
4075                 // @event zoom: Event
4076                 // Fired repeatedly during any change in zoom level, including zoom
4077                 // and fly animations.
4078                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
4079                         this.fire('zoom', data);
4080                 }
4081
4082                 // @event move: Event
4083                 // Fired repeatedly during any movement of the map, including pan and
4084                 // fly animations.
4085                 return this.fire('move', data);
4086         },
4087
4088         _moveEnd: function (zoomChanged) {
4089                 // @event zoomend: Event
4090                 // Fired when the map has changed, after any animations.
4091                 if (zoomChanged) {
4092                         this.fire('zoomend');
4093                 }
4094
4095                 // @event moveend: Event
4096                 // Fired when the center of the map stops changing (e.g. user stopped
4097                 // dragging the map).
4098                 return this.fire('moveend');
4099         },
4100
4101         _stop: function () {
4102                 cancelAnimFrame(this._flyToFrame);
4103                 if (this._panAnim) {
4104                         this._panAnim.stop();
4105                 }
4106                 return this;
4107         },
4108
4109         _rawPanBy: function (offset) {
4110                 setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
4111         },
4112
4113         _getZoomSpan: function () {
4114                 return this.getMaxZoom() - this.getMinZoom();
4115         },
4116
4117         _panInsideMaxBounds: function () {
4118                 if (!this._enforcingBounds) {
4119                         this.panInsideBounds(this.options.maxBounds);
4120                 }
4121         },
4122
4123         _checkIfLoaded: function () {
4124                 if (!this._loaded) {
4125                         throw new Error('Set map center and zoom first.');
4126                 }
4127         },
4128
4129         // DOM event handling
4130
4131         // @section Interaction events
4132         _initEvents: function (remove$$1) {
4133                 this._targets = {};
4134                 this._targets[stamp(this._container)] = this;
4135
4136                 var onOff = remove$$1 ? off : on;
4137
4138                 // @event click: MouseEvent
4139                 // Fired when the user clicks (or taps) the map.
4140                 // @event dblclick: MouseEvent
4141                 // Fired when the user double-clicks (or double-taps) the map.
4142                 // @event mousedown: MouseEvent
4143                 // Fired when the user pushes the mouse button on the map.
4144                 // @event mouseup: MouseEvent
4145                 // Fired when the user releases the mouse button on the map.
4146                 // @event mouseover: MouseEvent
4147                 // Fired when the mouse enters the map.
4148                 // @event mouseout: MouseEvent
4149                 // Fired when the mouse leaves the map.
4150                 // @event mousemove: MouseEvent
4151                 // Fired while the mouse moves over the map.
4152                 // @event contextmenu: MouseEvent
4153                 // Fired when the user pushes the right mouse button on the map, prevents
4154                 // default browser context menu from showing if there are listeners on
4155                 // this event. Also fired on mobile when the user holds a single touch
4156                 // for a second (also called long press).
4157                 // @event keypress: KeyboardEvent
4158                 // Fired when the user presses a key from the keyboard while the map is focused.
4159                 onOff(this._container, 'click dblclick mousedown mouseup ' +
4160                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
4161
4162                 if (this.options.trackResize) {
4163                         onOff(window, 'resize', this._onResize, this);
4164                 }
4165
4166                 if (any3d && this.options.transform3DLimit) {
4167                         (remove$$1 ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
4168                 }
4169         },
4170
4171         _onResize: function () {
4172                 cancelAnimFrame(this._resizeRequest);
4173                 this._resizeRequest = requestAnimFrame(
4174                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
4175         },
4176
4177         _onScroll: function () {
4178                 this._container.scrollTop  = 0;
4179                 this._container.scrollLeft = 0;
4180         },
4181
4182         _onMoveEnd: function () {
4183                 var pos = this._getMapPanePos();
4184                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
4185                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
4186                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
4187                         this._resetView(this.getCenter(), this.getZoom());
4188                 }
4189         },
4190
4191         _findEventTargets: function (e, type) {
4192                 var targets = [],
4193                     target,
4194                     isHover = type === 'mouseout' || type === 'mouseover',
4195                     src = e.target || e.srcElement,
4196                     dragging = false;
4197
4198                 while (src) {
4199                         target = this._targets[stamp(src)];
4200                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
4201                                 // Prevent firing click after you just dragged an object.
4202                                 dragging = true;
4203                                 break;
4204                         }
4205                         if (target && target.listens(type, true)) {
4206                                 if (isHover && !isExternalTarget(src, e)) { break; }
4207                                 targets.push(target);
4208                                 if (isHover) { break; }
4209                         }
4210                         if (src === this._container) { break; }
4211                         src = src.parentNode;
4212                 }
4213                 if (!targets.length && !dragging && !isHover && isExternalTarget(src, e)) {
4214                         targets = [this];
4215                 }
4216                 return targets;
4217         },
4218
4219         _handleDOMEvent: function (e) {
4220                 if (!this._loaded || skipped(e)) { return; }
4221
4222                 var type = e.type;
4223
4224                 if (type === 'mousedown' || type === 'keypress') {
4225                         // prevents outline when clicking on keyboard-focusable element
4226                         preventOutline(e.target || e.srcElement);
4227                 }
4228
4229                 this._fireDOMEvent(e, type);
4230         },
4231
4232         _mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],
4233
4234         _fireDOMEvent: function (e, type, targets) {
4235
4236                 if (e.type === 'click') {
4237                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
4238                         // @event preclick: MouseEvent
4239                         // Fired before mouse click on the map (sometimes useful when you
4240                         // want something to happen on click before any existing click
4241                         // handlers start running).
4242                         var synth = extend({}, e);
4243                         synth.type = 'preclick';
4244                         this._fireDOMEvent(synth, synth.type, targets);
4245                 }
4246
4247                 if (e._stopped) { return; }
4248
4249                 // Find the layer the event is propagating from and its parents.
4250                 targets = (targets || []).concat(this._findEventTargets(e, type));
4251
4252                 if (!targets.length) { return; }
4253
4254                 var target = targets[0];
4255                 if (type === 'contextmenu' && target.listens(type, true)) {
4256                         preventDefault(e);
4257                 }
4258
4259                 var data = {
4260                         originalEvent: e
4261                 };
4262
4263                 if (e.type !== 'keypress') {
4264                         var isMarker = (target.options && 'icon' in target.options);
4265                         data.containerPoint = isMarker ?
4266                                         this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
4267                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
4268                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
4269                 }
4270
4271                 for (var i = 0; i < targets.length; i++) {
4272                         targets[i].fire(type, data, true);
4273                         if (data.originalEvent._stopped ||
4274                                 (targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
4275                 }
4276         },
4277
4278         _draggableMoved: function (obj) {
4279                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
4280                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
4281         },
4282
4283         _clearHandlers: function () {
4284                 for (var i = 0, len = this._handlers.length; i < len; i++) {
4285                         this._handlers[i].disable();
4286                 }
4287         },
4288
4289         // @section Other Methods
4290
4291         // @method whenReady(fn: Function, context?: Object): this
4292         // Runs the given function `fn` when the map gets initialized with
4293         // a view (center and zoom) and at least one layer, or immediately
4294         // if it's already initialized, optionally passing a function context.
4295         whenReady: function (callback, context) {
4296                 if (this._loaded) {
4297                         callback.call(context || this, {target: this});
4298                 } else {
4299                         this.on('load', callback, context);
4300                 }
4301                 return this;
4302         },
4303
4304
4305         // private methods for getting map state
4306
4307         _getMapPanePos: function () {
4308                 return getPosition(this._mapPane) || new Point(0, 0);
4309         },
4310
4311         _moved: function () {
4312                 var pos = this._getMapPanePos();
4313                 return pos && !pos.equals([0, 0]);
4314         },
4315
4316         _getTopLeftPoint: function (center, zoom) {
4317                 var pixelOrigin = center && zoom !== undefined ?
4318                         this._getNewPixelOrigin(center, zoom) :
4319                         this.getPixelOrigin();
4320                 return pixelOrigin.subtract(this._getMapPanePos());
4321         },
4322
4323         _getNewPixelOrigin: function (center, zoom) {
4324                 var viewHalf = this.getSize()._divideBy(2);
4325                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
4326         },
4327
4328         _latLngToNewLayerPoint: function (latlng, zoom, center) {
4329                 var topLeft = this._getNewPixelOrigin(center, zoom);
4330                 return this.project(latlng, zoom)._subtract(topLeft);
4331         },
4332
4333         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
4334                 var topLeft = this._getNewPixelOrigin(center, zoom);
4335                 return toBounds([
4336                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
4337                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
4338                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
4339                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
4340                 ]);
4341         },
4342
4343         // layer point of the current center
4344         _getCenterLayerPoint: function () {
4345                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
4346         },
4347
4348         // offset of the specified place to the current center in pixels
4349         _getCenterOffset: function (latlng) {
4350                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
4351         },
4352
4353         // adjust center for view to get inside bounds
4354         _limitCenter: function (center, zoom, bounds) {
4355
4356                 if (!bounds) { return center; }
4357
4358                 var centerPoint = this.project(center, zoom),
4359                     viewHalf = this.getSize().divideBy(2),
4360                     viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
4361                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
4362
4363                 // If offset is less than a pixel, ignore.
4364                 // This prevents unstable projections from getting into
4365                 // an infinite loop of tiny offsets.
4366                 if (offset.round().equals([0, 0])) {
4367                         return center;
4368                 }
4369
4370                 return this.unproject(centerPoint.add(offset), zoom);
4371         },
4372
4373         // adjust offset for view to get inside bounds
4374         _limitOffset: function (offset, bounds) {
4375                 if (!bounds) { return offset; }
4376
4377                 var viewBounds = this.getPixelBounds(),
4378                     newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
4379
4380                 return offset.add(this._getBoundsOffset(newBounds, bounds));
4381         },
4382
4383         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
4384         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
4385                 var projectedMaxBounds = toBounds(
4386                         this.project(maxBounds.getNorthEast(), zoom),
4387                         this.project(maxBounds.getSouthWest(), zoom)
4388                     ),
4389                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
4390                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
4391
4392                     dx = this._rebound(minOffset.x, -maxOffset.x),
4393                     dy = this._rebound(minOffset.y, -maxOffset.y);
4394
4395                 return new Point(dx, dy);
4396         },
4397
4398         _rebound: function (left, right) {
4399                 return left + right > 0 ?
4400                         Math.round(left - right) / 2 :
4401                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
4402         },
4403
4404         _limitZoom: function (zoom) {
4405                 var min = this.getMinZoom(),
4406                     max = this.getMaxZoom(),
4407                     snap = any3d ? this.options.zoomSnap : 1;
4408                 if (snap) {
4409                         zoom = Math.round(zoom / snap) * snap;
4410                 }
4411                 return Math.max(min, Math.min(max, zoom));
4412         },
4413
4414         _onPanTransitionStep: function () {
4415                 this.fire('move');
4416         },
4417
4418         _onPanTransitionEnd: function () {
4419                 removeClass(this._mapPane, 'leaflet-pan-anim');
4420                 this.fire('moveend');
4421         },
4422
4423         _tryAnimatedPan: function (center, options) {
4424                 // difference between the new and current centers in pixels
4425                 var offset = this._getCenterOffset(center)._floor();
4426
4427                 // don't animate too far unless animate: true specified in options
4428                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
4429
4430                 this.panBy(offset, options);
4431
4432                 return true;
4433         },
4434
4435         _createAnimProxy: function () {
4436
4437                 var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
4438                 this._panes.mapPane.appendChild(proxy);
4439
4440                 this.on('zoomanim', function (e) {
4441                         var prop = TRANSFORM,
4442                             transform = this._proxy.style[prop];
4443
4444                         setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
4445
4446                         // workaround for case when transform is the same and so transitionend event is not fired
4447                         if (transform === this._proxy.style[prop] && this._animatingZoom) {
4448                                 this._onZoomTransitionEnd();
4449                         }
4450                 }, this);
4451
4452                 this.on('load moveend', function () {
4453                         var c = this.getCenter(),
4454                             z = this.getZoom();
4455                         setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
4456                 }, this);
4457
4458                 this._on('unload', this._destroyAnimProxy, this);
4459         },
4460
4461         _destroyAnimProxy: function () {
4462                 remove(this._proxy);
4463                 delete this._proxy;
4464         },
4465
4466         _catchTransitionEnd: function (e) {
4467                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
4468                         this._onZoomTransitionEnd();
4469                 }
4470         },
4471
4472         _nothingToAnimate: function () {
4473                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
4474         },
4475
4476         _tryAnimatedZoom: function (center, zoom, options) {
4477
4478                 if (this._animatingZoom) { return true; }
4479
4480                 options = options || {};
4481
4482                 // don't animate if disabled, not supported or zoom difference is too large
4483                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
4484                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
4485
4486                 // offset is the pixel coords of the zoom origin relative to the current center
4487                 var scale = this.getZoomScale(zoom),
4488                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
4489
4490                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
4491                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
4492
4493                 requestAnimFrame(function () {
4494                         this
4495                             ._moveStart(true)
4496                             ._animateZoom(center, zoom, true);
4497                 }, this);
4498
4499                 return true;
4500         },
4501
4502         _animateZoom: function (center, zoom, startAnim, noUpdate) {
4503                 if (startAnim) {
4504                         this._animatingZoom = true;
4505
4506                         // remember what center/zoom to set after animation
4507                         this._animateToCenter = center;
4508                         this._animateToZoom = zoom;
4509
4510                         addClass(this._mapPane, 'leaflet-zoom-anim');
4511                 }
4512
4513                 // @event zoomanim: ZoomAnimEvent
4514                 // Fired on every frame of a zoom animation
4515                 this.fire('zoomanim', {
4516                         center: center,
4517                         zoom: zoom,
4518                         noUpdate: noUpdate
4519                 });
4520
4521                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
4522                 setTimeout(bind(this._onZoomTransitionEnd, this), 250);
4523         },
4524
4525         _onZoomTransitionEnd: function () {
4526                 if (!this._animatingZoom) { return; }
4527
4528                 removeClass(this._mapPane, 'leaflet-zoom-anim');
4529
4530                 this._animatingZoom = false;
4531
4532                 this._move(this._animateToCenter, this._animateToZoom);
4533
4534                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
4535                 requestAnimFrame(function () {
4536                         this._moveEnd(true);
4537                 }, this);
4538         }
4539 });
4540
4541 // @section
4542
4543 // @factory L.map(id: String, options?: Map options)
4544 // Instantiates a map object given the DOM ID of a `<div>` element
4545 // and optionally an object literal with `Map options`.
4546 //
4547 // @alternative
4548 // @factory L.map(el: HTMLElement, options?: Map options)
4549 // Instantiates a map object given an instance of a `<div>` HTML element
4550 // and optionally an object literal with `Map options`.
4551 function createMap(id, options) {
4552         return new Map(id, options);
4553 }
4554
4555 /*
4556  * @class Control
4557  * @aka L.Control
4558  * @inherits Class
4559  *
4560  * L.Control is a base class for implementing map controls. Handles positioning.
4561  * All other controls extend from this class.
4562  */
4563
4564 var Control = Class.extend({
4565         // @section
4566         // @aka Control options
4567         options: {
4568                 // @option position: String = 'topright'
4569                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
4570                 // `'topright'`, `'bottomleft'` or `'bottomright'`
4571                 position: 'topright'
4572         },
4573
4574         initialize: function (options) {
4575                 setOptions(this, options);
4576         },
4577
4578         /* @section
4579          * Classes extending L.Control will inherit the following methods:
4580          *
4581          * @method getPosition: string
4582          * Returns the position of the control.
4583          */
4584         getPosition: function () {
4585                 return this.options.position;
4586         },
4587
4588         // @method setPosition(position: string): this
4589         // Sets the position of the control.
4590         setPosition: function (position) {
4591                 var map = this._map;
4592
4593                 if (map) {
4594                         map.removeControl(this);
4595                 }
4596
4597                 this.options.position = position;
4598
4599                 if (map) {
4600                         map.addControl(this);
4601                 }
4602
4603                 return this;
4604         },
4605
4606         // @method getContainer: HTMLElement
4607         // Returns the HTMLElement that contains the control.
4608         getContainer: function () {
4609                 return this._container;
4610         },
4611
4612         // @method addTo(map: Map): this
4613         // Adds the control to the given map.
4614         addTo: function (map) {
4615                 this.remove();
4616                 this._map = map;
4617
4618                 var container = this._container = this.onAdd(map),
4619                     pos = this.getPosition(),
4620                     corner = map._controlCorners[pos];
4621
4622                 addClass(container, 'leaflet-control');
4623
4624                 if (pos.indexOf('bottom') !== -1) {
4625                         corner.insertBefore(container, corner.firstChild);
4626                 } else {
4627                         corner.appendChild(container);
4628                 }
4629
4630                 return this;
4631         },
4632
4633         // @method remove: this
4634         // Removes the control from the map it is currently active on.
4635         remove: function () {
4636                 if (!this._map) {
4637                         return this;
4638                 }
4639
4640                 remove(this._container);
4641
4642                 if (this.onRemove) {
4643                         this.onRemove(this._map);
4644                 }
4645
4646                 this._map = null;
4647
4648                 return this;
4649         },
4650
4651         _refocusOnMap: function (e) {
4652                 // if map exists and event is not a keyboard event
4653                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
4654                         this._map.getContainer().focus();
4655                 }
4656         }
4657 });
4658
4659 var control = function (options) {
4660         return new Control(options);
4661 };
4662
4663 /* @section Extension methods
4664  * @uninheritable
4665  *
4666  * Every control should extend from `L.Control` and (re-)implement the following methods.
4667  *
4668  * @method onAdd(map: Map): HTMLElement
4669  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
4670  *
4671  * @method onRemove(map: Map)
4672  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
4673  */
4674
4675 /* @namespace Map
4676  * @section Methods for Layers and Controls
4677  */
4678 Map.include({
4679         // @method addControl(control: Control): this
4680         // Adds the given control to the map
4681         addControl: function (control) {
4682                 control.addTo(this);
4683                 return this;
4684         },
4685
4686         // @method removeControl(control: Control): this
4687         // Removes the given control from the map
4688         removeControl: function (control) {
4689                 control.remove();
4690                 return this;
4691         },
4692
4693         _initControlPos: function () {
4694                 var corners = this._controlCorners = {},
4695                     l = 'leaflet-',
4696                     container = this._controlContainer =
4697                             create$1('div', l + 'control-container', this._container);
4698
4699                 function createCorner(vSide, hSide) {
4700                         var className = l + vSide + ' ' + l + hSide;
4701
4702                         corners[vSide + hSide] = create$1('div', className, container);
4703                 }
4704
4705                 createCorner('top', 'left');
4706                 createCorner('top', 'right');
4707                 createCorner('bottom', 'left');
4708                 createCorner('bottom', 'right');
4709         },
4710
4711         _clearControlPos: function () {
4712                 for (var i in this._controlCorners) {
4713                         remove(this._controlCorners[i]);
4714                 }
4715                 remove(this._controlContainer);
4716                 delete this._controlCorners;
4717                 delete this._controlContainer;
4718         }
4719 });
4720
4721 /*
4722  * @class Control.Layers
4723  * @aka L.Control.Layers
4724  * @inherits Control
4725  *
4726  * 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`.
4727  *
4728  * @example
4729  *
4730  * ```js
4731  * var baseLayers = {
4732  *      "Mapbox": mapbox,
4733  *      "OpenStreetMap": osm
4734  * };
4735  *
4736  * var overlays = {
4737  *      "Marker": marker,
4738  *      "Roads": roadsLayer
4739  * };
4740  *
4741  * L.control.layers(baseLayers, overlays).addTo(map);
4742  * ```
4743  *
4744  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
4745  *
4746  * ```js
4747  * {
4748  *     "<someName1>": layer1,
4749  *     "<someName2>": layer2
4750  * }
4751  * ```
4752  *
4753  * The layer names can contain HTML, which allows you to add additional styling to the items:
4754  *
4755  * ```js
4756  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
4757  * ```
4758  */
4759
4760 var Layers = Control.extend({
4761         // @section
4762         // @aka Control.Layers options
4763         options: {
4764                 // @option collapsed: Boolean = true
4765                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
4766                 collapsed: true,
4767                 position: 'topright',
4768
4769                 // @option autoZIndex: Boolean = true
4770                 // 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.
4771                 autoZIndex: true,
4772
4773                 // @option hideSingleBase: Boolean = false
4774                 // If `true`, the base layers in the control will be hidden when there is only one.
4775                 hideSingleBase: false,
4776
4777                 // @option sortLayers: Boolean = false
4778                 // Whether to sort the layers. When `false`, layers will keep the order
4779                 // in which they were added to the control.
4780                 sortLayers: false,
4781
4782                 // @option sortFunction: Function = *
4783                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
4784                 // that will be used for sorting the layers, when `sortLayers` is `true`.
4785                 // The function receives both the `L.Layer` instances and their names, as in
4786                 // `sortFunction(layerA, layerB, nameA, nameB)`.
4787                 // By default, it sorts layers alphabetically by their name.
4788                 sortFunction: function (layerA, layerB, nameA, nameB) {
4789                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
4790                 }
4791         },
4792
4793         initialize: function (baseLayers, overlays, options) {
4794                 setOptions(this, options);
4795
4796                 this._layerControlInputs = [];
4797                 this._layers = [];
4798                 this._lastZIndex = 0;
4799                 this._handlingClick = false;
4800
4801                 for (var i in baseLayers) {
4802                         this._addLayer(baseLayers[i], i);
4803                 }
4804
4805                 for (i in overlays) {
4806                         this._addLayer(overlays[i], i, true);
4807                 }
4808         },
4809
4810         onAdd: function (map) {
4811                 this._initLayout();
4812                 this._update();
4813
4814                 this._map = map;
4815                 map.on('zoomend', this._checkDisabledLayers, this);
4816
4817                 for (var i = 0; i < this._layers.length; i++) {
4818                         this._layers[i].layer.on('add remove', this._onLayerChange, this);
4819                 }
4820
4821                 return this._container;
4822         },
4823
4824         addTo: function (map) {
4825                 Control.prototype.addTo.call(this, map);
4826                 // Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
4827                 return this._expandIfNotCollapsed();
4828         },
4829
4830         onRemove: function () {
4831                 this._map.off('zoomend', this._checkDisabledLayers, this);
4832
4833                 for (var i = 0; i < this._layers.length; i++) {
4834                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
4835                 }
4836         },
4837
4838         // @method addBaseLayer(layer: Layer, name: String): this
4839         // Adds a base layer (radio button entry) with the given name to the control.
4840         addBaseLayer: function (layer, name) {
4841                 this._addLayer(layer, name);
4842                 return (this._map) ? this._update() : this;
4843         },
4844
4845         // @method addOverlay(layer: Layer, name: String): this
4846         // Adds an overlay (checkbox entry) with the given name to the control.
4847         addOverlay: function (layer, name) {
4848                 this._addLayer(layer, name, true);
4849                 return (this._map) ? this._update() : this;
4850         },
4851
4852         // @method removeLayer(layer: Layer): this
4853         // Remove the given layer from the control.
4854         removeLayer: function (layer) {
4855                 layer.off('add remove', this._onLayerChange, this);
4856
4857                 var obj = this._getLayer(stamp(layer));
4858                 if (obj) {
4859                         this._layers.splice(this._layers.indexOf(obj), 1);
4860                 }
4861                 return (this._map) ? this._update() : this;
4862         },
4863
4864         // @method expand(): this
4865         // Expand the control container if collapsed.
4866         expand: function () {
4867                 addClass(this._container, 'leaflet-control-layers-expanded');
4868                 this._form.style.height = null;
4869                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
4870                 if (acceptableHeight < this._form.clientHeight) {
4871                         addClass(this._form, 'leaflet-control-layers-scrollbar');
4872                         this._form.style.height = acceptableHeight + 'px';
4873                 } else {
4874                         removeClass(this._form, 'leaflet-control-layers-scrollbar');
4875                 }
4876                 this._checkDisabledLayers();
4877                 return this;
4878         },
4879
4880         // @method collapse(): this
4881         // Collapse the control container if expanded.
4882         collapse: function () {
4883                 removeClass(this._container, 'leaflet-control-layers-expanded');
4884                 return this;
4885         },
4886
4887         _initLayout: function () {
4888                 var className = 'leaflet-control-layers',
4889                     container = this._container = create$1('div', className),
4890                     collapsed = this.options.collapsed;
4891
4892                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
4893                 container.setAttribute('aria-haspopup', true);
4894
4895                 disableClickPropagation(container);
4896                 disableScrollPropagation(container);
4897
4898                 var form = this._form = create$1('form', className + '-list');
4899
4900                 if (collapsed) {
4901                         this._map.on('click', this.collapse, this);
4902
4903                         if (!android) {
4904                                 on(container, {
4905                                         mouseenter: this.expand,
4906                                         mouseleave: this.collapse
4907                                 }, this);
4908                         }
4909                 }
4910
4911                 var link = this._layersLink = create$1('a', className + '-toggle', container);
4912                 link.href = '#';
4913                 link.title = 'Layers';
4914
4915                 if (touch) {
4916                         on(link, 'click', stop);
4917                         on(link, 'click', this.expand, this);
4918                 } else {
4919                         on(link, 'focus', this.expand, this);
4920                 }
4921
4922                 if (!collapsed) {
4923                         this.expand();
4924                 }
4925
4926                 this._baseLayersList = create$1('div', className + '-base', form);
4927                 this._separator = create$1('div', className + '-separator', form);
4928                 this._overlaysList = create$1('div', className + '-overlays', form);
4929
4930                 container.appendChild(form);
4931         },
4932
4933         _getLayer: function (id) {
4934                 for (var i = 0; i < this._layers.length; i++) {
4935
4936                         if (this._layers[i] && stamp(this._layers[i].layer) === id) {
4937                                 return this._layers[i];
4938                         }
4939                 }
4940         },
4941
4942         _addLayer: function (layer, name, overlay) {
4943                 if (this._map) {
4944                         layer.on('add remove', this._onLayerChange, this);
4945                 }
4946
4947                 this._layers.push({
4948                         layer: layer,
4949                         name: name,
4950                         overlay: overlay
4951                 });
4952
4953                 if (this.options.sortLayers) {
4954                         this._layers.sort(bind(function (a, b) {
4955                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
4956                         }, this));
4957                 }
4958
4959                 if (this.options.autoZIndex && layer.setZIndex) {
4960                         this._lastZIndex++;
4961                         layer.setZIndex(this._lastZIndex);
4962                 }
4963
4964                 this._expandIfNotCollapsed();
4965         },
4966
4967         _update: function () {
4968                 if (!this._container) { return this; }
4969
4970                 empty(this._baseLayersList);
4971                 empty(this._overlaysList);
4972
4973                 this._layerControlInputs = [];
4974                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
4975
4976                 for (i = 0; i < this._layers.length; i++) {
4977                         obj = this._layers[i];
4978                         this._addItem(obj);
4979                         overlaysPresent = overlaysPresent || obj.overlay;
4980                         baseLayersPresent = baseLayersPresent || !obj.overlay;
4981                         baseLayersCount += !obj.overlay ? 1 : 0;
4982                 }
4983
4984                 // Hide base layers section if there's only one layer.
4985                 if (this.options.hideSingleBase) {
4986                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
4987                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
4988                 }
4989
4990                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
4991
4992                 return this;
4993         },
4994
4995         _onLayerChange: function (e) {
4996                 if (!this._handlingClick) {
4997                         this._update();
4998                 }
4999
5000                 var obj = this._getLayer(stamp(e.target));
5001
5002                 // @namespace Map
5003                 // @section Layer events
5004                 // @event baselayerchange: LayersControlEvent
5005                 // Fired when the base layer is changed through the [layer control](#control-layers).
5006                 // @event overlayadd: LayersControlEvent
5007                 // Fired when an overlay is selected through the [layer control](#control-layers).
5008                 // @event overlayremove: LayersControlEvent
5009                 // Fired when an overlay is deselected through the [layer control](#control-layers).
5010                 // @namespace Control.Layers
5011                 var type = obj.overlay ?
5012                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
5013                         (e.type === 'add' ? 'baselayerchange' : null);
5014
5015                 if (type) {
5016                         this._map.fire(type, obj);
5017                 }
5018         },
5019
5020         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
5021         _createRadioElement: function (name, checked) {
5022
5023                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
5024                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
5025
5026                 var radioFragment = document.createElement('div');
5027                 radioFragment.innerHTML = radioHtml;
5028
5029                 return radioFragment.firstChild;
5030         },
5031
5032         _addItem: function (obj) {
5033                 var label = document.createElement('label'),
5034                     checked = this._map.hasLayer(obj.layer),
5035                     input;
5036
5037                 if (obj.overlay) {
5038                         input = document.createElement('input');
5039                         input.type = 'checkbox';
5040                         input.className = 'leaflet-control-layers-selector';
5041                         input.defaultChecked = checked;
5042                 } else {
5043                         input = this._createRadioElement('leaflet-base-layers', checked);
5044                 }
5045
5046                 this._layerControlInputs.push(input);
5047                 input.layerId = stamp(obj.layer);
5048
5049                 on(input, 'click', this._onInputClick, this);
5050
5051                 var name = document.createElement('span');
5052                 name.innerHTML = ' ' + obj.name;
5053
5054                 // Helps from preventing layer control flicker when checkboxes are disabled
5055                 // https://github.com/Leaflet/Leaflet/issues/2771
5056                 var holder = document.createElement('div');
5057
5058                 label.appendChild(holder);
5059                 holder.appendChild(input);
5060                 holder.appendChild(name);
5061
5062                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
5063                 container.appendChild(label);
5064
5065                 this._checkDisabledLayers();
5066                 return label;
5067         },
5068
5069         _onInputClick: function () {
5070                 var inputs = this._layerControlInputs,
5071                     input, layer;
5072                 var addedLayers = [],
5073                     removedLayers = [];
5074
5075                 this._handlingClick = true;
5076
5077                 for (var i = inputs.length - 1; i >= 0; i--) {
5078                         input = inputs[i];
5079                         layer = this._getLayer(input.layerId).layer;
5080
5081                         if (input.checked) {
5082                                 addedLayers.push(layer);
5083                         } else if (!input.checked) {
5084                                 removedLayers.push(layer);
5085                         }
5086                 }
5087
5088                 // Bugfix issue 2318: Should remove all old layers before readding new ones
5089                 for (i = 0; i < removedLayers.length; i++) {
5090                         if (this._map.hasLayer(removedLayers[i])) {
5091                                 this._map.removeLayer(removedLayers[i]);
5092                         }
5093                 }
5094                 for (i = 0; i < addedLayers.length; i++) {
5095                         if (!this._map.hasLayer(addedLayers[i])) {
5096                                 this._map.addLayer(addedLayers[i]);
5097                         }
5098                 }
5099
5100                 this._handlingClick = false;
5101
5102                 this._refocusOnMap();
5103         },
5104
5105         _checkDisabledLayers: function () {
5106                 var inputs = this._layerControlInputs,
5107                     input,
5108                     layer,
5109                     zoom = this._map.getZoom();
5110
5111                 for (var i = inputs.length - 1; i >= 0; i--) {
5112                         input = inputs[i];
5113                         layer = this._getLayer(input.layerId).layer;
5114                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
5115                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
5116
5117                 }
5118         },
5119
5120         _expandIfNotCollapsed: function () {
5121                 if (this._map && !this.options.collapsed) {
5122                         this.expand();
5123                 }
5124                 return this;
5125         },
5126
5127         _expand: function () {
5128                 // Backward compatibility, remove me in 1.1.
5129                 return this.expand();
5130         },
5131
5132         _collapse: function () {
5133                 // Backward compatibility, remove me in 1.1.
5134                 return this.collapse();
5135         }
5136
5137 });
5138
5139
5140 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
5141 // 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.
5142 var layers = function (baseLayers, overlays, options) {
5143         return new Layers(baseLayers, overlays, options);
5144 };
5145
5146 /*
5147  * @class Control.Zoom
5148  * @aka L.Control.Zoom
5149  * @inherits Control
5150  *
5151  * 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`.
5152  */
5153
5154 var Zoom = Control.extend({
5155         // @section
5156         // @aka Control.Zoom options
5157         options: {
5158                 position: 'topleft',
5159
5160                 // @option zoomInText: String = '+'
5161                 // The text set on the 'zoom in' button.
5162                 zoomInText: '+',
5163
5164                 // @option zoomInTitle: String = 'Zoom in'
5165                 // The title set on the 'zoom in' button.
5166                 zoomInTitle: 'Zoom in',
5167
5168                 // @option zoomOutText: String = '&#x2212;'
5169                 // The text set on the 'zoom out' button.
5170                 zoomOutText: '&#x2212;',
5171
5172                 // @option zoomOutTitle: String = 'Zoom out'
5173                 // The title set on the 'zoom out' button.
5174                 zoomOutTitle: 'Zoom out'
5175         },
5176
5177         onAdd: function (map) {
5178                 var zoomName = 'leaflet-control-zoom',
5179                     container = create$1('div', zoomName + ' leaflet-bar'),
5180                     options = this.options;
5181
5182                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
5183                         zoomName + '-in',  container, this._zoomIn);
5184                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
5185                         zoomName + '-out', container, this._zoomOut);
5186
5187                 this._updateDisabled();
5188                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
5189
5190                 return container;
5191         },
5192
5193         onRemove: function (map) {
5194                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
5195         },
5196
5197         disable: function () {
5198                 this._disabled = true;
5199                 this._updateDisabled();
5200                 return this;
5201         },
5202
5203         enable: function () {
5204                 this._disabled = false;
5205                 this._updateDisabled();
5206                 return this;
5207         },
5208
5209         _zoomIn: function (e) {
5210                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
5211                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5212                 }
5213         },
5214
5215         _zoomOut: function (e) {
5216                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
5217                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
5218                 }
5219         },
5220
5221         _createButton: function (html, title, className, container, fn) {
5222                 var link = create$1('a', className, container);
5223                 link.innerHTML = html;
5224                 link.href = '#';
5225                 link.title = title;
5226
5227                 /*
5228                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
5229                  */
5230                 link.setAttribute('role', 'button');
5231                 link.setAttribute('aria-label', title);
5232
5233                 disableClickPropagation(link);
5234                 on(link, 'click', stop);
5235                 on(link, 'click', fn, this);
5236                 on(link, 'click', this._refocusOnMap, this);
5237
5238                 return link;
5239         },
5240
5241         _updateDisabled: function () {
5242                 var map = this._map,
5243                     className = 'leaflet-disabled';
5244
5245                 removeClass(this._zoomInButton, className);
5246                 removeClass(this._zoomOutButton, className);
5247
5248                 if (this._disabled || map._zoom === map.getMinZoom()) {
5249                         addClass(this._zoomOutButton, className);
5250                 }
5251                 if (this._disabled || map._zoom === map.getMaxZoom()) {
5252                         addClass(this._zoomInButton, className);
5253                 }
5254         }
5255 });
5256
5257 // @namespace Map
5258 // @section Control options
5259 // @option zoomControl: Boolean = true
5260 // Whether a [zoom control](#control-zoom) is added to the map by default.
5261 Map.mergeOptions({
5262         zoomControl: true
5263 });
5264
5265 Map.addInitHook(function () {
5266         if (this.options.zoomControl) {
5267                 this.zoomControl = new Zoom();
5268                 this.addControl(this.zoomControl);
5269         }
5270 });
5271
5272 // @namespace Control.Zoom
5273 // @factory L.control.zoom(options: Control.Zoom options)
5274 // Creates a zoom control
5275 var zoom = function (options) {
5276         return new Zoom(options);
5277 };
5278
5279 /*
5280  * @class Control.Scale
5281  * @aka L.Control.Scale
5282  * @inherits Control
5283  *
5284  * 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`.
5285  *
5286  * @example
5287  *
5288  * ```js
5289  * L.control.scale().addTo(map);
5290  * ```
5291  */
5292
5293 var Scale = Control.extend({
5294         // @section
5295         // @aka Control.Scale options
5296         options: {
5297                 position: 'bottomleft',
5298
5299                 // @option maxWidth: Number = 100
5300                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
5301                 maxWidth: 100,
5302
5303                 // @option metric: Boolean = True
5304                 // Whether to show the metric scale line (m/km).
5305                 metric: true,
5306
5307                 // @option imperial: Boolean = True
5308                 // Whether to show the imperial scale line (mi/ft).
5309                 imperial: true
5310
5311                 // @option updateWhenIdle: Boolean = false
5312                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
5313         },
5314
5315         onAdd: function (map) {
5316                 var className = 'leaflet-control-scale',
5317                     container = create$1('div', className),
5318                     options = this.options;
5319
5320                 this._addScales(options, className + '-line', container);
5321
5322                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5323                 map.whenReady(this._update, this);
5324
5325                 return container;
5326         },
5327
5328         onRemove: function (map) {
5329                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
5330         },
5331
5332         _addScales: function (options, className, container) {
5333                 if (options.metric) {
5334                         this._mScale = create$1('div', className, container);
5335                 }
5336                 if (options.imperial) {
5337                         this._iScale = create$1('div', className, container);
5338                 }
5339         },
5340
5341         _update: function () {
5342                 var map = this._map,
5343                     y = map.getSize().y / 2;
5344
5345                 var maxMeters = map.distance(
5346                                 map.containerPointToLatLng([0, y]),
5347                                 map.containerPointToLatLng([this.options.maxWidth, y]));
5348
5349                 this._updateScales(maxMeters);
5350         },
5351
5352         _updateScales: function (maxMeters) {
5353                 if (this.options.metric && maxMeters) {
5354                         this._updateMetric(maxMeters);
5355                 }
5356                 if (this.options.imperial && maxMeters) {
5357                         this._updateImperial(maxMeters);
5358                 }
5359         },
5360
5361         _updateMetric: function (maxMeters) {
5362                 var meters = this._getRoundNum(maxMeters),
5363                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
5364
5365                 this._updateScale(this._mScale, label, meters / maxMeters);
5366         },
5367
5368         _updateImperial: function (maxMeters) {
5369                 var maxFeet = maxMeters * 3.2808399,
5370                     maxMiles, miles, feet;
5371
5372                 if (maxFeet > 5280) {
5373                         maxMiles = maxFeet / 5280;
5374                         miles = this._getRoundNum(maxMiles);
5375                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
5376
5377                 } else {
5378                         feet = this._getRoundNum(maxFeet);
5379                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
5380                 }
5381         },
5382
5383         _updateScale: function (scale, text, ratio) {
5384                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
5385                 scale.innerHTML = text;
5386         },
5387
5388         _getRoundNum: function (num) {
5389                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
5390                     d = num / pow10;
5391
5392                 d = d >= 10 ? 10 :
5393                     d >= 5 ? 5 :
5394                     d >= 3 ? 3 :
5395                     d >= 2 ? 2 : 1;
5396
5397                 return pow10 * d;
5398         }
5399 });
5400
5401
5402 // @factory L.control.scale(options?: Control.Scale options)
5403 // Creates an scale control with the given options.
5404 var scale = function (options) {
5405         return new Scale(options);
5406 };
5407
5408 /*
5409  * @class Control.Attribution
5410  * @aka L.Control.Attribution
5411  * @inherits Control
5412  *
5413  * 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.
5414  */
5415
5416 var Attribution = Control.extend({
5417         // @section
5418         // @aka Control.Attribution options
5419         options: {
5420                 position: 'bottomright',
5421
5422                 // @option prefix: String = 'Leaflet'
5423                 // The HTML text shown before the attributions. Pass `false` to disable.
5424                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
5425         },
5426
5427         initialize: function (options) {
5428                 setOptions(this, options);
5429
5430                 this._attributions = {};
5431         },
5432
5433         onAdd: function (map) {
5434                 map.attributionControl = this;
5435                 this._container = create$1('div', 'leaflet-control-attribution');
5436                 disableClickPropagation(this._container);
5437
5438                 // TODO ugly, refactor
5439                 for (var i in map._layers) {
5440                         if (map._layers[i].getAttribution) {
5441                                 this.addAttribution(map._layers[i].getAttribution());
5442                         }
5443                 }
5444
5445                 this._update();
5446
5447                 return this._container;
5448         },
5449
5450         // @method setPrefix(prefix: String): this
5451         // Sets the text before the attributions.
5452         setPrefix: function (prefix) {
5453                 this.options.prefix = prefix;
5454                 this._update();
5455                 return this;
5456         },
5457
5458         // @method addAttribution(text: String): this
5459         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
5460         addAttribution: function (text) {
5461                 if (!text) { return this; }
5462
5463                 if (!this._attributions[text]) {
5464                         this._attributions[text] = 0;
5465                 }
5466                 this._attributions[text]++;
5467
5468                 this._update();
5469
5470                 return this;
5471         },
5472
5473         // @method removeAttribution(text: String): this
5474         // Removes an attribution text.
5475         removeAttribution: function (text) {
5476                 if (!text) { return this; }
5477
5478                 if (this._attributions[text]) {
5479                         this._attributions[text]--;
5480                         this._update();
5481                 }
5482
5483                 return this;
5484         },
5485
5486         _update: function () {
5487                 if (!this._map) { return; }
5488
5489                 var attribs = [];
5490
5491                 for (var i in this._attributions) {
5492                         if (this._attributions[i]) {
5493                                 attribs.push(i);
5494                         }
5495                 }
5496
5497                 var prefixAndAttribs = [];
5498
5499                 if (this.options.prefix) {
5500                         prefixAndAttribs.push(this.options.prefix);
5501                 }
5502                 if (attribs.length) {
5503                         prefixAndAttribs.push(attribs.join(', '));
5504                 }
5505
5506                 this._container.innerHTML = prefixAndAttribs.join(' | ');
5507         }
5508 });
5509
5510 // @namespace Map
5511 // @section Control options
5512 // @option attributionControl: Boolean = true
5513 // Whether a [attribution control](#control-attribution) is added to the map by default.
5514 Map.mergeOptions({
5515         attributionControl: true
5516 });
5517
5518 Map.addInitHook(function () {
5519         if (this.options.attributionControl) {
5520                 new Attribution().addTo(this);
5521         }
5522 });
5523
5524 // @namespace Control.Attribution
5525 // @factory L.control.attribution(options: Control.Attribution options)
5526 // Creates an attribution control.
5527 var attribution = function (options) {
5528         return new Attribution(options);
5529 };
5530
5531 Control.Layers = Layers;
5532 Control.Zoom = Zoom;
5533 Control.Scale = Scale;
5534 Control.Attribution = Attribution;
5535
5536 control.layers = layers;
5537 control.zoom = zoom;
5538 control.scale = scale;
5539 control.attribution = attribution;
5540
5541 /*
5542         L.Handler is a base class for handler classes that are used internally to inject
5543         interaction features like dragging to classes like Map and Marker.
5544 */
5545
5546 // @class Handler
5547 // @aka L.Handler
5548 // Abstract class for map interaction handlers
5549
5550 var Handler = Class.extend({
5551         initialize: function (map) {
5552                 this._map = map;
5553         },
5554
5555         // @method enable(): this
5556         // Enables the handler
5557         enable: function () {
5558                 if (this._enabled) { return this; }
5559
5560                 this._enabled = true;
5561                 this.addHooks();
5562                 return this;
5563         },
5564
5565         // @method disable(): this
5566         // Disables the handler
5567         disable: function () {
5568                 if (!this._enabled) { return this; }
5569
5570                 this._enabled = false;
5571                 this.removeHooks();
5572                 return this;
5573         },
5574
5575         // @method enabled(): Boolean
5576         // Returns `true` if the handler is enabled
5577         enabled: function () {
5578                 return !!this._enabled;
5579         }
5580
5581         // @section Extension methods
5582         // Classes inheriting from `Handler` must implement the two following methods:
5583         // @method addHooks()
5584         // Called when the handler is enabled, should add event hooks.
5585         // @method removeHooks()
5586         // Called when the handler is disabled, should remove the event hooks added previously.
5587 });
5588
5589 var Mixin = {Events: Events};
5590
5591 /*
5592  * @class Draggable
5593  * @aka L.Draggable
5594  * @inherits Evented
5595  *
5596  * A class for making DOM elements draggable (including touch support).
5597  * Used internally for map and marker dragging. Only works for elements
5598  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
5599  *
5600  * @example
5601  * ```js
5602  * var draggable = new L.Draggable(elementToDrag);
5603  * draggable.enable();
5604  * ```
5605  */
5606
5607 var START = touch ? 'touchstart mousedown' : 'mousedown';
5608 var END = {
5609         mousedown: 'mouseup',
5610         touchstart: 'touchend',
5611         pointerdown: 'touchend',
5612         MSPointerDown: 'touchend'
5613 };
5614 var MOVE = {
5615         mousedown: 'mousemove',
5616         touchstart: 'touchmove',
5617         pointerdown: 'touchmove',
5618         MSPointerDown: 'touchmove'
5619 };
5620
5621
5622 var Draggable = Evented.extend({
5623
5624         options: {
5625                 // @section
5626                 // @aka Draggable options
5627                 // @option clickTolerance: Number = 3
5628                 // The max number of pixels a user can shift the mouse pointer during a click
5629                 // for it to be considered a valid click (as opposed to a mouse drag).
5630                 clickTolerance: 3
5631         },
5632
5633         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
5634         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
5635         initialize: function (element, dragStartTarget, preventOutline$$1, options) {
5636                 setOptions(this, options);
5637
5638                 this._element = element;
5639                 this._dragStartTarget = dragStartTarget || element;
5640                 this._preventOutline = preventOutline$$1;
5641         },
5642
5643         // @method enable()
5644         // Enables the dragging ability
5645         enable: function () {
5646                 if (this._enabled) { return; }
5647
5648                 on(this._dragStartTarget, START, this._onDown, this);
5649
5650                 this._enabled = true;
5651         },
5652
5653         // @method disable()
5654         // Disables the dragging ability
5655         disable: function () {
5656                 if (!this._enabled) { return; }
5657
5658                 // If we're currently dragging this draggable,
5659                 // disabling it counts as first ending the drag.
5660                 if (Draggable._dragging === this) {
5661                         this.finishDrag();
5662                 }
5663
5664                 off(this._dragStartTarget, START, this._onDown, this);
5665
5666                 this._enabled = false;
5667                 this._moved = false;
5668         },
5669
5670         _onDown: function (e) {
5671                 // Ignore simulated events, since we handle both touch and
5672                 // mouse explicitly; otherwise we risk getting duplicates of
5673                 // touch events, see #4315.
5674                 // Also ignore the event if disabled; this happens in IE11
5675                 // under some circumstances, see #3666.
5676                 if (e._simulated || !this._enabled) { return; }
5677
5678                 this._moved = false;
5679
5680                 if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }
5681
5682                 if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
5683                 Draggable._dragging = this;  // Prevent dragging multiple objects at once.
5684
5685                 if (this._preventOutline) {
5686                         preventOutline(this._element);
5687                 }
5688
5689                 disableImageDrag();
5690                 disableTextSelection();
5691
5692                 if (this._moving) { return; }
5693
5694                 // @event down: Event
5695                 // Fired when a drag is about to start.
5696                 this.fire('down');
5697
5698                 var first = e.touches ? e.touches[0] : e;
5699
5700                 this._startPoint = new Point(first.clientX, first.clientY);
5701
5702                 on(document, MOVE[e.type], this._onMove, this);
5703                 on(document, END[e.type], this._onUp, this);
5704         },
5705
5706         _onMove: function (e) {
5707                 // Ignore simulated events, since we handle both touch and
5708                 // mouse explicitly; otherwise we risk getting duplicates of
5709                 // touch events, see #4315.
5710                 // Also ignore the event if disabled; this happens in IE11
5711                 // under some circumstances, see #3666.
5712                 if (e._simulated || !this._enabled) { return; }
5713
5714                 if (e.touches && e.touches.length > 1) {
5715                         this._moved = true;
5716                         return;
5717                 }
5718
5719                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
5720                     newPoint = new Point(first.clientX, first.clientY),
5721                     offset = newPoint.subtract(this._startPoint);
5722
5723                 if (!offset.x && !offset.y) { return; }
5724                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
5725
5726                 preventDefault(e);
5727
5728                 if (!this._moved) {
5729                         // @event dragstart: Event
5730                         // Fired when a drag starts
5731                         this.fire('dragstart');
5732
5733                         this._moved = true;
5734                         this._startPos = getPosition(this._element).subtract(offset);
5735
5736                         addClass(document.body, 'leaflet-dragging');
5737
5738                         this._lastTarget = e.target || e.srcElement;
5739                         // IE and Edge do not give the <use> element, so fetch it
5740                         // if necessary
5741                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
5742                                 this._lastTarget = this._lastTarget.correspondingUseElement;
5743                         }
5744                         addClass(this._lastTarget, 'leaflet-drag-target');
5745                 }
5746
5747                 this._newPos = this._startPos.add(offset);
5748                 this._moving = true;
5749
5750                 cancelAnimFrame(this._animRequest);
5751                 this._lastEvent = e;
5752                 this._animRequest = requestAnimFrame(this._updatePosition, this, true);
5753         },
5754
5755         _updatePosition: function () {
5756                 var e = {originalEvent: this._lastEvent};
5757
5758                 // @event predrag: Event
5759                 // Fired continuously during dragging *before* each corresponding
5760                 // update of the element's position.
5761                 this.fire('predrag', e);
5762                 setPosition(this._element, this._newPos);
5763
5764                 // @event drag: Event
5765                 // Fired continuously during dragging.
5766                 this.fire('drag', e);
5767         },
5768
5769         _onUp: function (e) {
5770                 // Ignore simulated events, since we handle both touch and
5771                 // mouse explicitly; otherwise we risk getting duplicates of
5772                 // touch events, see #4315.
5773                 // Also ignore the event if disabled; this happens in IE11
5774                 // under some circumstances, see #3666.
5775                 if (e._simulated || !this._enabled) { return; }
5776                 this.finishDrag();
5777         },
5778
5779         finishDrag: function () {
5780                 removeClass(document.body, 'leaflet-dragging');
5781
5782                 if (this._lastTarget) {
5783                         removeClass(this._lastTarget, 'leaflet-drag-target');
5784                         this._lastTarget = null;
5785                 }
5786
5787                 for (var i in MOVE) {
5788                         off(document, MOVE[i], this._onMove, this);
5789                         off(document, END[i], this._onUp, this);
5790                 }
5791
5792                 enableImageDrag();
5793                 enableTextSelection();
5794
5795                 if (this._moved && this._moving) {
5796                         // ensure drag is not fired after dragend
5797                         cancelAnimFrame(this._animRequest);
5798
5799                         // @event dragend: DragEndEvent
5800                         // Fired when the drag ends.
5801                         this.fire('dragend', {
5802                                 distance: this._newPos.distanceTo(this._startPos)
5803                         });
5804                 }
5805
5806                 this._moving = false;
5807                 Draggable._dragging = false;
5808         }
5809
5810 });
5811
5812 /*
5813  * @namespace LineUtil
5814  *
5815  * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
5816  */
5817
5818 // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
5819 // Improves rendering performance dramatically by lessening the number of points to draw.
5820
5821 // @function simplify(points: Point[], tolerance: Number): Point[]
5822 // Dramatically reduces the number of points in a polyline while retaining
5823 // its shape and returns a new array of simplified points, using the
5824 // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
5825 // Used for a huge performance boost when processing/displaying Leaflet polylines for
5826 // each zoom level and also reducing visual noise. tolerance affects the amount of
5827 // simplification (lesser value means higher quality but slower and with more points).
5828 // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
5829 function simplify(points, tolerance) {
5830         if (!tolerance || !points.length) {
5831                 return points.slice();
5832         }
5833
5834         var sqTolerance = tolerance * tolerance;
5835
5836             // stage 1: vertex reduction
5837             points = _reducePoints(points, sqTolerance);
5838
5839             // stage 2: Douglas-Peucker simplification
5840             points = _simplifyDP(points, sqTolerance);
5841
5842         return points;
5843 }
5844
5845 // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
5846 // Returns the distance between point `p` and segment `p1` to `p2`.
5847 function pointToSegmentDistance(p, p1, p2) {
5848         return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
5849 }
5850
5851 // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
5852 // Returns the closest point from a point `p` on a segment `p1` to `p2`.
5853 function closestPointOnSegment(p, p1, p2) {
5854         return _sqClosestPointOnSegment(p, p1, p2);
5855 }
5856
5857 // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
5858 function _simplifyDP(points, sqTolerance) {
5859
5860         var len = points.length,
5861             ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
5862             markers = new ArrayConstructor(len);
5863
5864             markers[0] = markers[len - 1] = 1;
5865
5866         _simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
5867
5868         var i,
5869             newPoints = [];
5870
5871         for (i = 0; i < len; i++) {
5872                 if (markers[i]) {
5873                         newPoints.push(points[i]);
5874                 }
5875         }
5876
5877         return newPoints;
5878 }
5879
5880 function _simplifyDPStep(points, markers, sqTolerance, first, last) {
5881
5882         var maxSqDist = 0,
5883         index, i, sqDist;
5884
5885         for (i = first + 1; i <= last - 1; i++) {
5886                 sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
5887
5888                 if (sqDist > maxSqDist) {
5889                         index = i;
5890                         maxSqDist = sqDist;
5891                 }
5892         }
5893
5894         if (maxSqDist > sqTolerance) {
5895                 markers[index] = 1;
5896
5897                 _simplifyDPStep(points, markers, sqTolerance, first, index);
5898                 _simplifyDPStep(points, markers, sqTolerance, index, last);
5899         }
5900 }
5901
5902 // reduce points that are too close to each other to a single point
5903 function _reducePoints(points, sqTolerance) {
5904         var reducedPoints = [points[0]];
5905
5906         for (var i = 1, prev = 0, len = points.length; i < len; i++) {
5907                 if (_sqDist(points[i], points[prev]) > sqTolerance) {
5908                         reducedPoints.push(points[i]);
5909                         prev = i;
5910                 }
5911         }
5912         if (prev < len - 1) {
5913                 reducedPoints.push(points[len - 1]);
5914         }
5915         return reducedPoints;
5916 }
5917
5918 var _lastCode;
5919
5920 // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
5921 // Clips the segment a to b by rectangular bounds with the
5922 // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
5923 // (modifying the segment points directly!). Used by Leaflet to only show polyline
5924 // points that are on the screen or near, increasing performance.
5925 function clipSegment(a, b, bounds, useLastCode, round) {
5926         var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
5927             codeB = _getBitCode(b, bounds),
5928
5929             codeOut, p, newCode;
5930
5931             // save 2nd code to avoid calculating it on the next segment
5932             _lastCode = codeB;
5933
5934         while (true) {
5935                 // if a,b is inside the clip window (trivial accept)
5936                 if (!(codeA | codeB)) {
5937                         return [a, b];
5938                 }
5939
5940                 // if a,b is outside the clip window (trivial reject)
5941                 if (codeA & codeB) {
5942                         return false;
5943                 }
5944
5945                 // other cases
5946                 codeOut = codeA || codeB;
5947                 p = _getEdgeIntersection(a, b, codeOut, bounds, round);
5948                 newCode = _getBitCode(p, bounds);
5949
5950                 if (codeOut === codeA) {
5951                         a = p;
5952                         codeA = newCode;
5953                 } else {
5954                         b = p;
5955                         codeB = newCode;
5956                 }
5957         }
5958 }
5959
5960 function _getEdgeIntersection(a, b, code, bounds, round) {
5961         var dx = b.x - a.x,
5962             dy = b.y - a.y,
5963             min = bounds.min,
5964             max = bounds.max,
5965             x, y;
5966
5967         if (code & 8) { // top
5968                 x = a.x + dx * (max.y - a.y) / dy;
5969                 y = max.y;
5970
5971         } else if (code & 4) { // bottom
5972                 x = a.x + dx * (min.y - a.y) / dy;
5973                 y = min.y;
5974
5975         } else if (code & 2) { // right
5976                 x = max.x;
5977                 y = a.y + dy * (max.x - a.x) / dx;
5978
5979         } else if (code & 1) { // left
5980                 x = min.x;
5981                 y = a.y + dy * (min.x - a.x) / dx;
5982         }
5983
5984         return new Point(x, y, round);
5985 }
5986
5987 function _getBitCode(p, bounds) {
5988         var code = 0;
5989
5990         if (p.x < bounds.min.x) { // left
5991                 code |= 1;
5992         } else if (p.x > bounds.max.x) { // right
5993                 code |= 2;
5994         }
5995
5996         if (p.y < bounds.min.y) { // bottom
5997                 code |= 4;
5998         } else if (p.y > bounds.max.y) { // top
5999                 code |= 8;
6000         }
6001
6002         return code;
6003 }
6004
6005 // square distance (to avoid unnecessary Math.sqrt calls)
6006 function _sqDist(p1, p2) {
6007         var dx = p2.x - p1.x,
6008             dy = p2.y - p1.y;
6009         return dx * dx + dy * dy;
6010 }
6011
6012 // return closest point on segment or distance to that point
6013 function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
6014         var x = p1.x,
6015             y = p1.y,
6016             dx = p2.x - x,
6017             dy = p2.y - y,
6018             dot = dx * dx + dy * dy,
6019             t;
6020
6021         if (dot > 0) {
6022                 t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
6023
6024                 if (t > 1) {
6025                         x = p2.x;
6026                         y = p2.y;
6027                 } else if (t > 0) {
6028                         x += dx * t;
6029                         y += dy * t;
6030                 }
6031         }
6032
6033         dx = p.x - x;
6034         dy = p.y - y;
6035
6036         return sqDist ? dx * dx + dy * dy : new Point(x, y);
6037 }
6038
6039
6040 // @function isFlat(latlngs: LatLng[]): Boolean
6041 // Returns true if `latlngs` is a flat array, false is nested.
6042 function isFlat(latlngs) {
6043         return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
6044 }
6045
6046 function _flat(latlngs) {
6047         console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
6048         return isFlat(latlngs);
6049 }
6050
6051
6052 var LineUtil = (Object.freeze || Object)({
6053         simplify: simplify,
6054         pointToSegmentDistance: pointToSegmentDistance,
6055         closestPointOnSegment: closestPointOnSegment,
6056         clipSegment: clipSegment,
6057         _getEdgeIntersection: _getEdgeIntersection,
6058         _getBitCode: _getBitCode,
6059         _sqClosestPointOnSegment: _sqClosestPointOnSegment,
6060         isFlat: isFlat,
6061         _flat: _flat
6062 });
6063
6064 /*
6065  * @namespace PolyUtil
6066  * Various utility functions for polygon geometries.
6067  */
6068
6069 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
6070  * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
6071  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
6072  * performance. Note that polygon points needs different algorithm for clipping
6073  * than polyline, so there's a seperate method for it.
6074  */
6075 function clipPolygon(points, bounds, round) {
6076         var clippedPoints,
6077             edges = [1, 4, 2, 8],
6078             i, j, k,
6079             a, b,
6080             len, edge, p;
6081
6082         for (i = 0, len = points.length; i < len; i++) {
6083                 points[i]._code = _getBitCode(points[i], bounds);
6084         }
6085
6086         // for each edge (left, bottom, right, top)
6087         for (k = 0; k < 4; k++) {
6088                 edge = edges[k];
6089                 clippedPoints = [];
6090
6091                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
6092                         a = points[i];
6093                         b = points[j];
6094
6095                         // if a is inside the clip window
6096                         if (!(a._code & edge)) {
6097                                 // if b is outside the clip window (a->b goes out of screen)
6098                                 if (b._code & edge) {
6099                                         p = _getEdgeIntersection(b, a, edge, bounds, round);
6100                                         p._code = _getBitCode(p, bounds);
6101                                         clippedPoints.push(p);
6102                                 }
6103                                 clippedPoints.push(a);
6104
6105                         // else if b is inside the clip window (a->b enters the screen)
6106                         } else if (!(b._code & edge)) {
6107                                 p = _getEdgeIntersection(b, a, edge, bounds, round);
6108                                 p._code = _getBitCode(p, bounds);
6109                                 clippedPoints.push(p);
6110                         }
6111                 }
6112                 points = clippedPoints;
6113         }
6114
6115         return points;
6116 }
6117
6118
6119 var PolyUtil = (Object.freeze || Object)({
6120         clipPolygon: clipPolygon
6121 });
6122
6123 /*
6124  * @namespace Projection
6125  * @section
6126  * Leaflet comes with a set of already defined Projections out of the box:
6127  *
6128  * @projection L.Projection.LonLat
6129  *
6130  * Equirectangular, or Plate Carree projection — the most simple projection,
6131  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
6132  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
6133  * `EPSG:4326` and `Simple` CRS.
6134  */
6135
6136 var LonLat = {
6137         project: function (latlng) {
6138                 return new Point(latlng.lng, latlng.lat);
6139         },
6140
6141         unproject: function (point) {
6142                 return new LatLng(point.y, point.x);
6143         },
6144
6145         bounds: new Bounds([-180, -90], [180, 90])
6146 };
6147
6148 /*
6149  * @namespace Projection
6150  * @projection L.Projection.Mercator
6151  *
6152  * 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.
6153  */
6154
6155 var Mercator = {
6156         R: 6378137,
6157         R_MINOR: 6356752.314245179,
6158
6159         bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
6160
6161         project: function (latlng) {
6162                 var d = Math.PI / 180,
6163                     r = this.R,
6164                     y = latlng.lat * d,
6165                     tmp = this.R_MINOR / r,
6166                     e = Math.sqrt(1 - tmp * tmp),
6167                     con = e * Math.sin(y);
6168
6169                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
6170                 y = -r * Math.log(Math.max(ts, 1E-10));
6171
6172                 return new Point(latlng.lng * d * r, y);
6173         },
6174
6175         unproject: function (point) {
6176                 var d = 180 / Math.PI,
6177                     r = this.R,
6178                     tmp = this.R_MINOR / r,
6179                     e = Math.sqrt(1 - tmp * tmp),
6180                     ts = Math.exp(-point.y / r),
6181                     phi = Math.PI / 2 - 2 * Math.atan(ts);
6182
6183                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
6184                         con = e * Math.sin(phi);
6185                         con = Math.pow((1 - con) / (1 + con), e / 2);
6186                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
6187                         phi += dphi;
6188                 }
6189
6190                 return new LatLng(phi * d, point.x * d / r);
6191         }
6192 };
6193
6194 /*
6195  * @class Projection
6196
6197  * An object with methods for projecting geographical coordinates of the world onto
6198  * a flat surface (and back). See [Map projection](http://en.wikipedia.org/wiki/Map_projection).
6199
6200  * @property bounds: Bounds
6201  * The bounds (specified in CRS units) where the projection is valid
6202
6203  * @method project(latlng: LatLng): Point
6204  * Projects geographical coordinates into a 2D point.
6205  * Only accepts actual `L.LatLng` instances, not arrays.
6206
6207  * @method unproject(point: Point): LatLng
6208  * The inverse of `project`. Projects a 2D point into a geographical location.
6209  * Only accepts actual `L.Point` instances, not arrays.
6210
6211  */
6212
6213
6214
6215
6216 var index = (Object.freeze || Object)({
6217         LonLat: LonLat,
6218         Mercator: Mercator,
6219         SphericalMercator: SphericalMercator
6220 });
6221
6222 /*
6223  * @namespace CRS
6224  * @crs L.CRS.EPSG3395
6225  *
6226  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
6227  */
6228 var EPSG3395 = extend({}, Earth, {
6229         code: 'EPSG:3395',
6230         projection: Mercator,
6231
6232         transformation: (function () {
6233                 var scale = 0.5 / (Math.PI * Mercator.R);
6234                 return toTransformation(scale, 0.5, -scale, 0.5);
6235         }())
6236 });
6237
6238 /*
6239  * @namespace CRS
6240  * @crs L.CRS.EPSG4326
6241  *
6242  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
6243  *
6244  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
6245  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
6246  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
6247  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
6248  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
6249  */
6250
6251 var EPSG4326 = extend({}, Earth, {
6252         code: 'EPSG:4326',
6253         projection: LonLat,
6254         transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
6255 });
6256
6257 /*
6258  * @namespace CRS
6259  * @crs L.CRS.Simple
6260  *
6261  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
6262  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
6263  * axis should still be inverted (going from bottom to top). `distance()` returns
6264  * simple euclidean distance.
6265  */
6266
6267 var Simple = extend({}, CRS, {
6268         projection: LonLat,
6269         transformation: toTransformation(1, 0, -1, 0),
6270
6271         scale: function (zoom) {
6272                 return Math.pow(2, zoom);
6273         },
6274
6275         zoom: function (scale) {
6276                 return Math.log(scale) / Math.LN2;
6277         },
6278
6279         distance: function (latlng1, latlng2) {
6280                 var dx = latlng2.lng - latlng1.lng,
6281                     dy = latlng2.lat - latlng1.lat;
6282
6283                 return Math.sqrt(dx * dx + dy * dy);
6284         },
6285
6286         infinite: true
6287 });
6288
6289 CRS.Earth = Earth;
6290 CRS.EPSG3395 = EPSG3395;
6291 CRS.EPSG3857 = EPSG3857;
6292 CRS.EPSG900913 = EPSG900913;
6293 CRS.EPSG4326 = EPSG4326;
6294 CRS.Simple = Simple;
6295
6296 /*
6297  * @class Layer
6298  * @inherits Evented
6299  * @aka L.Layer
6300  * @aka ILayer
6301  *
6302  * A set of methods from the Layer base class that all Leaflet layers use.
6303  * Inherits all methods, options and events from `L.Evented`.
6304  *
6305  * @example
6306  *
6307  * ```js
6308  * var layer = L.Marker(latlng).addTo(map);
6309  * layer.addTo(map);
6310  * layer.remove();
6311  * ```
6312  *
6313  * @event add: Event
6314  * Fired after the layer is added to a map
6315  *
6316  * @event remove: Event
6317  * Fired after the layer is removed from a map
6318  */
6319
6320
6321 var Layer = Evented.extend({
6322
6323         // Classes extending `L.Layer` will inherit the following options:
6324         options: {
6325                 // @option pane: String = 'overlayPane'
6326                 // 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.
6327                 pane: 'overlayPane',
6328
6329                 // @option attribution: String = null
6330                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
6331                 attribution: null,
6332
6333                 bubblingMouseEvents: true
6334         },
6335
6336         /* @section
6337          * Classes extending `L.Layer` will inherit the following methods:
6338          *
6339          * @method addTo(map: Map|LayerGroup): this
6340          * Adds the layer to the given map or layer group.
6341          */
6342         addTo: function (map) {
6343                 map.addLayer(this);
6344                 return this;
6345         },
6346
6347         // @method remove: this
6348         // Removes the layer from the map it is currently active on.
6349         remove: function () {
6350                 return this.removeFrom(this._map || this._mapToAdd);
6351         },
6352
6353         // @method removeFrom(map: Map): this
6354         // Removes the layer from the given map
6355         removeFrom: function (obj) {
6356                 if (obj) {
6357                         obj.removeLayer(this);
6358                 }
6359                 return this;
6360         },
6361
6362         // @method getPane(name? : String): HTMLElement
6363         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
6364         getPane: function (name) {
6365                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
6366         },
6367
6368         addInteractiveTarget: function (targetEl) {
6369                 this._map._targets[stamp(targetEl)] = this;
6370                 return this;
6371         },
6372
6373         removeInteractiveTarget: function (targetEl) {
6374                 delete this._map._targets[stamp(targetEl)];
6375                 return this;
6376         },
6377
6378         // @method getAttribution: String
6379         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
6380         getAttribution: function () {
6381                 return this.options.attribution;
6382         },
6383
6384         _layerAdd: function (e) {
6385                 var map = e.target;
6386
6387                 // check in case layer gets added and then removed before the map is ready
6388                 if (!map.hasLayer(this)) { return; }
6389
6390                 this._map = map;
6391                 this._zoomAnimated = map._zoomAnimated;
6392
6393                 if (this.getEvents) {
6394                         var events = this.getEvents();
6395                         map.on(events, this);
6396                         this.once('remove', function () {
6397                                 map.off(events, this);
6398                         }, this);
6399                 }
6400
6401                 this.onAdd(map);
6402
6403                 if (this.getAttribution && map.attributionControl) {
6404                         map.attributionControl.addAttribution(this.getAttribution());
6405                 }
6406
6407                 this.fire('add');
6408                 map.fire('layeradd', {layer: this});
6409         }
6410 });
6411
6412 /* @section Extension methods
6413  * @uninheritable
6414  *
6415  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
6416  *
6417  * @method onAdd(map: Map): this
6418  * 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).
6419  *
6420  * @method onRemove(map: Map): this
6421  * 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).
6422  *
6423  * @method getEvents(): Object
6424  * 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.
6425  *
6426  * @method getAttribution(): String
6427  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
6428  *
6429  * @method beforeAdd(map: Map): this
6430  * 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.
6431  */
6432
6433
6434 /* @namespace Map
6435  * @section Layer events
6436  *
6437  * @event layeradd: LayerEvent
6438  * Fired when a new layer is added to the map.
6439  *
6440  * @event layerremove: LayerEvent
6441  * Fired when some layer is removed from the map
6442  *
6443  * @section Methods for Layers and Controls
6444  */
6445 Map.include({
6446         // @method addLayer(layer: Layer): this
6447         // Adds the given layer to the map
6448         addLayer: function (layer) {
6449                 if (!layer._layerAdd) {
6450                         throw new Error('The provided object is not a Layer.');
6451                 }
6452
6453                 var id = stamp(layer);
6454                 if (this._layers[id]) { return this; }
6455                 this._layers[id] = layer;
6456
6457                 layer._mapToAdd = this;
6458
6459                 if (layer.beforeAdd) {
6460                         layer.beforeAdd(this);
6461                 }
6462
6463                 this.whenReady(layer._layerAdd, layer);
6464
6465                 return this;
6466         },
6467
6468         // @method removeLayer(layer: Layer): this
6469         // Removes the given layer from the map.
6470         removeLayer: function (layer) {
6471                 var id = stamp(layer);
6472
6473                 if (!this._layers[id]) { return this; }
6474
6475                 if (this._loaded) {
6476                         layer.onRemove(this);
6477                 }
6478
6479                 if (layer.getAttribution && this.attributionControl) {
6480                         this.attributionControl.removeAttribution(layer.getAttribution());
6481                 }
6482
6483                 delete this._layers[id];
6484
6485                 if (this._loaded) {
6486                         this.fire('layerremove', {layer: layer});
6487                         layer.fire('remove');
6488                 }
6489
6490                 layer._map = layer._mapToAdd = null;
6491
6492                 return this;
6493         },
6494
6495         // @method hasLayer(layer: Layer): Boolean
6496         // Returns `true` if the given layer is currently added to the map
6497         hasLayer: function (layer) {
6498                 return !!layer && (stamp(layer) in this._layers);
6499         },
6500
6501         /* @method eachLayer(fn: Function, context?: Object): this
6502          * Iterates over the layers of the map, optionally specifying context of the iterator function.
6503          * ```
6504          * map.eachLayer(function(layer){
6505          *     layer.bindPopup('Hello');
6506          * });
6507          * ```
6508          */
6509         eachLayer: function (method, context) {
6510                 for (var i in this._layers) {
6511                         method.call(context, this._layers[i]);
6512                 }
6513                 return this;
6514         },
6515
6516         _addLayers: function (layers) {
6517                 layers = layers ? (isArray(layers) ? layers : [layers]) : [];
6518
6519                 for (var i = 0, len = layers.length; i < len; i++) {
6520                         this.addLayer(layers[i]);
6521                 }
6522         },
6523
6524         _addZoomLimit: function (layer) {
6525                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
6526                         this._zoomBoundLayers[stamp(layer)] = layer;
6527                         this._updateZoomLevels();
6528                 }
6529         },
6530
6531         _removeZoomLimit: function (layer) {
6532                 var id = stamp(layer);
6533
6534                 if (this._zoomBoundLayers[id]) {
6535                         delete this._zoomBoundLayers[id];
6536                         this._updateZoomLevels();
6537                 }
6538         },
6539
6540         _updateZoomLevels: function () {
6541                 var minZoom = Infinity,
6542                     maxZoom = -Infinity,
6543                     oldZoomSpan = this._getZoomSpan();
6544
6545                 for (var i in this._zoomBoundLayers) {
6546                         var options = this._zoomBoundLayers[i].options;
6547
6548                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
6549                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
6550                 }
6551
6552                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
6553                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
6554
6555                 // @section Map state change events
6556                 // @event zoomlevelschange: Event
6557                 // Fired when the number of zoomlevels on the map is changed due
6558                 // to adding or removing a layer.
6559                 if (oldZoomSpan !== this._getZoomSpan()) {
6560                         this.fire('zoomlevelschange');
6561                 }
6562
6563                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
6564                         this.setZoom(this._layersMaxZoom);
6565                 }
6566                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
6567                         this.setZoom(this._layersMinZoom);
6568                 }
6569         }
6570 });
6571
6572 /*
6573  * @class LayerGroup
6574  * @aka L.LayerGroup
6575  * @inherits Layer
6576  *
6577  * Used to group several layers and handle them as one. If you add it to the map,
6578  * any layers added or removed from the group will be added/removed on the map as
6579  * well. Extends `Layer`.
6580  *
6581  * @example
6582  *
6583  * ```js
6584  * L.layerGroup([marker1, marker2])
6585  *      .addLayer(polyline)
6586  *      .addTo(map);
6587  * ```
6588  */
6589
6590 var LayerGroup = Layer.extend({
6591
6592         initialize: function (layers) {
6593                 this._layers = {};
6594
6595                 var i, len;
6596
6597                 if (layers) {
6598                         for (i = 0, len = layers.length; i < len; i++) {
6599                                 this.addLayer(layers[i]);
6600                         }
6601                 }
6602         },
6603
6604         // @method addLayer(layer: Layer): this
6605         // Adds the given layer to the group.
6606         addLayer: function (layer) {
6607                 var id = this.getLayerId(layer);
6608
6609                 this._layers[id] = layer;
6610
6611                 if (this._map) {
6612                         this._map.addLayer(layer);
6613                 }
6614
6615                 return this;
6616         },
6617
6618         // @method removeLayer(layer: Layer): this
6619         // Removes the given layer from the group.
6620         // @alternative
6621         // @method removeLayer(id: Number): this
6622         // Removes the layer with the given internal ID from the group.
6623         removeLayer: function (layer) {
6624                 var id = layer in this._layers ? layer : this.getLayerId(layer);
6625
6626                 if (this._map && this._layers[id]) {
6627                         this._map.removeLayer(this._layers[id]);
6628                 }
6629
6630                 delete this._layers[id];
6631
6632                 return this;
6633         },
6634
6635         // @method hasLayer(layer: Layer): Boolean
6636         // Returns `true` if the given layer is currently added to the group.
6637         // @alternative
6638         // @method hasLayer(id: Number): Boolean
6639         // Returns `true` if the given internal ID is currently added to the group.
6640         hasLayer: function (layer) {
6641                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
6642         },
6643
6644         // @method clearLayers(): this
6645         // Removes all the layers from the group.
6646         clearLayers: function () {
6647                 for (var i in this._layers) {
6648                         this.removeLayer(this._layers[i]);
6649                 }
6650                 return this;
6651         },
6652
6653         // @method invoke(methodName: String, …): this
6654         // Calls `methodName` on every layer contained in this group, passing any
6655         // additional parameters. Has no effect if the layers contained do not
6656         // implement `methodName`.
6657         invoke: function (methodName) {
6658                 var args = Array.prototype.slice.call(arguments, 1),
6659                     i, layer;
6660
6661                 for (i in this._layers) {
6662                         layer = this._layers[i];
6663
6664                         if (layer[methodName]) {
6665                                 layer[methodName].apply(layer, args);
6666                         }
6667                 }
6668
6669                 return this;
6670         },
6671
6672         onAdd: function (map) {
6673                 for (var i in this._layers) {
6674                         map.addLayer(this._layers[i]);
6675                 }
6676         },
6677
6678         onRemove: function (map) {
6679                 for (var i in this._layers) {
6680                         map.removeLayer(this._layers[i]);
6681                 }
6682         },
6683
6684         // @method eachLayer(fn: Function, context?: Object): this
6685         // Iterates over the layers of the group, optionally specifying context of the iterator function.
6686         // ```js
6687         // group.eachLayer(function (layer) {
6688         //      layer.bindPopup('Hello');
6689         // });
6690         // ```
6691         eachLayer: function (method, context) {
6692                 for (var i in this._layers) {
6693                         method.call(context, this._layers[i]);
6694                 }
6695                 return this;
6696         },
6697
6698         // @method getLayer(id: Number): Layer
6699         // Returns the layer with the given internal ID.
6700         getLayer: function (id) {
6701                 return this._layers[id];
6702         },
6703
6704         // @method getLayers(): Layer[]
6705         // Returns an array of all the layers added to the group.
6706         getLayers: function () {
6707                 var layers = [];
6708
6709                 for (var i in this._layers) {
6710                         layers.push(this._layers[i]);
6711                 }
6712                 return layers;
6713         },
6714
6715         // @method setZIndex(zIndex: Number): this
6716         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
6717         setZIndex: function (zIndex) {
6718                 return this.invoke('setZIndex', zIndex);
6719         },
6720
6721         // @method getLayerId(layer: Layer): Number
6722         // Returns the internal ID for a layer
6723         getLayerId: function (layer) {
6724                 return stamp(layer);
6725         }
6726 });
6727
6728
6729 // @factory L.layerGroup(layers?: Layer[])
6730 // Create a layer group, optionally given an initial set of layers.
6731 var layerGroup = function (layers) {
6732         return new LayerGroup(layers);
6733 };
6734
6735 /*
6736  * @class FeatureGroup
6737  * @aka L.FeatureGroup
6738  * @inherits LayerGroup
6739  *
6740  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
6741  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
6742  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
6743  * handler, it will handle events from any of the layers. This includes mouse events
6744  * and custom events.
6745  *  * Has `layeradd` and `layerremove` events
6746  *
6747  * @example
6748  *
6749  * ```js
6750  * L.featureGroup([marker1, marker2, polyline])
6751  *      .bindPopup('Hello world!')
6752  *      .on('click', function() { alert('Clicked on a member of the group!'); })
6753  *      .addTo(map);
6754  * ```
6755  */
6756
6757 var FeatureGroup = LayerGroup.extend({
6758
6759         addLayer: function (layer) {
6760                 if (this.hasLayer(layer)) {
6761                         return this;
6762                 }
6763
6764                 layer.addEventParent(this);
6765
6766                 LayerGroup.prototype.addLayer.call(this, layer);
6767
6768                 // @event layeradd: LayerEvent
6769                 // Fired when a layer is added to this `FeatureGroup`
6770                 return this.fire('layeradd', {layer: layer});
6771         },
6772
6773         removeLayer: function (layer) {
6774                 if (!this.hasLayer(layer)) {
6775                         return this;
6776                 }
6777                 if (layer in this._layers) {
6778                         layer = this._layers[layer];
6779                 }
6780
6781                 layer.removeEventParent(this);
6782
6783                 LayerGroup.prototype.removeLayer.call(this, layer);
6784
6785                 // @event layerremove: LayerEvent
6786                 // Fired when a layer is removed from this `FeatureGroup`
6787                 return this.fire('layerremove', {layer: layer});
6788         },
6789
6790         // @method setStyle(style: Path options): this
6791         // Sets the given path options to each layer of the group that has a `setStyle` method.
6792         setStyle: function (style) {
6793                 return this.invoke('setStyle', style);
6794         },
6795
6796         // @method bringToFront(): this
6797         // Brings the layer group to the top of all other layers
6798         bringToFront: function () {
6799                 return this.invoke('bringToFront');
6800         },
6801
6802         // @method bringToBack(): this
6803         // Brings the layer group to the top of all other layers
6804         bringToBack: function () {
6805                 return this.invoke('bringToBack');
6806         },
6807
6808         // @method getBounds(): LatLngBounds
6809         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
6810         getBounds: function () {
6811                 var bounds = new LatLngBounds();
6812
6813                 for (var id in this._layers) {
6814                         var layer = this._layers[id];
6815                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
6816                 }
6817                 return bounds;
6818         }
6819 });
6820
6821 // @factory L.featureGroup(layers: Layer[])
6822 // Create a feature group, optionally given an initial set of layers.
6823 var featureGroup = function (layers) {
6824         return new FeatureGroup(layers);
6825 };
6826
6827 /*
6828  * @class Icon
6829  * @aka L.Icon
6830  *
6831  * Represents an icon to provide when creating a marker.
6832  *
6833  * @example
6834  *
6835  * ```js
6836  * var myIcon = L.icon({
6837  *     iconUrl: 'my-icon.png',
6838  *     iconRetinaUrl: 'my-icon@2x.png',
6839  *     iconSize: [38, 95],
6840  *     iconAnchor: [22, 94],
6841  *     popupAnchor: [-3, -76],
6842  *     shadowUrl: 'my-icon-shadow.png',
6843  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
6844  *     shadowSize: [68, 95],
6845  *     shadowAnchor: [22, 94]
6846  * });
6847  *
6848  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6849  * ```
6850  *
6851  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6852  *
6853  */
6854
6855 var Icon = Class.extend({
6856
6857         /* @section
6858          * @aka Icon options
6859          *
6860          * @option iconUrl: String = null
6861          * **(required)** The URL to the icon image (absolute or relative to your script path).
6862          *
6863          * @option iconRetinaUrl: String = null
6864          * The URL to a retina sized version of the icon image (absolute or relative to your
6865          * script path). Used for Retina screen devices.
6866          *
6867          * @option iconSize: Point = null
6868          * Size of the icon image in pixels.
6869          *
6870          * @option iconAnchor: Point = null
6871          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6872          * will be aligned so that this point is at the marker's geographical location. Centered
6873          * by default if size is specified, also can be set in CSS with negative margins.
6874          *
6875          * @option popupAnchor: Point = null
6876          * The coordinates of the point from which popups will "open", relative to the icon anchor.
6877          *
6878          * @option shadowUrl: String = null
6879          * The URL to the icon shadow image. If not specified, no shadow image will be created.
6880          *
6881          * @option shadowRetinaUrl: String = null
6882          *
6883          * @option shadowSize: Point = null
6884          * Size of the shadow image in pixels.
6885          *
6886          * @option shadowAnchor: Point = null
6887          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6888          * as iconAnchor if not specified).
6889          *
6890          * @option className: String = ''
6891          * A custom class name to assign to both icon and shadow images. Empty by default.
6892          */
6893
6894         initialize: function (options) {
6895                 setOptions(this, options);
6896         },
6897
6898         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6899         // Called internally when the icon has to be shown, returns a `<img>` HTML element
6900         // styled according to the options.
6901         createIcon: function (oldIcon) {
6902                 return this._createIcon('icon', oldIcon);
6903         },
6904
6905         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6906         // As `createIcon`, but for the shadow beneath it.
6907         createShadow: function (oldIcon) {
6908                 return this._createIcon('shadow', oldIcon);
6909         },
6910
6911         _createIcon: function (name, oldIcon) {
6912                 var src = this._getIconUrl(name);
6913
6914                 if (!src) {
6915                         if (name === 'icon') {
6916                                 throw new Error('iconUrl not set in Icon options (see the docs).');
6917                         }
6918                         return null;
6919                 }
6920
6921                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6922                 this._setIconStyles(img, name);
6923
6924                 return img;
6925         },
6926
6927         _setIconStyles: function (img, name) {
6928                 var options = this.options;
6929                 var sizeOption = options[name + 'Size'];
6930
6931                 if (typeof sizeOption === 'number') {
6932                         sizeOption = [sizeOption, sizeOption];
6933                 }
6934
6935                 var size = toPoint(sizeOption),
6936                     anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6937                             size && size.divideBy(2, true));
6938
6939                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6940
6941                 if (anchor) {
6942                         img.style.marginLeft = (-anchor.x) + 'px';
6943                         img.style.marginTop  = (-anchor.y) + 'px';
6944                 }
6945
6946                 if (size) {
6947                         img.style.width  = size.x + 'px';
6948                         img.style.height = size.y + 'px';
6949                 }
6950         },
6951
6952         _createImg: function (src, el) {
6953                 el = el || document.createElement('img');
6954                 el.src = src;
6955                 return el;
6956         },
6957
6958         _getIconUrl: function (name) {
6959                 return retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6960         }
6961 });
6962
6963
6964 // @factory L.icon(options: Icon options)
6965 // Creates an icon instance with the given options.
6966 function icon(options) {
6967         return new Icon(options);
6968 }
6969
6970 /*
6971  * @miniclass Icon.Default (Icon)
6972  * @aka L.Icon.Default
6973  * @section
6974  *
6975  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6976  * no icon is specified. Points to the blue marker image distributed with Leaflet
6977  * releases.
6978  *
6979  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6980  * (which is a set of `Icon options`).
6981  *
6982  * If you want to _completely_ replace the default icon, override the
6983  * `L.Marker.prototype.options.icon` with your own icon instead.
6984  */
6985
6986 var IconDefault = Icon.extend({
6987
6988         options: {
6989                 iconUrl:       'marker-icon.png',
6990                 iconRetinaUrl: 'marker-icon-2x.png',
6991                 shadowUrl:     'marker-shadow.png',
6992                 iconSize:    [25, 41],
6993                 iconAnchor:  [12, 41],
6994                 popupAnchor: [1, -34],
6995                 tooltipAnchor: [16, -28],
6996                 shadowSize:  [41, 41]
6997         },
6998
6999         _getIconUrl: function (name) {
7000                 if (!IconDefault.imagePath) {   // Deprecated, backwards-compatibility only
7001                         IconDefault.imagePath = this._detectIconPath();
7002                 }
7003
7004                 // @option imagePath: String
7005                 // `Icon.Default` will try to auto-detect the absolute location of the
7006                 // blue icon images. If you are placing these images in a non-standard
7007                 // way, set this option to point to the right absolute path.
7008                 return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
7009         },
7010
7011         _detectIconPath: function () {
7012                 var el = create$1('div',  'leaflet-default-icon-path', document.body);
7013                 var path = getStyle(el, 'background-image') ||
7014                            getStyle(el, 'backgroundImage');     // IE8
7015
7016                 document.body.removeChild(el);
7017
7018                 if (path === null || path.indexOf('url') !== 0) {
7019                         path = '';
7020                 } else {
7021                         path = path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '');
7022                 }
7023
7024                 return path;
7025         }
7026 });
7027
7028 /*
7029  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
7030  */
7031
7032
7033 /* @namespace Marker
7034  * @section Interaction handlers
7035  *
7036  * 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:
7037  *
7038  * ```js
7039  * marker.dragging.disable();
7040  * ```
7041  *
7042  * @property dragging: Handler
7043  * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
7044  */
7045
7046 var MarkerDrag = Handler.extend({
7047         initialize: function (marker) {
7048                 this._marker = marker;
7049         },
7050
7051         addHooks: function () {
7052                 var icon = this._marker._icon;
7053
7054                 if (!this._draggable) {
7055                         this._draggable = new Draggable(icon, icon, true);
7056                 }
7057
7058                 this._draggable.on({
7059                         dragstart: this._onDragStart,
7060                         drag: this._onDrag,
7061                         dragend: this._onDragEnd
7062                 }, this).enable();
7063
7064                 addClass(icon, 'leaflet-marker-draggable');
7065         },
7066
7067         removeHooks: function () {
7068                 this._draggable.off({
7069                         dragstart: this._onDragStart,
7070                         drag: this._onDrag,
7071                         dragend: this._onDragEnd
7072                 }, this).disable();
7073
7074                 if (this._marker._icon) {
7075                         removeClass(this._marker._icon, 'leaflet-marker-draggable');
7076                 }
7077         },
7078
7079         moved: function () {
7080                 return this._draggable && this._draggable._moved;
7081         },
7082
7083         _onDragStart: function () {
7084                 // @section Dragging events
7085                 // @event dragstart: Event
7086                 // Fired when the user starts dragging the marker.
7087
7088                 // @event movestart: Event
7089                 // Fired when the marker starts moving (because of dragging).
7090
7091                 this._oldLatLng = this._marker.getLatLng();
7092                 this._marker
7093                     .closePopup()
7094                     .fire('movestart')
7095                     .fire('dragstart');
7096         },
7097
7098         _onDrag: function (e) {
7099                 var marker = this._marker,
7100                     shadow = marker._shadow,
7101                 iconPos = getPosition(marker._icon),
7102                     latlng = marker._map.layerPointToLatLng(iconPos);
7103
7104                 // update shadow position
7105                 if (shadow) {
7106                         setPosition(shadow, iconPos);
7107                 }
7108
7109                 marker._latlng = latlng;
7110                 e.latlng = latlng;
7111                 e.oldLatLng = this._oldLatLng;
7112
7113                 // @event drag: Event
7114                 // Fired repeatedly while the user drags the marker.
7115                 marker
7116                     .fire('move', e)
7117                     .fire('drag', e);
7118         },
7119
7120         _onDragEnd: function (e) {
7121                 // @event dragend: DragEndEvent
7122                 // Fired when the user stops dragging the marker.
7123
7124                 // @event moveend: Event
7125                 // Fired when the marker stops moving (because of dragging).
7126                 delete this._oldLatLng;
7127                 this._marker
7128                     .fire('moveend')
7129                     .fire('dragend', e);
7130         }
7131 });
7132
7133 /*
7134  * @class Marker
7135  * @inherits Interactive layer
7136  * @aka L.Marker
7137  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
7138  *
7139  * @example
7140  *
7141  * ```js
7142  * L.marker([50.5, 30.5]).addTo(map);
7143  * ```
7144  */
7145
7146 var Marker = Layer.extend({
7147
7148         // @section
7149         // @aka Marker options
7150         options: {
7151                 // @option icon: Icon = *
7152                 // Icon instance to use for rendering the marker.
7153                 // See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
7154                 // If not specified, a common instance of `L.Icon.Default` is used.
7155                 icon: new IconDefault(),
7156
7157                 // Option inherited from "Interactive layer" abstract class
7158                 interactive: true,
7159
7160                 // @option draggable: Boolean = false
7161                 // Whether the marker is draggable with mouse/touch or not.
7162                 draggable: false,
7163
7164                 // @option keyboard: Boolean = true
7165                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
7166                 keyboard: true,
7167
7168                 // @option title: String = ''
7169                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
7170                 title: '',
7171
7172                 // @option alt: String = ''
7173                 // Text for the `alt` attribute of the icon image (useful for accessibility).
7174                 alt: '',
7175
7176                 // @option zIndexOffset: Number = 0
7177                 // 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).
7178                 zIndexOffset: 0,
7179
7180                 // @option opacity: Number = 1.0
7181                 // The opacity of the marker.
7182                 opacity: 1,
7183
7184                 // @option riseOnHover: Boolean = false
7185                 // If `true`, the marker will get on top of others when you hover the mouse over it.
7186                 riseOnHover: false,
7187
7188                 // @option riseOffset: Number = 250
7189                 // The z-index offset used for the `riseOnHover` feature.
7190                 riseOffset: 250,
7191
7192                 // @option pane: String = 'markerPane'
7193                 // `Map pane` where the markers icon will be added.
7194                 pane: 'markerPane',
7195
7196                 // @option bubblingMouseEvents: Boolean = false
7197                 // When `true`, a mouse event on this marker will trigger the same event on the map
7198                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7199                 bubblingMouseEvents: false
7200         },
7201
7202         /* @section
7203          *
7204          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
7205          */
7206
7207         initialize: function (latlng, options) {
7208                 setOptions(this, options);
7209                 this._latlng = toLatLng(latlng);
7210         },
7211
7212         onAdd: function (map) {
7213                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
7214
7215                 if (this._zoomAnimated) {
7216                         map.on('zoomanim', this._animateZoom, this);
7217                 }
7218
7219                 this._initIcon();
7220                 this.update();
7221         },
7222
7223         onRemove: function (map) {
7224                 if (this.dragging && this.dragging.enabled()) {
7225                         this.options.draggable = true;
7226                         this.dragging.removeHooks();
7227                 }
7228                 delete this.dragging;
7229
7230                 if (this._zoomAnimated) {
7231                         map.off('zoomanim', this._animateZoom, this);
7232                 }
7233
7234                 this._removeIcon();
7235                 this._removeShadow();
7236         },
7237
7238         getEvents: function () {
7239                 return {
7240                         zoom: this.update,
7241                         viewreset: this.update
7242                 };
7243         },
7244
7245         // @method getLatLng: LatLng
7246         // Returns the current geographical position of the marker.
7247         getLatLng: function () {
7248                 return this._latlng;
7249         },
7250
7251         // @method setLatLng(latlng: LatLng): this
7252         // Changes the marker position to the given point.
7253         setLatLng: function (latlng) {
7254                 var oldLatLng = this._latlng;
7255                 this._latlng = toLatLng(latlng);
7256                 this.update();
7257
7258                 // @event move: Event
7259                 // 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`.
7260                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
7261         },
7262
7263         // @method setZIndexOffset(offset: Number): this
7264         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
7265         setZIndexOffset: function (offset) {
7266                 this.options.zIndexOffset = offset;
7267                 return this.update();
7268         },
7269
7270         // @method setIcon(icon: Icon): this
7271         // Changes the marker icon.
7272         setIcon: function (icon) {
7273
7274                 this.options.icon = icon;
7275
7276                 if (this._map) {
7277                         this._initIcon();
7278                         this.update();
7279                 }
7280
7281                 if (this._popup) {
7282                         this.bindPopup(this._popup, this._popup.options);
7283                 }
7284
7285                 return this;
7286         },
7287
7288         getElement: function () {
7289                 return this._icon;
7290         },
7291
7292         update: function () {
7293
7294                 if (this._icon) {
7295                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
7296                         this._setPos(pos);
7297                 }
7298
7299                 return this;
7300         },
7301
7302         _initIcon: function () {
7303                 var options = this.options,
7304                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7305
7306                 var icon = options.icon.createIcon(this._icon),
7307                     addIcon = false;
7308
7309                 // if we're not reusing the icon, remove the old one and init new one
7310                 if (icon !== this._icon) {
7311                         if (this._icon) {
7312                                 this._removeIcon();
7313                         }
7314                         addIcon = true;
7315
7316                         if (options.title) {
7317                                 icon.title = options.title;
7318                         }
7319                         if (options.alt) {
7320                                 icon.alt = options.alt;
7321                         }
7322                 }
7323
7324                 addClass(icon, classToAdd);
7325
7326                 if (options.keyboard) {
7327                         icon.tabIndex = '0';
7328                 }
7329
7330                 this._icon = icon;
7331
7332                 if (options.riseOnHover) {
7333                         this.on({
7334                                 mouseover: this._bringToFront,
7335                                 mouseout: this._resetZIndex
7336                         });
7337                 }
7338
7339                 var newShadow = options.icon.createShadow(this._shadow),
7340                     addShadow = false;
7341
7342                 if (newShadow !== this._shadow) {
7343                         this._removeShadow();
7344                         addShadow = true;
7345                 }
7346
7347                 if (newShadow) {
7348                         addClass(newShadow, classToAdd);
7349                         newShadow.alt = '';
7350                 }
7351                 this._shadow = newShadow;
7352
7353
7354                 if (options.opacity < 1) {
7355                         this._updateOpacity();
7356                 }
7357
7358
7359                 if (addIcon) {
7360                         this.getPane().appendChild(this._icon);
7361                 }
7362                 this._initInteraction();
7363                 if (newShadow && addShadow) {
7364                         this.getPane('shadowPane').appendChild(this._shadow);
7365                 }
7366         },
7367
7368         _removeIcon: function () {
7369                 if (this.options.riseOnHover) {
7370                         this.off({
7371                                 mouseover: this._bringToFront,
7372                                 mouseout: this._resetZIndex
7373                         });
7374                 }
7375
7376                 remove(this._icon);
7377                 this.removeInteractiveTarget(this._icon);
7378
7379                 this._icon = null;
7380         },
7381
7382         _removeShadow: function () {
7383                 if (this._shadow) {
7384                         remove(this._shadow);
7385                 }
7386                 this._shadow = null;
7387         },
7388
7389         _setPos: function (pos) {
7390                 setPosition(this._icon, pos);
7391
7392                 if (this._shadow) {
7393                         setPosition(this._shadow, pos);
7394                 }
7395
7396                 this._zIndex = pos.y + this.options.zIndexOffset;
7397
7398                 this._resetZIndex();
7399         },
7400
7401         _updateZIndex: function (offset) {
7402                 this._icon.style.zIndex = this._zIndex + offset;
7403         },
7404
7405         _animateZoom: function (opt) {
7406                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
7407
7408                 this._setPos(pos);
7409         },
7410
7411         _initInteraction: function () {
7412
7413                 if (!this.options.interactive) { return; }
7414
7415                 addClass(this._icon, 'leaflet-interactive');
7416
7417                 this.addInteractiveTarget(this._icon);
7418
7419                 if (MarkerDrag) {
7420                         var draggable = this.options.draggable;
7421                         if (this.dragging) {
7422                                 draggable = this.dragging.enabled();
7423                                 this.dragging.disable();
7424                         }
7425
7426                         this.dragging = new MarkerDrag(this);
7427
7428                         if (draggable) {
7429                                 this.dragging.enable();
7430                         }
7431                 }
7432         },
7433
7434         // @method setOpacity(opacity: Number): this
7435         // Changes the opacity of the marker.
7436         setOpacity: function (opacity) {
7437                 this.options.opacity = opacity;
7438                 if (this._map) {
7439                         this._updateOpacity();
7440                 }
7441
7442                 return this;
7443         },
7444
7445         _updateOpacity: function () {
7446                 var opacity = this.options.opacity;
7447
7448                 setOpacity(this._icon, opacity);
7449
7450                 if (this._shadow) {
7451                         setOpacity(this._shadow, opacity);
7452                 }
7453         },
7454
7455         _bringToFront: function () {
7456                 this._updateZIndex(this.options.riseOffset);
7457         },
7458
7459         _resetZIndex: function () {
7460                 this._updateZIndex(0);
7461         },
7462
7463         _getPopupAnchor: function () {
7464                 return this.options.icon.options.popupAnchor || [0, 0];
7465         },
7466
7467         _getTooltipAnchor: function () {
7468                 return this.options.icon.options.tooltipAnchor || [0, 0];
7469         }
7470 });
7471
7472
7473 // factory L.marker(latlng: LatLng, options? : Marker options)
7474
7475 // @factory L.marker(latlng: LatLng, options? : Marker options)
7476 // Instantiates a Marker object given a geographical point and optionally an options object.
7477 function marker(latlng, options) {
7478         return new Marker(latlng, options);
7479 }
7480
7481 /*
7482  * @class Path
7483  * @aka L.Path
7484  * @inherits Interactive layer
7485  *
7486  * An abstract class that contains options and constants shared between vector
7487  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7488  */
7489
7490 var Path = Layer.extend({
7491
7492         // @section
7493         // @aka Path options
7494         options: {
7495                 // @option stroke: Boolean = true
7496                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7497                 stroke: true,
7498
7499                 // @option color: String = '#3388ff'
7500                 // Stroke color
7501                 color: '#3388ff',
7502
7503                 // @option weight: Number = 3
7504                 // Stroke width in pixels
7505                 weight: 3,
7506
7507                 // @option opacity: Number = 1.0
7508                 // Stroke opacity
7509                 opacity: 1,
7510
7511                 // @option lineCap: String= 'round'
7512                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7513                 lineCap: 'round',
7514
7515                 // @option lineJoin: String = 'round'
7516                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7517                 lineJoin: 'round',
7518
7519                 // @option dashArray: String = null
7520                 // 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).
7521                 dashArray: null,
7522
7523                 // @option dashOffset: String = null
7524                 // 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).
7525                 dashOffset: null,
7526
7527                 // @option fill: Boolean = depends
7528                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7529                 fill: false,
7530
7531                 // @option fillColor: String = *
7532                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7533                 fillColor: null,
7534
7535                 // @option fillOpacity: Number = 0.2
7536                 // Fill opacity.
7537                 fillOpacity: 0.2,
7538
7539                 // @option fillRule: String = 'evenodd'
7540                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7541                 fillRule: 'evenodd',
7542
7543                 // className: '',
7544
7545                 // Option inherited from "Interactive layer" abstract class
7546                 interactive: true,
7547
7548                 // @option bubblingMouseEvents: Boolean = true
7549                 // When `true`, a mouse event on this path will trigger the same event on the map
7550                 // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
7551                 bubblingMouseEvents: true
7552         },
7553
7554         beforeAdd: function (map) {
7555                 // Renderer is set here because we need to call renderer.getEvents
7556                 // before this.getEvents.
7557                 this._renderer = map.getRenderer(this);
7558         },
7559
7560         onAdd: function () {
7561                 this._renderer._initPath(this);
7562                 this._reset();
7563                 this._renderer._addPath(this);
7564         },
7565
7566         onRemove: function () {
7567                 this._renderer._removePath(this);
7568         },
7569
7570         // @method redraw(): this
7571         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7572         redraw: function () {
7573                 if (this._map) {
7574                         this._renderer._updatePath(this);
7575                 }
7576                 return this;
7577         },
7578
7579         // @method setStyle(style: Path options): this
7580         // Changes the appearance of a Path based on the options in the `Path options` object.
7581         setStyle: function (style) {
7582                 setOptions(this, style);
7583                 if (this._renderer) {
7584                         this._renderer._updateStyle(this);
7585                 }
7586                 return this;
7587         },
7588
7589         // @method bringToFront(): this
7590         // Brings the layer to the top of all path layers.
7591         bringToFront: function () {
7592                 if (this._renderer) {
7593                         this._renderer._bringToFront(this);
7594                 }
7595                 return this;
7596         },
7597
7598         // @method bringToBack(): this
7599         // Brings the layer to the bottom of all path layers.
7600         bringToBack: function () {
7601                 if (this._renderer) {
7602                         this._renderer._bringToBack(this);
7603                 }
7604                 return this;
7605         },
7606
7607         getElement: function () {
7608                 return this._path;
7609         },
7610
7611         _reset: function () {
7612                 // defined in child classes
7613                 this._project();
7614                 this._update();
7615         },
7616
7617         _clickTolerance: function () {
7618                 // used when doing hit detection for Canvas layers
7619                 return (this.options.stroke ? this.options.weight / 2 : 0) + (touch ? 10 : 0);
7620         }
7621 });
7622
7623 /*
7624  * @class CircleMarker
7625  * @aka L.CircleMarker
7626  * @inherits Path
7627  *
7628  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
7629  */
7630
7631 var CircleMarker = Path.extend({
7632
7633         // @section
7634         // @aka CircleMarker options
7635         options: {
7636                 fill: true,
7637
7638                 // @option radius: Number = 10
7639                 // Radius of the circle marker, in pixels
7640                 radius: 10
7641         },
7642
7643         initialize: function (latlng, options) {
7644                 setOptions(this, options);
7645                 this._latlng = toLatLng(latlng);
7646                 this._radius = this.options.radius;
7647         },
7648
7649         // @method setLatLng(latLng: LatLng): this
7650         // Sets the position of a circle marker to a new location.
7651         setLatLng: function (latlng) {
7652                 this._latlng = toLatLng(latlng);
7653                 this.redraw();
7654                 return this.fire('move', {latlng: this._latlng});
7655         },
7656
7657         // @method getLatLng(): LatLng
7658         // Returns the current geographical position of the circle marker
7659         getLatLng: function () {
7660                 return this._latlng;
7661         },
7662
7663         // @method setRadius(radius: Number): this
7664         // Sets the radius of a circle marker. Units are in pixels.
7665         setRadius: function (radius) {
7666                 this.options.radius = this._radius = radius;
7667                 return this.redraw();
7668         },
7669
7670         // @method getRadius(): Number
7671         // Returns the current radius of the circle
7672         getRadius: function () {
7673                 return this._radius;
7674         },
7675
7676         setStyle : function (options) {
7677                 var radius = options && options.radius || this._radius;
7678                 Path.prototype.setStyle.call(this, options);
7679                 this.setRadius(radius);
7680                 return this;
7681         },
7682
7683         _project: function () {
7684                 this._point = this._map.latLngToLayerPoint(this._latlng);
7685                 this._updateBounds();
7686         },
7687
7688         _updateBounds: function () {
7689                 var r = this._radius,
7690                     r2 = this._radiusY || r,
7691                     w = this._clickTolerance(),
7692                     p = [r + w, r2 + w];
7693                 this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
7694         },
7695
7696         _update: function () {
7697                 if (this._map) {
7698                         this._updatePath();
7699                 }
7700         },
7701
7702         _updatePath: function () {
7703                 this._renderer._updateCircle(this);
7704         },
7705
7706         _empty: function () {
7707                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
7708         },
7709
7710         // Needed by the `Canvas` renderer for interactivity
7711         _containsPoint: function (p) {
7712                 return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
7713         }
7714 });
7715
7716
7717 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
7718 // Instantiates a circle marker object given a geographical point, and an optional options object.
7719 function circleMarker(latlng, options) {
7720         return new CircleMarker(latlng, options);
7721 }
7722
7723 /*
7724  * @class Circle
7725  * @aka L.Circle
7726  * @inherits CircleMarker
7727  *
7728  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
7729  *
7730  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
7731  *
7732  * @example
7733  *
7734  * ```js
7735  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
7736  * ```
7737  */
7738
7739 var Circle = CircleMarker.extend({
7740
7741         initialize: function (latlng, options, legacyOptions) {
7742                 if (typeof options === 'number') {
7743                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
7744                         options = extend({}, legacyOptions, {radius: options});
7745                 }
7746                 setOptions(this, options);
7747                 this._latlng = toLatLng(latlng);
7748
7749                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
7750
7751                 // @section
7752                 // @aka Circle options
7753                 // @option radius: Number; Radius of the circle, in meters.
7754                 this._mRadius = this.options.radius;
7755         },
7756
7757         // @method setRadius(radius: Number): this
7758         // Sets the radius of a circle. Units are in meters.
7759         setRadius: function (radius) {
7760                 this._mRadius = radius;
7761                 return this.redraw();
7762         },
7763
7764         // @method getRadius(): Number
7765         // Returns the current radius of a circle. Units are in meters.
7766         getRadius: function () {
7767                 return this._mRadius;
7768         },
7769
7770         // @method getBounds(): LatLngBounds
7771         // Returns the `LatLngBounds` of the path.
7772         getBounds: function () {
7773                 var half = [this._radius, this._radiusY || this._radius];
7774
7775                 return new LatLngBounds(
7776                         this._map.layerPointToLatLng(this._point.subtract(half)),
7777                         this._map.layerPointToLatLng(this._point.add(half)));
7778         },
7779
7780         setStyle: Path.prototype.setStyle,
7781
7782         _project: function () {
7783
7784                 var lng = this._latlng.lng,
7785                     lat = this._latlng.lat,
7786                     map = this._map,
7787                     crs = map.options.crs;
7788
7789                 if (crs.distance === Earth.distance) {
7790                         var d = Math.PI / 180,
7791                             latR = (this._mRadius / Earth.R) / d,
7792                             top = map.project([lat + latR, lng]),
7793                             bottom = map.project([lat - latR, lng]),
7794                             p = top.add(bottom).divideBy(2),
7795                             lat2 = map.unproject(p).lat,
7796                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
7797                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
7798
7799                         if (isNaN(lngR) || lngR === 0) {
7800                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
7801                         }
7802
7803                         this._point = p.subtract(map.getPixelOrigin());
7804                         this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
7805                         this._radiusY = Math.max(Math.round(p.y - top.y), 1);
7806
7807                 } else {
7808                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
7809
7810                         this._point = map.latLngToLayerPoint(this._latlng);
7811                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
7812                 }
7813
7814                 this._updateBounds();
7815         }
7816 });
7817
7818 // @factory L.circle(latlng: LatLng, options?: Circle options)
7819 // Instantiates a circle object given a geographical point, and an options object
7820 // which contains the circle radius.
7821 // @alternative
7822 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
7823 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
7824 // Do not use in new applications or plugins.
7825 function circle(latlng, options, legacyOptions) {
7826         return new Circle(latlng, options, legacyOptions);
7827 }
7828
7829 /*
7830  * @class Polyline
7831  * @aka L.Polyline
7832  * @inherits Path
7833  *
7834  * A class for drawing polyline overlays on a map. Extends `Path`.
7835  *
7836  * @example
7837  *
7838  * ```js
7839  * // create a red polyline from an array of LatLng points
7840  * var latlngs = [
7841  *      [45.51, -122.68],
7842  *      [37.77, -122.43],
7843  *      [34.04, -118.2]
7844  * ];
7845  *
7846  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7847  *
7848  * // zoom the map to the polyline
7849  * map.fitBounds(polyline.getBounds());
7850  * ```
7851  *
7852  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7853  *
7854  * ```js
7855  * // create a red polyline from an array of arrays of LatLng points
7856  * var latlngs = [
7857  *      [[45.51, -122.68],
7858  *       [37.77, -122.43],
7859  *       [34.04, -118.2]],
7860  *      [[40.78, -73.91],
7861  *       [41.83, -87.62],
7862  *       [32.76, -96.72]]
7863  * ];
7864  * ```
7865  */
7866
7867
7868 var Polyline = Path.extend({
7869
7870         // @section
7871         // @aka Polyline options
7872         options: {
7873                 // @option smoothFactor: Number = 1.0
7874                 // How much to simplify the polyline on each zoom level. More means
7875                 // better performance and smoother look, and less means more accurate representation.
7876                 smoothFactor: 1.0,
7877
7878                 // @option noClip: Boolean = false
7879                 // Disable polyline clipping.
7880                 noClip: false
7881         },
7882
7883         initialize: function (latlngs, options) {
7884                 setOptions(this, options);
7885                 this._setLatLngs(latlngs);
7886         },
7887
7888         // @method getLatLngs(): LatLng[]
7889         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7890         getLatLngs: function () {
7891                 return this._latlngs;
7892         },
7893
7894         // @method setLatLngs(latlngs: LatLng[]): this
7895         // Replaces all the points in the polyline with the given array of geographical points.
7896         setLatLngs: function (latlngs) {
7897                 this._setLatLngs(latlngs);
7898                 return this.redraw();
7899         },
7900
7901         // @method isEmpty(): Boolean
7902         // Returns `true` if the Polyline has no LatLngs.
7903         isEmpty: function () {
7904                 return !this._latlngs.length;
7905         },
7906
7907         closestLayerPoint: function (p) {
7908                 var minDistance = Infinity,
7909                     minPoint = null,
7910                     closest = _sqClosestPointOnSegment,
7911                     p1, p2;
7912
7913                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7914                         var points = this._parts[j];
7915
7916                         for (var i = 1, len = points.length; i < len; i++) {
7917                                 p1 = points[i - 1];
7918                                 p2 = points[i];
7919
7920                                 var sqDist = closest(p, p1, p2, true);
7921
7922                                 if (sqDist < minDistance) {
7923                                         minDistance = sqDist;
7924                                         minPoint = closest(p, p1, p2);
7925                                 }
7926                         }
7927                 }
7928                 if (minPoint) {
7929                         minPoint.distance = Math.sqrt(minDistance);
7930                 }
7931                 return minPoint;
7932         },
7933
7934         // @method getCenter(): LatLng
7935         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7936         getCenter: function () {
7937                 // throws error when not yet added to map as this center calculation requires projected coordinates
7938                 if (!this._map) {
7939                         throw new Error('Must add layer to map before using getCenter()');
7940                 }
7941
7942                 var i, halfDist, segDist, dist, p1, p2, ratio,
7943                     points = this._rings[0],
7944                     len = points.length;
7945
7946                 if (!len) { return null; }
7947
7948                 // polyline centroid algorithm; only uses the first ring if there are multiple
7949
7950                 for (i = 0, halfDist = 0; i < len - 1; i++) {
7951                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
7952                 }
7953
7954                 // The line is so small in the current view that all points are on the same pixel.
7955                 if (halfDist === 0) {
7956                         return this._map.layerPointToLatLng(points[0]);
7957                 }
7958
7959                 for (i = 0, dist = 0; i < len - 1; i++) {
7960                         p1 = points[i];
7961                         p2 = points[i + 1];
7962                         segDist = p1.distanceTo(p2);
7963                         dist += segDist;
7964
7965                         if (dist > halfDist) {
7966                                 ratio = (dist - halfDist) / segDist;
7967                                 return this._map.layerPointToLatLng([
7968                                         p2.x - ratio * (p2.x - p1.x),
7969                                         p2.y - ratio * (p2.y - p1.y)
7970                                 ]);
7971                         }
7972                 }
7973         },
7974
7975         // @method getBounds(): LatLngBounds
7976         // Returns the `LatLngBounds` of the path.
7977         getBounds: function () {
7978                 return this._bounds;
7979         },
7980
7981         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7982         // Adds a given point to the polyline. By default, adds to the first ring of
7983         // the polyline in case of a multi-polyline, but can be overridden by passing
7984         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7985         addLatLng: function (latlng, latlngs) {
7986                 latlngs = latlngs || this._defaultShape();
7987                 latlng = toLatLng(latlng);
7988                 latlngs.push(latlng);
7989                 this._bounds.extend(latlng);
7990                 return this.redraw();
7991         },
7992
7993         _setLatLngs: function (latlngs) {
7994                 this._bounds = new LatLngBounds();
7995                 this._latlngs = this._convertLatLngs(latlngs);
7996         },
7997
7998         _defaultShape: function () {
7999                 return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
8000         },
8001
8002         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8003         _convertLatLngs: function (latlngs) {
8004                 var result = [],
8005                     flat = isFlat(latlngs);
8006
8007                 for (var i = 0, len = latlngs.length; i < len; i++) {
8008                         if (flat) {
8009                                 result[i] = toLatLng(latlngs[i]);
8010                                 this._bounds.extend(result[i]);
8011                         } else {
8012                                 result[i] = this._convertLatLngs(latlngs[i]);
8013                         }
8014                 }
8015
8016                 return result;
8017         },
8018
8019         _project: function () {
8020                 var pxBounds = new Bounds();
8021                 this._rings = [];
8022                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8023
8024                 var w = this._clickTolerance(),
8025                     p = new Point(w, w);
8026
8027                 if (this._bounds.isValid() && pxBounds.isValid()) {
8028                         pxBounds.min._subtract(p);
8029                         pxBounds.max._add(p);
8030                         this._pxBounds = pxBounds;
8031                 }
8032         },
8033
8034         // recursively turns latlngs into a set of rings with projected coordinates
8035         _projectLatlngs: function (latlngs, result, projectedBounds) {
8036                 var flat = latlngs[0] instanceof LatLng,
8037                     len = latlngs.length,
8038                     i, ring;
8039
8040                 if (flat) {
8041                         ring = [];
8042                         for (i = 0; i < len; i++) {
8043                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8044                                 projectedBounds.extend(ring[i]);
8045                         }
8046                         result.push(ring);
8047                 } else {
8048                         for (i = 0; i < len; i++) {
8049                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8050                         }
8051                 }
8052         },
8053
8054         // clip polyline by renderer bounds so that we have less to render for performance
8055         _clipPoints: function () {
8056                 var bounds = this._renderer._bounds;
8057
8058                 this._parts = [];
8059                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8060                         return;
8061                 }
8062
8063                 if (this.options.noClip) {
8064                         this._parts = this._rings;
8065                         return;
8066                 }
8067
8068                 var parts = this._parts,
8069                     i, j, k, len, len2, segment, points;
8070
8071                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8072                         points = this._rings[i];
8073
8074                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8075                                 segment = clipSegment(points[j], points[j + 1], bounds, j, true);
8076
8077                                 if (!segment) { continue; }
8078
8079                                 parts[k] = parts[k] || [];
8080                                 parts[k].push(segment[0]);
8081
8082                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8083                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8084                                         parts[k].push(segment[1]);
8085                                         k++;
8086                                 }
8087                         }
8088                 }
8089         },
8090
8091         // simplify each clipped part of the polyline for performance
8092         _simplifyPoints: function () {
8093                 var parts = this._parts,
8094                     tolerance = this.options.smoothFactor;
8095
8096                 for (var i = 0, len = parts.length; i < len; i++) {
8097                         parts[i] = simplify(parts[i], tolerance);
8098                 }
8099         },
8100
8101         _update: function () {
8102                 if (!this._map) { return; }
8103
8104                 this._clipPoints();
8105                 this._simplifyPoints();
8106                 this._updatePath();
8107         },
8108
8109         _updatePath: function () {
8110                 this._renderer._updatePoly(this);
8111         },
8112
8113         // Needed by the `Canvas` renderer for interactivity
8114         _containsPoint: function (p, closed) {
8115                 var i, j, k, len, len2, part,
8116                     w = this._clickTolerance();
8117
8118                 if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }
8119
8120                 // hit detection for polylines
8121                 for (i = 0, len = this._parts.length; i < len; i++) {
8122                         part = this._parts[i];
8123
8124                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8125                                 if (!closed && (j === 0)) { continue; }
8126
8127                                 if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
8128                                         return true;
8129                                 }
8130                         }
8131                 }
8132                 return false;
8133         }
8134 });
8135
8136 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8137 // Instantiates a polyline object given an array of geographical points and
8138 // optionally an options object. You can create a `Polyline` object with
8139 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8140 // of geographic points.
8141 function polyline(latlngs, options) {
8142         return new Polyline(latlngs, options);
8143 }
8144
8145 // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
8146 Polyline._flat = _flat;
8147
8148 /*
8149  * @class Polygon
8150  * @aka L.Polygon
8151  * @inherits Polyline
8152  *
8153  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8154  *
8155  * 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.
8156  *
8157  *
8158  * @example
8159  *
8160  * ```js
8161  * // create a red polygon from an array of LatLng points
8162  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
8163  *
8164  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8165  *
8166  * // zoom the map to the polygon
8167  * map.fitBounds(polygon.getBounds());
8168  * ```
8169  *
8170  * 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:
8171  *
8172  * ```js
8173  * var latlngs = [
8174  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8175  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8176  * ];
8177  * ```
8178  *
8179  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8180  *
8181  * ```js
8182  * var latlngs = [
8183  *   [ // first polygon
8184  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
8185  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
8186  *   ],
8187  *   [ // second polygon
8188  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
8189  *   ]
8190  * ];
8191  * ```
8192  */
8193
8194 var Polygon = Polyline.extend({
8195
8196         options: {
8197                 fill: true
8198         },
8199
8200         isEmpty: function () {
8201                 return !this._latlngs.length || !this._latlngs[0].length;
8202         },
8203
8204         getCenter: function () {
8205                 // throws error when not yet added to map as this center calculation requires projected coordinates
8206                 if (!this._map) {
8207                         throw new Error('Must add layer to map before using getCenter()');
8208                 }
8209
8210                 var i, j, p1, p2, f, area, x, y, center,
8211                     points = this._rings[0],
8212                     len = points.length;
8213
8214                 if (!len) { return null; }
8215
8216                 // polygon centroid algorithm; only uses the first ring if there are multiple
8217
8218                 area = x = y = 0;
8219
8220                 for (i = 0, j = len - 1; i < len; j = i++) {
8221                         p1 = points[i];
8222                         p2 = points[j];
8223
8224                         f = p1.y * p2.x - p2.y * p1.x;
8225                         x += (p1.x + p2.x) * f;
8226                         y += (p1.y + p2.y) * f;
8227                         area += f * 3;
8228                 }
8229
8230                 if (area === 0) {
8231                         // Polygon is so small that all points are on same pixel.
8232                         center = points[0];
8233                 } else {
8234                         center = [x / area, y / area];
8235                 }
8236                 return this._map.layerPointToLatLng(center);
8237         },
8238
8239         _convertLatLngs: function (latlngs) {
8240                 var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
8241                     len = result.length;
8242
8243                 // remove last point if it equals first one
8244                 if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
8245                         result.pop();
8246                 }
8247                 return result;
8248         },
8249
8250         _setLatLngs: function (latlngs) {
8251                 Polyline.prototype._setLatLngs.call(this, latlngs);
8252                 if (isFlat(this._latlngs)) {
8253                         this._latlngs = [this._latlngs];
8254                 }
8255         },
8256
8257         _defaultShape: function () {
8258                 return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8259         },
8260
8261         _clipPoints: function () {
8262                 // polygons need a different clipping algorithm so we redefine that
8263
8264                 var bounds = this._renderer._bounds,
8265                     w = this.options.weight,
8266                     p = new Point(w, w);
8267
8268                 // increase clip padding by stroke width to avoid stroke on clip edges
8269                 bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));
8270
8271                 this._parts = [];
8272                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8273                         return;
8274                 }
8275
8276                 if (this.options.noClip) {
8277                         this._parts = this._rings;
8278                         return;
8279                 }
8280
8281                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8282                         clipped = clipPolygon(this._rings[i], bounds, true);
8283                         if (clipped.length) {
8284                                 this._parts.push(clipped);
8285                         }
8286                 }
8287         },
8288
8289         _updatePath: function () {
8290                 this._renderer._updatePoly(this, true);
8291         },
8292
8293         // Needed by the `Canvas` renderer for interactivity
8294         _containsPoint: function (p) {
8295                 var inside = false,
8296                     part, p1, p2, i, j, k, len, len2;
8297
8298                 if (!this._pxBounds.contains(p)) { return false; }
8299
8300                 // ray casting algorithm for detecting if point is in polygon
8301                 for (i = 0, len = this._parts.length; i < len; i++) {
8302                         part = this._parts[i];
8303
8304                         for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
8305                                 p1 = part[j];
8306                                 p2 = part[k];
8307
8308                                 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)) {
8309                                         inside = !inside;
8310                                 }
8311                         }
8312                 }
8313
8314                 // also check if it's on polygon stroke
8315                 return inside || Polyline.prototype._containsPoint.call(this, p, true);
8316         }
8317
8318 });
8319
8320
8321 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8322 function polygon(latlngs, options) {
8323         return new Polygon(latlngs, options);
8324 }
8325
8326 /*
8327  * @class GeoJSON
8328  * @aka L.GeoJSON
8329  * @inherits FeatureGroup
8330  *
8331  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
8332  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
8333  *
8334  * @example
8335  *
8336  * ```js
8337  * L.geoJSON(data, {
8338  *      style: function (feature) {
8339  *              return {color: feature.properties.color};
8340  *      }
8341  * }).bindPopup(function (layer) {
8342  *      return layer.feature.properties.description;
8343  * }).addTo(map);
8344  * ```
8345  */
8346
8347 var GeoJSON = FeatureGroup.extend({
8348
8349         /* @section
8350          * @aka GeoJSON options
8351          *
8352          * @option pointToLayer: Function = *
8353          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
8354          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
8355          * The default is to spawn a default `Marker`:
8356          * ```js
8357          * function(geoJsonPoint, latlng) {
8358          *      return L.marker(latlng);
8359          * }
8360          * ```
8361          *
8362          * @option style: Function = *
8363          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
8364          * called internally when data is added.
8365          * The default value is to not override any defaults:
8366          * ```js
8367          * function (geoJsonFeature) {
8368          *      return {}
8369          * }
8370          * ```
8371          *
8372          * @option onEachFeature: Function = *
8373          * A `Function` that will be called once for each created `Feature`, after it has
8374          * been created and styled. Useful for attaching events and popups to features.
8375          * The default is to do nothing with the newly created layers:
8376          * ```js
8377          * function (feature, layer) {}
8378          * ```
8379          *
8380          * @option filter: Function = *
8381          * A `Function` that will be used to decide whether to include a feature or not.
8382          * The default is to include all features:
8383          * ```js
8384          * function (geoJsonFeature) {
8385          *      return true;
8386          * }
8387          * ```
8388          * Note: dynamically changing the `filter` option will have effect only on newly
8389          * added data. It will _not_ re-evaluate already included features.
8390          *
8391          * @option coordsToLatLng: Function = *
8392          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
8393          * The default is the `coordsToLatLng` static method.
8394          */
8395
8396         initialize: function (geojson, options) {
8397                 setOptions(this, options);
8398
8399                 this._layers = {};
8400
8401                 if (geojson) {
8402                         this.addData(geojson);
8403                 }
8404         },
8405
8406         // @method addData( <GeoJSON> data ): this
8407         // Adds a GeoJSON object to the layer.
8408         addData: function (geojson) {
8409                 var features = isArray(geojson) ? geojson : geojson.features,
8410                     i, len, feature;
8411
8412                 if (features) {
8413                         for (i = 0, len = features.length; i < len; i++) {
8414                                 // only add this if geometry or geometries are set and not null
8415                                 feature = features[i];
8416                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
8417                                         this.addData(feature);
8418                                 }
8419                         }
8420                         return this;
8421                 }
8422
8423                 var options = this.options;
8424
8425                 if (options.filter && !options.filter(geojson)) { return this; }
8426
8427                 var layer = geometryToLayer(geojson, options);
8428                 if (!layer) {
8429                         return this;
8430                 }
8431                 layer.feature = asFeature(geojson);
8432
8433                 layer.defaultOptions = layer.options;
8434                 this.resetStyle(layer);
8435
8436                 if (options.onEachFeature) {
8437                         options.onEachFeature(geojson, layer);
8438                 }
8439
8440                 return this.addLayer(layer);
8441         },
8442
8443         // @method resetStyle( <Path> layer ): this
8444         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
8445         resetStyle: function (layer) {
8446                 // reset any custom styles
8447                 layer.options = extend({}, layer.defaultOptions);
8448                 this._setLayerStyle(layer, this.options.style);
8449                 return this;
8450         },
8451
8452         // @method setStyle( <Function> style ): this
8453         // Changes styles of GeoJSON vector layers with the given style function.
8454         setStyle: function (style) {
8455                 return this.eachLayer(function (layer) {
8456                         this._setLayerStyle(layer, style);
8457                 }, this);
8458         },
8459
8460         _setLayerStyle: function (layer, style) {
8461                 if (typeof style === 'function') {
8462                         style = style(layer.feature);
8463                 }
8464                 if (layer.setStyle) {
8465                         layer.setStyle(style);
8466                 }
8467         }
8468 });
8469
8470 // @section
8471 // There are several static functions which can be called without instantiating L.GeoJSON:
8472
8473 // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
8474 // Creates a `Layer` from a given GeoJSON feature. Can use a custom
8475 // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
8476 // functions if provided as options.
8477 function geometryToLayer(geojson, options) {
8478
8479         var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
8480             coords = geometry ? geometry.coordinates : null,
8481             layers = [],
8482             pointToLayer = options && options.pointToLayer,
8483             _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
8484             latlng, latlngs, i, len;
8485
8486         if (!coords && !geometry) {
8487                 return null;
8488         }
8489
8490         switch (geometry.type) {
8491         case 'Point':
8492                 latlng = _coordsToLatLng(coords);
8493                 return pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng);
8494
8495         case 'MultiPoint':
8496                 for (i = 0, len = coords.length; i < len; i++) {
8497                         latlng = _coordsToLatLng(coords[i]);
8498                         layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new Marker(latlng));
8499                 }
8500                 return new FeatureGroup(layers);
8501
8502         case 'LineString':
8503         case 'MultiLineString':
8504                 latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
8505                 return new Polyline(latlngs, options);
8506
8507         case 'Polygon':
8508         case 'MultiPolygon':
8509                 latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
8510                 return new Polygon(latlngs, options);
8511
8512         case 'GeometryCollection':
8513                 for (i = 0, len = geometry.geometries.length; i < len; i++) {
8514                         var layer = geometryToLayer({
8515                                 geometry: geometry.geometries[i],
8516                                 type: 'Feature',
8517                                 properties: geojson.properties
8518                         }, options);
8519
8520                         if (layer) {
8521                                 layers.push(layer);
8522                         }
8523                 }
8524                 return new FeatureGroup(layers);
8525
8526         default:
8527                 throw new Error('Invalid GeoJSON object.');
8528         }
8529 }
8530
8531 // @function coordsToLatLng(coords: Array): LatLng
8532 // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
8533 // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
8534 function coordsToLatLng(coords) {
8535         return new LatLng(coords[1], coords[0], coords[2]);
8536 }
8537
8538 // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
8539 // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
8540 // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
8541 // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
8542 function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
8543         var latlngs = [];
8544
8545         for (var i = 0, len = coords.length, latlng; i < len; i++) {
8546                 latlng = levelsDeep ?
8547                                 coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
8548                                 (_coordsToLatLng || coordsToLatLng)(coords[i]);
8549
8550                 latlngs.push(latlng);
8551         }
8552
8553         return latlngs;
8554 }
8555
8556 // @function latLngToCoords(latlng: LatLng, precision?: Number): Array
8557 // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
8558 function latLngToCoords(latlng, precision) {
8559         precision = typeof precision === 'number' ? precision : 6;
8560         return latlng.alt !== undefined ?
8561                         [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
8562                         [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
8563 }
8564
8565 // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
8566 // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
8567 // `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.
8568 function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
8569         var coords = [];
8570
8571         for (var i = 0, len = latlngs.length; i < len; i++) {
8572                 coords.push(levelsDeep ?
8573                         latLngsToCoords(latlngs[i], levelsDeep - 1, closed, precision) :
8574                         latLngToCoords(latlngs[i], precision));
8575         }
8576
8577         if (!levelsDeep && closed) {
8578                 coords.push(coords[0]);
8579         }
8580
8581         return coords;
8582 }
8583
8584 function getFeature(layer, newGeometry) {
8585         return layer.feature ?
8586                         extend({}, layer.feature, {geometry: newGeometry}) :
8587                         asFeature(newGeometry);
8588 }
8589
8590 // @function asFeature(geojson: Object): Object
8591 // Normalize GeoJSON geometries/features into GeoJSON features.
8592 function asFeature(geojson) {
8593         if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
8594                 return geojson;
8595         }
8596
8597         return {
8598                 type: 'Feature',
8599                 properties: {},
8600                 geometry: geojson
8601         };
8602 }
8603
8604 var PointToGeoJSON = {
8605         toGeoJSON: function (precision) {
8606                 return getFeature(this, {
8607                         type: 'Point',
8608                         coordinates: latLngToCoords(this.getLatLng(), precision)
8609                 });
8610         }
8611 };
8612
8613 // @namespace Marker
8614 // @method toGeoJSON(): Object
8615 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
8616 Marker.include(PointToGeoJSON);
8617
8618 // @namespace CircleMarker
8619 // @method toGeoJSON(): Object
8620 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
8621 Circle.include(PointToGeoJSON);
8622 CircleMarker.include(PointToGeoJSON);
8623
8624
8625 // @namespace Polyline
8626 // @method toGeoJSON(): Object
8627 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
8628 Polyline.include({
8629         toGeoJSON: function (precision) {
8630                 var multi = !isFlat(this._latlngs);
8631
8632                 var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);
8633
8634                 return getFeature(this, {
8635                         type: (multi ? 'Multi' : '') + 'LineString',
8636                         coordinates: coords
8637                 });
8638         }
8639 });
8640
8641 // @namespace Polygon
8642 // @method toGeoJSON(): Object
8643 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
8644 Polygon.include({
8645         toGeoJSON: function (precision) {
8646                 var holes = !isFlat(this._latlngs),
8647                     multi = holes && !isFlat(this._latlngs[0]);
8648
8649                 var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);
8650
8651                 if (!holes) {
8652                         coords = [coords];
8653                 }
8654
8655                 return getFeature(this, {
8656                         type: (multi ? 'Multi' : '') + 'Polygon',
8657                         coordinates: coords
8658                 });
8659         }
8660 });
8661
8662
8663 // @namespace LayerGroup
8664 LayerGroup.include({
8665         toMultiPoint: function (precision) {
8666                 var coords = [];
8667
8668                 this.eachLayer(function (layer) {
8669                         coords.push(layer.toGeoJSON(precision).geometry.coordinates);
8670                 });
8671
8672                 return getFeature(this, {
8673                         type: 'MultiPoint',
8674                         coordinates: coords
8675                 });
8676         },
8677
8678         // @method toGeoJSON(): Object
8679         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
8680         toGeoJSON: function (precision) {
8681
8682                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
8683
8684                 if (type === 'MultiPoint') {
8685                         return this.toMultiPoint(precision);
8686                 }
8687
8688                 var isGeometryCollection = type === 'GeometryCollection',
8689                     jsons = [];
8690
8691                 this.eachLayer(function (layer) {
8692                         if (layer.toGeoJSON) {
8693                                 var json = layer.toGeoJSON(precision);
8694                                 if (isGeometryCollection) {
8695                                         jsons.push(json.geometry);
8696                                 } else {
8697                                         var feature = asFeature(json);
8698                                         // Squash nested feature collections
8699                                         if (feature.type === 'FeatureCollection') {
8700                                                 jsons.push.apply(jsons, feature.features);
8701                                         } else {
8702                                                 jsons.push(feature);
8703                                         }
8704                                 }
8705                         }
8706                 });
8707
8708                 if (isGeometryCollection) {
8709                         return getFeature(this, {
8710                                 geometries: jsons,
8711                                 type: 'GeometryCollection'
8712                         });
8713                 }
8714
8715                 return {
8716                         type: 'FeatureCollection',
8717                         features: jsons
8718                 };
8719         }
8720 });
8721
8722 // @namespace GeoJSON
8723 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
8724 // Creates a GeoJSON layer. Optionally accepts an object in
8725 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
8726 // (you can alternatively add it later with `addData` method) and an `options` object.
8727 function geoJSON(geojson, options) {
8728         return new GeoJSON(geojson, options);
8729 }
8730
8731 // Backward compatibility.
8732 var geoJson = geoJSON;
8733
8734 /*
8735  * @class ImageOverlay
8736  * @aka L.ImageOverlay
8737  * @inherits Interactive layer
8738  *
8739  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
8740  *
8741  * @example
8742  *
8743  * ```js
8744  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
8745  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
8746  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
8747  * ```
8748  */
8749
8750 var ImageOverlay = Layer.extend({
8751
8752         // @section
8753         // @aka ImageOverlay options
8754         options: {
8755                 // @option opacity: Number = 1.0
8756                 // The opacity of the image overlay.
8757                 opacity: 1,
8758
8759                 // @option alt: String = ''
8760                 // Text for the `alt` attribute of the image (useful for accessibility).
8761                 alt: '',
8762
8763                 // @option interactive: Boolean = false
8764                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
8765                 interactive: false,
8766
8767                 // @option crossOrigin: Boolean = false
8768                 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
8769                 crossOrigin: false,
8770
8771                 // @option errorOverlayUrl: String = ''
8772                 // URL to the overlay image to show in place of the overlay that failed to load.
8773                 errorOverlayUrl: '',
8774
8775                 // @option zIndex: Number = 1
8776                 // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the tile layer.
8777                 zIndex: 1,
8778
8779                 // @option className: String = ''
8780                 // A custom class name to assign to the image. Empty by default.
8781                 className: '',
8782         },
8783
8784         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
8785                 this._url = url;
8786                 this._bounds = toLatLngBounds(bounds);
8787
8788                 setOptions(this, options);
8789         },
8790
8791         onAdd: function () {
8792                 if (!this._image) {
8793                         this._initImage();
8794
8795                         if (this.options.opacity < 1) {
8796                                 this._updateOpacity();
8797                         }
8798                 }
8799
8800                 if (this.options.interactive) {
8801                         addClass(this._image, 'leaflet-interactive');
8802                         this.addInteractiveTarget(this._image);
8803                 }
8804
8805                 this.getPane().appendChild(this._image);
8806                 this._reset();
8807         },
8808
8809         onRemove: function () {
8810                 remove(this._image);
8811                 if (this.options.interactive) {
8812                         this.removeInteractiveTarget(this._image);
8813                 }
8814         },
8815
8816         // @method setOpacity(opacity: Number): this
8817         // Sets the opacity of the overlay.
8818         setOpacity: function (opacity) {
8819                 this.options.opacity = opacity;
8820
8821                 if (this._image) {
8822                         this._updateOpacity();
8823                 }
8824                 return this;
8825         },
8826
8827         setStyle: function (styleOpts) {
8828                 if (styleOpts.opacity) {
8829                         this.setOpacity(styleOpts.opacity);
8830                 }
8831                 return this;
8832         },
8833
8834         // @method bringToFront(): this
8835         // Brings the layer to the top of all overlays.
8836         bringToFront: function () {
8837                 if (this._map) {
8838                         toFront(this._image);
8839                 }
8840                 return this;
8841         },
8842
8843         // @method bringToBack(): this
8844         // Brings the layer to the bottom of all overlays.
8845         bringToBack: function () {
8846                 if (this._map) {
8847                         toBack(this._image);
8848                 }
8849                 return this;
8850         },
8851
8852         // @method setUrl(url: String): this
8853         // Changes the URL of the image.
8854         setUrl: function (url) {
8855                 this._url = url;
8856
8857                 if (this._image) {
8858                         this._image.src = url;
8859                 }
8860                 return this;
8861         },
8862
8863         // @method setBounds(bounds: LatLngBounds): this
8864         // Update the bounds that this ImageOverlay covers
8865         setBounds: function (bounds) {
8866                 this._bounds = toLatLngBounds(bounds);
8867
8868                 if (this._map) {
8869                         this._reset();
8870                 }
8871                 return this;
8872         },
8873
8874         getEvents: function () {
8875                 var events = {
8876                         zoom: this._reset,
8877                         viewreset: this._reset
8878                 };
8879
8880                 if (this._zoomAnimated) {
8881                         events.zoomanim = this._animateZoom;
8882                 }
8883
8884                 return events;
8885         },
8886
8887         // @method: setZIndex(value: Number) : this
8888         // Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
8889         setZIndex: function (value) {
8890                 this.options.zIndex = value;
8891                 this._updateZIndex();
8892                 return this;
8893         },
8894
8895         // @method getBounds(): LatLngBounds
8896         // Get the bounds that this ImageOverlay covers
8897         getBounds: function () {
8898                 return this._bounds;
8899         },
8900
8901         // @method getElement(): HTMLElement
8902         // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
8903         // used by this overlay.
8904         getElement: function () {
8905                 return this._image;
8906         },
8907
8908         _initImage: function () {
8909                 var img = this._image = create$1('img',
8910                                 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '') +
8911                                  (this.options.className || ''));
8912
8913                 img.onselectstart = falseFn;
8914                 img.onmousemove = falseFn;
8915
8916                 // @event load: Event
8917                 // Fired when the ImageOverlay layer has loaded its image
8918                 img.onload = bind(this.fire, this, 'load');
8919                 img.onerror = bind(this._overlayOnError, this, 'error');
8920
8921                 if (this.options.crossOrigin) {
8922                         img.crossOrigin = '';
8923                 }
8924
8925                 if (this.options.zIndex) {
8926                         this._updateZIndex();
8927                 }
8928
8929                 img.src = this._url;
8930                 img.alt = this.options.alt;
8931         },
8932
8933         _animateZoom: function (e) {
8934                 var scale = this._map.getZoomScale(e.zoom),
8935                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
8936
8937                 setTransform(this._image, offset, scale);
8938         },
8939
8940         _reset: function () {
8941                 var image = this._image,
8942                     bounds = new Bounds(
8943                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
8944                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
8945                     size = bounds.getSize();
8946
8947                 setPosition(image, bounds.min);
8948
8949                 image.style.width  = size.x + 'px';
8950                 image.style.height = size.y + 'px';
8951         },
8952
8953         _updateOpacity: function () {
8954                 setOpacity(this._image, this.options.opacity);
8955         },
8956
8957         _updateZIndex: function () {
8958                 if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
8959                         this._image.style.zIndex = this.options.zIndex;
8960                 }
8961         },
8962
8963         _overlayOnError: function () {
8964                 // @event error: Event
8965                 // Fired when the ImageOverlay layer has loaded its image
8966                 this.fire('error');
8967
8968                 var errorUrl = this.options.errorOverlayUrl;
8969                 if (errorUrl && this._url !== errorUrl) {
8970                         this._url = errorUrl;
8971                         this._image.src = errorUrl;
8972                 }
8973         }
8974 });
8975
8976 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
8977 // Instantiates an image overlay object given the URL of the image and the
8978 // geographical bounds it is tied to.
8979 var imageOverlay = function (url, bounds, options) {
8980         return new ImageOverlay(url, bounds, options);
8981 };
8982
8983 /*
8984  * @class VideoOverlay
8985  * @aka L.VideoOverlay
8986  * @inherits ImageOverlay
8987  *
8988  * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
8989  *
8990  * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
8991  * HTML5 element.
8992  *
8993  * @example
8994  *
8995  * ```js
8996  * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
8997  *      videoBounds = [[ 32, -130], [ 13, -100]];
8998  * L.VideoOverlay(videoUrl, videoBounds ).addTo(map);
8999  * ```
9000  */
9001
9002 var VideoOverlay = ImageOverlay.extend({
9003
9004         // @section
9005         // @aka VideoOverlay options
9006         options: {
9007                 // @option autoplay: Boolean = true
9008                 // Whether the video starts playing automatically when loaded.
9009                 autoplay: true,
9010
9011                 // @option loop: Boolean = true
9012                 // Whether the video will loop back to the beginning when played.
9013                 loop: true
9014         },
9015
9016         _initImage: function () {
9017                 var wasElementSupplied = this._url.tagName === 'VIDEO';
9018                 var vid = this._image = wasElementSupplied ? this._url : create$1('video');
9019
9020                 vid.class = vid.class || '';
9021                 vid.class += 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : '');
9022
9023                 vid.onselectstart = falseFn;
9024                 vid.onmousemove = falseFn;
9025
9026                 // @event load: Event
9027                 // Fired when the video has finished loading the first frame
9028                 vid.onloadeddata = bind(this.fire, this, 'load');
9029
9030                 if (wasElementSupplied) { return; }
9031
9032                 if (!isArray(this._url)) { this._url = [this._url]; }
9033
9034                 vid.autoplay = !!this.options.autoplay;
9035                 vid.loop = !!this.options.loop;
9036                 for (var i = 0; i < this._url.length; i++) {
9037                         var source = create$1('source');
9038                         source.src = this._url[i];
9039                         vid.appendChild(source);
9040                 }
9041         }
9042
9043         // @method getElement(): HTMLVideoElement
9044         // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
9045         // used by this overlay.
9046 });
9047
9048
9049 // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
9050 // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
9051 // geographical bounds it is tied to.
9052
9053 function videoOverlay(video, bounds, options) {
9054         return new VideoOverlay(video, bounds, options);
9055 }
9056
9057 /*
9058  * @class DivOverlay
9059  * @inherits Layer
9060  * @aka L.DivOverlay
9061  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
9062  */
9063
9064 // @namespace DivOverlay
9065 var DivOverlay = Layer.extend({
9066
9067         // @section
9068         // @aka DivOverlay options
9069         options: {
9070                 // @option offset: Point = Point(0, 7)
9071                 // The offset of the popup position. Useful to control the anchor
9072                 // of the popup when opening it on some overlays.
9073                 offset: [0, 7],
9074
9075                 // @option className: String = ''
9076                 // A custom CSS class name to assign to the popup.
9077                 className: '',
9078
9079                 // @option pane: String = 'popupPane'
9080                 // `Map pane` where the popup will be added.
9081                 pane: 'popupPane'
9082         },
9083
9084         initialize: function (options, source) {
9085                 setOptions(this, options);
9086
9087                 this._source = source;
9088         },
9089
9090         onAdd: function (map) {
9091                 this._zoomAnimated = map._zoomAnimated;
9092
9093                 if (!this._container) {
9094                         this._initLayout();
9095                 }
9096
9097                 if (map._fadeAnimated) {
9098                         setOpacity(this._container, 0);
9099                 }
9100
9101                 clearTimeout(this._removeTimeout);
9102                 this.getPane().appendChild(this._container);
9103                 this.update();
9104
9105                 if (map._fadeAnimated) {
9106                         setOpacity(this._container, 1);
9107                 }
9108
9109                 this.bringToFront();
9110         },
9111
9112         onRemove: function (map) {
9113                 if (map._fadeAnimated) {
9114                         setOpacity(this._container, 0);
9115                         this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
9116                 } else {
9117                         remove(this._container);
9118                 }
9119         },
9120
9121         // @namespace Popup
9122         // @method getLatLng: LatLng
9123         // Returns the geographical point of popup.
9124         getLatLng: function () {
9125                 return this._latlng;
9126         },
9127
9128         // @method setLatLng(latlng: LatLng): this
9129         // Sets the geographical point where the popup will open.
9130         setLatLng: function (latlng) {
9131                 this._latlng = toLatLng(latlng);
9132                 if (this._map) {
9133                         this._updatePosition();
9134                         this._adjustPan();
9135                 }
9136                 return this;
9137         },
9138
9139         // @method getContent: String|HTMLElement
9140         // Returns the content of the popup.
9141         getContent: function () {
9142                 return this._content;
9143         },
9144
9145         // @method setContent(htmlContent: String|HTMLElement|Function): this
9146         // 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.
9147         setContent: function (content) {
9148                 this._content = content;
9149                 this.update();
9150                 return this;
9151         },
9152
9153         // @method getElement: String|HTMLElement
9154         // Alias for [getContent()](#popup-getcontent)
9155         getElement: function () {
9156                 return this._container;
9157         },
9158
9159         // @method update: null
9160         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
9161         update: function () {
9162                 if (!this._map) { return; }
9163
9164                 this._container.style.visibility = 'hidden';
9165
9166                 this._updateContent();
9167                 this._updateLayout();
9168                 this._updatePosition();
9169
9170                 this._container.style.visibility = '';
9171
9172                 this._adjustPan();
9173         },
9174
9175         getEvents: function () {
9176                 var events = {
9177                         zoom: this._updatePosition,
9178                         viewreset: this._updatePosition
9179                 };
9180
9181                 if (this._zoomAnimated) {
9182                         events.zoomanim = this._animateZoom;
9183                 }
9184                 return events;
9185         },
9186
9187         // @method isOpen: Boolean
9188         // Returns `true` when the popup is visible on the map.
9189         isOpen: function () {
9190                 return !!this._map && this._map.hasLayer(this);
9191         },
9192
9193         // @method bringToFront: this
9194         // Brings this popup in front of other popups (in the same map pane).
9195         bringToFront: function () {
9196                 if (this._map) {
9197                         toFront(this._container);
9198                 }
9199                 return this;
9200         },
9201
9202         // @method bringToBack: this
9203         // Brings this popup to the back of other popups (in the same map pane).
9204         bringToBack: function () {
9205                 if (this._map) {
9206                         toBack(this._container);
9207                 }
9208                 return this;
9209         },
9210
9211         _updateContent: function () {
9212                 if (!this._content) { return; }
9213
9214                 var node = this._contentNode;
9215                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
9216
9217                 if (typeof content === 'string') {
9218                         node.innerHTML = content;
9219                 } else {
9220                         while (node.hasChildNodes()) {
9221                                 node.removeChild(node.firstChild);
9222                         }
9223                         node.appendChild(content);
9224                 }
9225                 this.fire('contentupdate');
9226         },
9227
9228         _updatePosition: function () {
9229                 if (!this._map) { return; }
9230
9231                 var pos = this._map.latLngToLayerPoint(this._latlng),
9232                     offset = toPoint(this.options.offset),
9233                     anchor = this._getAnchor();
9234
9235                 if (this._zoomAnimated) {
9236                         setPosition(this._container, pos.add(anchor));
9237                 } else {
9238                         offset = offset.add(pos).add(anchor);
9239                 }
9240
9241                 var bottom = this._containerBottom = -offset.y,
9242                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
9243
9244                 // bottom position the popup in case the height of the popup changes (images loading etc)
9245                 this._container.style.bottom = bottom + 'px';
9246                 this._container.style.left = left + 'px';
9247         },
9248
9249         _getAnchor: function () {
9250                 return [0, 0];
9251         }
9252
9253 });
9254
9255 /*
9256  * @class Popup
9257  * @inherits DivOverlay
9258  * @aka L.Popup
9259  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
9260  * open popups while making sure that only one popup is open at one time
9261  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
9262  *
9263  * @example
9264  *
9265  * If you want to just bind a popup to marker click and then open it, it's really easy:
9266  *
9267  * ```js
9268  * marker.bindPopup(popupContent).openPopup();
9269  * ```
9270  * Path overlays like polylines also have a `bindPopup` method.
9271  * Here's a more complicated way to open a popup on a map:
9272  *
9273  * ```js
9274  * var popup = L.popup()
9275  *      .setLatLng(latlng)
9276  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
9277  *      .openOn(map);
9278  * ```
9279  */
9280
9281
9282 // @namespace Popup
9283 var Popup = DivOverlay.extend({
9284
9285         // @section
9286         // @aka Popup options
9287         options: {
9288                 // @option maxWidth: Number = 300
9289                 // Max width of the popup, in pixels.
9290                 maxWidth: 300,
9291
9292                 // @option minWidth: Number = 50
9293                 // Min width of the popup, in pixels.
9294                 minWidth: 50,
9295
9296                 // @option maxHeight: Number = null
9297                 // If set, creates a scrollable container of the given height
9298                 // inside a popup if its content exceeds it.
9299                 maxHeight: null,
9300
9301                 // @option autoPan: Boolean = true
9302                 // Set it to `false` if you don't want the map to do panning animation
9303                 // to fit the opened popup.
9304                 autoPan: true,
9305
9306                 // @option autoPanPaddingTopLeft: Point = null
9307                 // The margin between the popup and the top left corner of the map
9308                 // view after autopanning was performed.
9309                 autoPanPaddingTopLeft: null,
9310
9311                 // @option autoPanPaddingBottomRight: Point = null
9312                 // The margin between the popup and the bottom right corner of the map
9313                 // view after autopanning was performed.
9314                 autoPanPaddingBottomRight: null,
9315
9316                 // @option autoPanPadding: Point = Point(5, 5)
9317                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
9318                 autoPanPadding: [5, 5],
9319
9320                 // @option keepInView: Boolean = false
9321                 // Set it to `true` if you want to prevent users from panning the popup
9322                 // off of the screen while it is open.
9323                 keepInView: false,
9324
9325                 // @option closeButton: Boolean = true
9326                 // Controls the presence of a close button in the popup.
9327                 closeButton: true,
9328
9329                 // @option autoClose: Boolean = true
9330                 // Set it to `false` if you want to override the default behavior of
9331                 // the popup closing when another popup is opened.
9332                 autoClose: true,
9333
9334                 // @option closeOnClick: Boolean = *
9335                 // Set it if you want to override the default behavior of the popup closing when user clicks
9336                 // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.
9337
9338                 // @option className: String = ''
9339                 // A custom CSS class name to assign to the popup.
9340                 className: ''
9341         },
9342
9343         // @namespace Popup
9344         // @method openOn(map: Map): this
9345         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
9346         openOn: function (map) {
9347                 map.openPopup(this);
9348                 return this;
9349         },
9350
9351         onAdd: function (map) {
9352                 DivOverlay.prototype.onAdd.call(this, map);
9353
9354                 // @namespace Map
9355                 // @section Popup events
9356                 // @event popupopen: PopupEvent
9357                 // Fired when a popup is opened in the map
9358                 map.fire('popupopen', {popup: this});
9359
9360                 if (this._source) {
9361                         // @namespace Layer
9362                         // @section Popup events
9363                         // @event popupopen: PopupEvent
9364                         // Fired when a popup bound to this layer is opened
9365                         this._source.fire('popupopen', {popup: this}, true);
9366                         // For non-path layers, we toggle the popup when clicking
9367                         // again the layer, so prevent the map to reopen it.
9368                         if (!(this._source instanceof Path)) {
9369                                 this._source.on('preclick', stopPropagation);
9370                         }
9371                 }
9372         },
9373
9374         onRemove: function (map) {
9375                 DivOverlay.prototype.onRemove.call(this, map);
9376
9377                 // @namespace Map
9378                 // @section Popup events
9379                 // @event popupclose: PopupEvent
9380                 // Fired when a popup in the map is closed
9381                 map.fire('popupclose', {popup: this});
9382
9383                 if (this._source) {
9384                         // @namespace Layer
9385                         // @section Popup events
9386                         // @event popupclose: PopupEvent
9387                         // Fired when a popup bound to this layer is closed
9388                         this._source.fire('popupclose', {popup: this}, true);
9389                         if (!(this._source instanceof Path)) {
9390                                 this._source.off('preclick', stopPropagation);
9391                         }
9392                 }
9393         },
9394
9395         getEvents: function () {
9396                 var events = DivOverlay.prototype.getEvents.call(this);
9397
9398                 if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
9399                         events.preclick = this._close;
9400                 }
9401
9402                 if (this.options.keepInView) {
9403                         events.moveend = this._adjustPan;
9404                 }
9405
9406                 return events;
9407         },
9408
9409         _close: function () {
9410                 if (this._map) {
9411                         this._map.closePopup(this);
9412                 }
9413         },
9414
9415         _initLayout: function () {
9416                 var prefix = 'leaflet-popup',
9417                     container = this._container = create$1('div',
9418                         prefix + ' ' + (this.options.className || '') +
9419                         ' leaflet-zoom-animated');
9420
9421                 var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
9422                 this._contentNode = create$1('div', prefix + '-content', wrapper);
9423
9424                 disableClickPropagation(wrapper);
9425                 disableScrollPropagation(this._contentNode);
9426                 on(wrapper, 'contextmenu', stopPropagation);
9427
9428                 this._tipContainer = create$1('div', prefix + '-tip-container', container);
9429                 this._tip = create$1('div', prefix + '-tip', this._tipContainer);
9430
9431                 if (this.options.closeButton) {
9432                         var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
9433                         closeButton.href = '#close';
9434                         closeButton.innerHTML = '&#215;';
9435
9436                         on(closeButton, 'click', this._onCloseButtonClick, this);
9437                 }
9438         },
9439
9440         _updateLayout: function () {
9441                 var container = this._contentNode,
9442                     style = container.style;
9443
9444                 style.width = '';
9445                 style.whiteSpace = 'nowrap';
9446
9447                 var width = container.offsetWidth;
9448                 width = Math.min(width, this.options.maxWidth);
9449                 width = Math.max(width, this.options.minWidth);
9450
9451                 style.width = (width + 1) + 'px';
9452                 style.whiteSpace = '';
9453
9454                 style.height = '';
9455
9456                 var height = container.offsetHeight,
9457                     maxHeight = this.options.maxHeight,
9458                     scrolledClass = 'leaflet-popup-scrolled';
9459
9460                 if (maxHeight && height > maxHeight) {
9461                         style.height = maxHeight + 'px';
9462                         addClass(container, scrolledClass);
9463                 } else {
9464                         removeClass(container, scrolledClass);
9465                 }
9466
9467                 this._containerWidth = this._container.offsetWidth;
9468         },
9469
9470         _animateZoom: function (e) {
9471                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
9472                     anchor = this._getAnchor();
9473                 setPosition(this._container, pos.add(anchor));
9474         },
9475
9476         _adjustPan: function () {
9477                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
9478
9479                 var map = this._map,
9480                     marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
9481                     containerHeight = this._container.offsetHeight + marginBottom,
9482                     containerWidth = this._containerWidth,
9483                     layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);
9484
9485                 layerPos._add(getPosition(this._container));
9486
9487                 var containerPos = map.layerPointToContainerPoint(layerPos),
9488                     padding = toPoint(this.options.autoPanPadding),
9489                     paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
9490                     paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
9491                     size = map.getSize(),
9492                     dx = 0,
9493                     dy = 0;
9494
9495                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
9496                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
9497                 }
9498                 if (containerPos.x - dx - paddingTL.x < 0) { // left
9499                         dx = containerPos.x - paddingTL.x;
9500                 }
9501                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
9502                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
9503                 }
9504                 if (containerPos.y - dy - paddingTL.y < 0) { // top
9505                         dy = containerPos.y - paddingTL.y;
9506                 }
9507
9508                 // @namespace Map
9509                 // @section Popup events
9510                 // @event autopanstart: Event
9511                 // Fired when the map starts autopanning when opening a popup.
9512                 if (dx || dy) {
9513                         map
9514                             .fire('autopanstart')
9515                             .panBy([dx, dy]);
9516                 }
9517         },
9518
9519         _onCloseButtonClick: function (e) {
9520                 this._close();
9521                 stop(e);
9522         },
9523
9524         _getAnchor: function () {
9525                 // Where should we anchor the popup on the source layer?
9526                 return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
9527         }
9528
9529 });
9530
9531 // @namespace Popup
9532 // @factory L.popup(options?: Popup options, source?: Layer)
9533 // 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.
9534 var popup = function (options, source) {
9535         return new Popup(options, source);
9536 };
9537
9538
9539 /* @namespace Map
9540  * @section Interaction Options
9541  * @option closePopupOnClick: Boolean = true
9542  * Set it to `false` if you don't want popups to close when user clicks the map.
9543  */
9544 Map.mergeOptions({
9545         closePopupOnClick: true
9546 });
9547
9548
9549 // @namespace Map
9550 // @section Methods for Layers and Controls
9551 Map.include({
9552         // @method openPopup(popup: Popup): this
9553         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
9554         // @alternative
9555         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
9556         // Creates a popup with the specified content and options and opens it in the given point on a map.
9557         openPopup: function (popup, latlng, options) {
9558                 if (!(popup instanceof Popup)) {
9559                         popup = new Popup(options).setContent(popup);
9560                 }
9561
9562                 if (latlng) {
9563                         popup.setLatLng(latlng);
9564                 }
9565
9566                 if (this.hasLayer(popup)) {
9567                         return this;
9568                 }
9569
9570                 if (this._popup && this._popup.options.autoClose) {
9571                         this.closePopup();
9572                 }
9573
9574                 this._popup = popup;
9575                 return this.addLayer(popup);
9576         },
9577
9578         // @method closePopup(popup?: Popup): this
9579         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
9580         closePopup: function (popup) {
9581                 if (!popup || popup === this._popup) {
9582                         popup = this._popup;
9583                         this._popup = null;
9584                 }
9585                 if (popup) {
9586                         this.removeLayer(popup);
9587                 }
9588                 return this;
9589         }
9590 });
9591
9592 /*
9593  * @namespace Layer
9594  * @section Popup methods example
9595  *
9596  * All layers share a set of methods convenient for binding popups to it.
9597  *
9598  * ```js
9599  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
9600  * layer.openPopup();
9601  * layer.closePopup();
9602  * ```
9603  *
9604  * 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.
9605  */
9606
9607 // @section Popup methods
9608 Layer.include({
9609
9610         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
9611         // Binds a popup to the layer with the passed `content` and sets up the
9612         // necessary event listeners. If a `Function` is passed it will receive
9613         // the layer as the first argument and should return a `String` or `HTMLElement`.
9614         bindPopup: function (content, options) {
9615
9616                 if (content instanceof Popup) {
9617                         setOptions(content, options);
9618                         this._popup = content;
9619                         content._source = this;
9620                 } else {
9621                         if (!this._popup || options) {
9622                                 this._popup = new Popup(options, this);
9623                         }
9624                         this._popup.setContent(content);
9625                 }
9626
9627                 if (!this._popupHandlersAdded) {
9628                         this.on({
9629                                 click: this._openPopup,
9630                                 keypress: this._onKeyPress,
9631                                 remove: this.closePopup,
9632                                 move: this._movePopup
9633                         });
9634                         this._popupHandlersAdded = true;
9635                 }
9636
9637                 return this;
9638         },
9639
9640         // @method unbindPopup(): this
9641         // Removes the popup previously bound with `bindPopup`.
9642         unbindPopup: function () {
9643                 if (this._popup) {
9644                         this.off({
9645                                 click: this._openPopup,
9646                                 keypress: this._onKeyPress,
9647                                 remove: this.closePopup,
9648                                 move: this._movePopup
9649                         });
9650                         this._popupHandlersAdded = false;
9651                         this._popup = null;
9652                 }
9653                 return this;
9654         },
9655
9656         // @method openPopup(latlng?: LatLng): this
9657         // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
9658         openPopup: function (layer, latlng) {
9659                 if (!(layer instanceof Layer)) {
9660                         latlng = layer;
9661                         layer = this;
9662                 }
9663
9664                 if (layer instanceof FeatureGroup) {
9665                         for (var id in this._layers) {
9666                                 layer = this._layers[id];
9667                                 break;
9668                         }
9669                 }
9670
9671                 if (!latlng) {
9672                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
9673                 }
9674
9675                 if (this._popup && this._map) {
9676                         // set popup source to this layer
9677                         this._popup._source = layer;
9678
9679                         // update the popup (content, layout, ect...)
9680                         this._popup.update();
9681
9682                         // open the popup on the map
9683                         this._map.openPopup(this._popup, latlng);
9684                 }
9685
9686                 return this;
9687         },
9688
9689         // @method closePopup(): this
9690         // Closes the popup bound to this layer if it is open.
9691         closePopup: function () {
9692                 if (this._popup) {
9693                         this._popup._close();
9694                 }
9695                 return this;
9696         },
9697
9698         // @method togglePopup(): this
9699         // Opens or closes the popup bound to this layer depending on its current state.
9700         togglePopup: function (target) {
9701                 if (this._popup) {
9702                         if (this._popup._map) {
9703                                 this.closePopup();
9704                         } else {
9705                                 this.openPopup(target);
9706                         }
9707                 }
9708                 return this;
9709         },
9710
9711         // @method isPopupOpen(): boolean
9712         // Returns `true` if the popup bound to this layer is currently open.
9713         isPopupOpen: function () {
9714                 return (this._popup ? this._popup.isOpen() : false);
9715         },
9716
9717         // @method setPopupContent(content: String|HTMLElement|Popup): this
9718         // Sets the content of the popup bound to this layer.
9719         setPopupContent: function (content) {
9720                 if (this._popup) {
9721                         this._popup.setContent(content);
9722                 }
9723                 return this;
9724         },
9725
9726         // @method getPopup(): Popup
9727         // Returns the popup bound to this layer.
9728         getPopup: function () {
9729                 return this._popup;
9730         },
9731
9732         _openPopup: function (e) {
9733                 var layer = e.layer || e.target;
9734
9735                 if (!this._popup) {
9736                         return;
9737                 }
9738
9739                 if (!this._map) {
9740                         return;
9741                 }
9742
9743                 // prevent map click
9744                 stop(e);
9745
9746                 // if this inherits from Path its a vector and we can just
9747                 // open the popup at the new location
9748                 if (layer instanceof Path) {
9749                         this.openPopup(e.layer || e.target, e.latlng);
9750                         return;
9751                 }
9752
9753                 // otherwise treat it like a marker and figure out
9754                 // if we should toggle it open/closed
9755                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
9756                         this.closePopup();
9757                 } else {
9758                         this.openPopup(layer, e.latlng);
9759                 }
9760         },
9761
9762         _movePopup: function (e) {
9763                 this._popup.setLatLng(e.latlng);
9764         },
9765
9766         _onKeyPress: function (e) {
9767                 if (e.originalEvent.keyCode === 13) {
9768                         this._openPopup(e);
9769                 }
9770         }
9771 });
9772
9773 /*
9774  * @class Tooltip
9775  * @inherits DivOverlay
9776  * @aka L.Tooltip
9777  * Used to display small texts on top of map layers.
9778  *
9779  * @example
9780  *
9781  * ```js
9782  * marker.bindTooltip("my tooltip text").openTooltip();
9783  * ```
9784  * Note about tooltip offset. Leaflet takes two options in consideration
9785  * for computing tooltip offseting:
9786  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
9787  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
9788  *   move it to the bottom. Negatives will move to the left and top.
9789  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
9790  *   should adapt this value if you use a custom icon.
9791  */
9792
9793
9794 // @namespace Tooltip
9795 var Tooltip = DivOverlay.extend({
9796
9797         // @section
9798         // @aka Tooltip options
9799         options: {
9800                 // @option pane: String = 'tooltipPane'
9801                 // `Map pane` where the tooltip will be added.
9802                 pane: 'tooltipPane',
9803
9804                 // @option offset: Point = Point(0, 0)
9805                 // Optional offset of the tooltip position.
9806                 offset: [0, 0],
9807
9808                 // @option direction: String = 'auto'
9809                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
9810                 // `top`, `bottom`, `center`, `auto`.
9811                 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
9812                 // position on the map.
9813                 direction: 'auto',
9814
9815                 // @option permanent: Boolean = false
9816                 // Whether to open the tooltip permanently or only on mouseover.
9817                 permanent: false,
9818
9819                 // @option sticky: Boolean = false
9820                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
9821                 sticky: false,
9822
9823                 // @option interactive: Boolean = false
9824                 // If true, the tooltip will listen to the feature events.
9825                 interactive: false,
9826
9827                 // @option opacity: Number = 0.9
9828                 // Tooltip container opacity.
9829                 opacity: 0.9
9830         },
9831
9832         onAdd: function (map) {
9833                 DivOverlay.prototype.onAdd.call(this, map);
9834                 this.setOpacity(this.options.opacity);
9835
9836                 // @namespace Map
9837                 // @section Tooltip events
9838                 // @event tooltipopen: TooltipEvent
9839                 // Fired when a tooltip is opened in the map.
9840                 map.fire('tooltipopen', {tooltip: this});
9841
9842                 if (this._source) {
9843                         // @namespace Layer
9844                         // @section Tooltip events
9845                         // @event tooltipopen: TooltipEvent
9846                         // Fired when a tooltip bound to this layer is opened.
9847                         this._source.fire('tooltipopen', {tooltip: this}, true);
9848                 }
9849         },
9850
9851         onRemove: function (map) {
9852                 DivOverlay.prototype.onRemove.call(this, map);
9853
9854                 // @namespace Map
9855                 // @section Tooltip events
9856                 // @event tooltipclose: TooltipEvent
9857                 // Fired when a tooltip in the map is closed.
9858                 map.fire('tooltipclose', {tooltip: this});
9859
9860                 if (this._source) {
9861                         // @namespace Layer
9862                         // @section Tooltip events
9863                         // @event tooltipclose: TooltipEvent
9864                         // Fired when a tooltip bound to this layer is closed.
9865                         this._source.fire('tooltipclose', {tooltip: this}, true);
9866                 }
9867         },
9868
9869         getEvents: function () {
9870                 var events = DivOverlay.prototype.getEvents.call(this);
9871
9872                 if (touch && !this.options.permanent) {
9873                         events.preclick = this._close;
9874                 }
9875
9876                 return events;
9877         },
9878
9879         _close: function () {
9880                 if (this._map) {
9881                         this._map.closeTooltip(this);
9882                 }
9883         },
9884
9885         _initLayout: function () {
9886                 var prefix = 'leaflet-tooltip',
9887                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
9888
9889                 this._contentNode = this._container = create$1('div', className);
9890         },
9891
9892         _updateLayout: function () {},
9893
9894         _adjustPan: function () {},
9895
9896         _setPosition: function (pos) {
9897                 var map = this._map,
9898                     container = this._container,
9899                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
9900                     tooltipPoint = map.layerPointToContainerPoint(pos),
9901                     direction = this.options.direction,
9902                     tooltipWidth = container.offsetWidth,
9903                     tooltipHeight = container.offsetHeight,
9904                     offset = toPoint(this.options.offset),
9905                     anchor = this._getAnchor();
9906
9907                 if (direction === 'top') {
9908                         pos = pos.add(toPoint(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
9909                 } else if (direction === 'bottom') {
9910                         pos = pos.subtract(toPoint(tooltipWidth / 2 - offset.x, -offset.y, true));
9911                 } else if (direction === 'center') {
9912                         pos = pos.subtract(toPoint(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
9913                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
9914                         direction = 'right';
9915                         pos = pos.add(toPoint(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
9916                 } else {
9917                         direction = 'left';
9918                         pos = pos.subtract(toPoint(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
9919                 }
9920
9921                 removeClass(container, 'leaflet-tooltip-right');
9922                 removeClass(container, 'leaflet-tooltip-left');
9923                 removeClass(container, 'leaflet-tooltip-top');
9924                 removeClass(container, 'leaflet-tooltip-bottom');
9925                 addClass(container, 'leaflet-tooltip-' + direction);
9926                 setPosition(container, pos);
9927         },
9928
9929         _updatePosition: function () {
9930                 var pos = this._map.latLngToLayerPoint(this._latlng);
9931                 this._setPosition(pos);
9932         },
9933
9934         setOpacity: function (opacity) {
9935                 this.options.opacity = opacity;
9936
9937                 if (this._container) {
9938                         setOpacity(this._container, opacity);
9939                 }
9940         },
9941
9942         _animateZoom: function (e) {
9943                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
9944                 this._setPosition(pos);
9945         },
9946
9947         _getAnchor: function () {
9948                 // Where should we anchor the tooltip on the source layer?
9949                 return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
9950         }
9951
9952 });
9953
9954 // @namespace Tooltip
9955 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
9956 // 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.
9957 var tooltip = function (options, source) {
9958         return new Tooltip(options, source);
9959 };
9960
9961 // @namespace Map
9962 // @section Methods for Layers and Controls
9963 Map.include({
9964
9965         // @method openTooltip(tooltip: Tooltip): this
9966         // Opens the specified tooltip.
9967         // @alternative
9968         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
9969         // Creates a tooltip with the specified content and options and open it.
9970         openTooltip: function (tooltip, latlng, options) {
9971                 if (!(tooltip instanceof Tooltip)) {
9972                         tooltip = new Tooltip(options).setContent(tooltip);
9973                 }
9974
9975                 if (latlng) {
9976                         tooltip.setLatLng(latlng);
9977                 }
9978
9979                 if (this.hasLayer(tooltip)) {
9980                         return this;
9981                 }
9982
9983                 return this.addLayer(tooltip);
9984         },
9985
9986         // @method closeTooltip(tooltip?: Tooltip): this
9987         // Closes the tooltip given as parameter.
9988         closeTooltip: function (tooltip) {
9989                 if (tooltip) {
9990                         this.removeLayer(tooltip);
9991                 }
9992                 return this;
9993         }
9994
9995 });
9996
9997 /*
9998  * @namespace Layer
9999  * @section Tooltip methods example
10000  *
10001  * All layers share a set of methods convenient for binding tooltips to it.
10002  *
10003  * ```js
10004  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
10005  * layer.openTooltip();
10006  * layer.closeTooltip();
10007  * ```
10008  */
10009
10010 // @section Tooltip methods
10011 Layer.include({
10012
10013         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
10014         // Binds a tooltip to the layer with the passed `content` and sets up the
10015         // necessary event listeners. If a `Function` is passed it will receive
10016         // the layer as the first argument and should return a `String` or `HTMLElement`.
10017         bindTooltip: function (content, options) {
10018
10019                 if (content instanceof Tooltip) {
10020                         setOptions(content, options);
10021                         this._tooltip = content;
10022                         content._source = this;
10023                 } else {
10024                         if (!this._tooltip || options) {
10025                                 this._tooltip = new Tooltip(options, this);
10026                         }
10027                         this._tooltip.setContent(content);
10028
10029                 }
10030
10031                 this._initTooltipInteractions();
10032
10033                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
10034                         this.openTooltip();
10035                 }
10036
10037                 return this;
10038         },
10039
10040         // @method unbindTooltip(): this
10041         // Removes the tooltip previously bound with `bindTooltip`.
10042         unbindTooltip: function () {
10043                 if (this._tooltip) {
10044                         this._initTooltipInteractions(true);
10045                         this.closeTooltip();
10046                         this._tooltip = null;
10047                 }
10048                 return this;
10049         },
10050
10051         _initTooltipInteractions: function (remove$$1) {
10052                 if (!remove$$1 && this._tooltipHandlersAdded) { return; }
10053                 var onOff = remove$$1 ? 'off' : 'on',
10054                     events = {
10055                         remove: this.closeTooltip,
10056                         move: this._moveTooltip
10057                     };
10058                 if (!this._tooltip.options.permanent) {
10059                         events.mouseover = this._openTooltip;
10060                         events.mouseout = this.closeTooltip;
10061                         if (this._tooltip.options.sticky) {
10062                                 events.mousemove = this._moveTooltip;
10063                         }
10064                         if (touch) {
10065                                 events.click = this._openTooltip;
10066                         }
10067                 } else {
10068                         events.add = this._openTooltip;
10069                 }
10070                 this[onOff](events);
10071                 this._tooltipHandlersAdded = !remove$$1;
10072         },
10073
10074         // @method openTooltip(latlng?: LatLng): this
10075         // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
10076         openTooltip: function (layer, latlng) {
10077                 if (!(layer instanceof Layer)) {
10078                         latlng = layer;
10079                         layer = this;
10080                 }
10081
10082                 if (layer instanceof FeatureGroup) {
10083                         for (var id in this._layers) {
10084                                 layer = this._layers[id];
10085                                 break;
10086                         }
10087                 }
10088
10089                 if (!latlng) {
10090                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
10091                 }
10092
10093                 if (this._tooltip && this._map) {
10094
10095                         // set tooltip source to this layer
10096                         this._tooltip._source = layer;
10097
10098                         // update the tooltip (content, layout, ect...)
10099                         this._tooltip.update();
10100
10101                         // open the tooltip on the map
10102                         this._map.openTooltip(this._tooltip, latlng);
10103
10104                         // Tooltip container may not be defined if not permanent and never
10105                         // opened.
10106                         if (this._tooltip.options.interactive && this._tooltip._container) {
10107                                 addClass(this._tooltip._container, 'leaflet-clickable');
10108                                 this.addInteractiveTarget(this._tooltip._container);
10109                         }
10110                 }
10111
10112                 return this;
10113         },
10114
10115         // @method closeTooltip(): this
10116         // Closes the tooltip bound to this layer if it is open.
10117         closeTooltip: function () {
10118                 if (this._tooltip) {
10119                         this._tooltip._close();
10120                         if (this._tooltip.options.interactive && this._tooltip._container) {
10121                                 removeClass(this._tooltip._container, 'leaflet-clickable');
10122                                 this.removeInteractiveTarget(this._tooltip._container);
10123                         }
10124                 }
10125                 return this;
10126         },
10127
10128         // @method toggleTooltip(): this
10129         // Opens or closes the tooltip bound to this layer depending on its current state.
10130         toggleTooltip: function (target) {
10131                 if (this._tooltip) {
10132                         if (this._tooltip._map) {
10133                                 this.closeTooltip();
10134                         } else {
10135                                 this.openTooltip(target);
10136                         }
10137                 }
10138                 return this;
10139         },
10140
10141         // @method isTooltipOpen(): boolean
10142         // Returns `true` if the tooltip bound to this layer is currently open.
10143         isTooltipOpen: function () {
10144                 return this._tooltip.isOpen();
10145         },
10146
10147         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
10148         // Sets the content of the tooltip bound to this layer.
10149         setTooltipContent: function (content) {
10150                 if (this._tooltip) {
10151                         this._tooltip.setContent(content);
10152                 }
10153                 return this;
10154         },
10155
10156         // @method getTooltip(): Tooltip
10157         // Returns the tooltip bound to this layer.
10158         getTooltip: function () {
10159                 return this._tooltip;
10160         },
10161
10162         _openTooltip: function (e) {
10163                 var layer = e.layer || e.target;
10164
10165                 if (!this._tooltip || !this._map) {
10166                         return;
10167                 }
10168                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
10169         },
10170
10171         _moveTooltip: function (e) {
10172                 var latlng = e.latlng, containerPoint, layerPoint;
10173                 if (this._tooltip.options.sticky && e.originalEvent) {
10174                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
10175                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
10176                         latlng = this._map.layerPointToLatLng(layerPoint);
10177                 }
10178                 this._tooltip.setLatLng(latlng);
10179         }
10180 });
10181
10182 /*
10183  * @class DivIcon
10184  * @aka L.DivIcon
10185  * @inherits Icon
10186  *
10187  * Represents a lightweight icon for markers that uses a simple `<div>`
10188  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
10189  *
10190  * @example
10191  * ```js
10192  * var myIcon = L.divIcon({className: 'my-div-icon'});
10193  * // you can set .my-div-icon styles in CSS
10194  *
10195  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
10196  * ```
10197  *
10198  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
10199  */
10200
10201 var DivIcon = Icon.extend({
10202         options: {
10203                 // @section
10204                 // @aka DivIcon options
10205                 iconSize: [12, 12], // also can be set through CSS
10206
10207                 // iconAnchor: (Point),
10208                 // popupAnchor: (Point),
10209
10210                 // @option html: String = ''
10211                 // Custom HTML code to put inside the div element, empty by default.
10212                 html: false,
10213
10214                 // @option bgPos: Point = [0, 0]
10215                 // Optional relative position of the background, in pixels
10216                 bgPos: null,
10217
10218                 className: 'leaflet-div-icon'
10219         },
10220
10221         createIcon: function (oldIcon) {
10222                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
10223                     options = this.options;
10224
10225                 div.innerHTML = options.html !== false ? options.html : '';
10226
10227                 if (options.bgPos) {
10228                         var bgPos = toPoint(options.bgPos);
10229                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
10230                 }
10231                 this._setIconStyles(div, 'icon');
10232
10233                 return div;
10234         },
10235
10236         createShadow: function () {
10237                 return null;
10238         }
10239 });
10240
10241 // @factory L.divIcon(options: DivIcon options)
10242 // Creates a `DivIcon` instance with the given options.
10243 function divIcon(options) {
10244         return new DivIcon(options);
10245 }
10246
10247 Icon.Default = IconDefault;
10248
10249 /*
10250  * @class GridLayer
10251  * @inherits Layer
10252  * @aka L.GridLayer
10253  *
10254  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
10255  * 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.
10256  *
10257  *
10258  * @section Synchronous usage
10259  * @example
10260  *
10261  * 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.
10262  *
10263  * ```js
10264  * var CanvasLayer = L.GridLayer.extend({
10265  *     createTile: function(coords){
10266  *         // create a <canvas> element for drawing
10267  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10268  *
10269  *         // setup tile width and height according to the options
10270  *         var size = this.getTileSize();
10271  *         tile.width = size.x;
10272  *         tile.height = size.y;
10273  *
10274  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
10275  *         var ctx = tile.getContext('2d');
10276  *
10277  *         // return the tile so it can be rendered on screen
10278  *         return tile;
10279  *     }
10280  * });
10281  * ```
10282  *
10283  * @section Asynchronous usage
10284  * @example
10285  *
10286  * 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.
10287  *
10288  * ```js
10289  * var CanvasLayer = L.GridLayer.extend({
10290  *     createTile: function(coords, done){
10291  *         var error;
10292  *
10293  *         // create a <canvas> element for drawing
10294  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
10295  *
10296  *         // setup tile width and height according to the options
10297  *         var size = this.getTileSize();
10298  *         tile.width = size.x;
10299  *         tile.height = size.y;
10300  *
10301  *         // draw something asynchronously and pass the tile to the done() callback
10302  *         setTimeout(function() {
10303  *             done(error, tile);
10304  *         }, 1000);
10305  *
10306  *         return tile;
10307  *     }
10308  * });
10309  * ```
10310  *
10311  * @section
10312  */
10313
10314
10315 var GridLayer = Layer.extend({
10316
10317         // @section
10318         // @aka GridLayer options
10319         options: {
10320                 // @option tileSize: Number|Point = 256
10321                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
10322                 tileSize: 256,
10323
10324                 // @option opacity: Number = 1.0
10325                 // Opacity of the tiles. Can be used in the `createTile()` function.
10326                 opacity: 1,
10327
10328                 // @option updateWhenIdle: Boolean = (depends)
10329                 // Load new tiles only when panning ends.
10330                 // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
10331                 // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
10332                 // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
10333                 updateWhenIdle: mobile,
10334
10335                 // @option updateWhenZooming: Boolean = true
10336                 // 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.
10337                 updateWhenZooming: true,
10338
10339                 // @option updateInterval: Number = 200
10340                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
10341                 updateInterval: 200,
10342
10343                 // @option zIndex: Number = 1
10344                 // The explicit zIndex of the tile layer.
10345                 zIndex: 1,
10346
10347                 // @option bounds: LatLngBounds = undefined
10348                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
10349                 bounds: null,
10350
10351                 // @option minZoom: Number = 0
10352                 // The minimum zoom level down to which this layer will be displayed (inclusive).
10353                 minZoom: 0,
10354
10355                 // @option maxZoom: Number = undefined
10356                 // The maximum zoom level up to which this layer will be displayed (inclusive).
10357                 maxZoom: undefined,
10358
10359                 // @option maxNativeZoom: Number = undefined
10360                 // Maximum zoom number the tile source has available. If it is specified,
10361                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
10362                 // from `maxNativeZoom` level and auto-scaled.
10363                 maxNativeZoom: undefined,
10364
10365                 // @option minNativeZoom: Number = undefined
10366                 // Minimum zoom number the tile source has available. If it is specified,
10367                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
10368                 // from `minNativeZoom` level and auto-scaled.
10369                 minNativeZoom: undefined,
10370
10371                 // @option noWrap: Boolean = false
10372                 // Whether the layer is wrapped around the antimeridian. If `true`, the
10373                 // GridLayer will only be displayed once at low zoom levels. Has no
10374                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
10375                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
10376                 // tiles outside the CRS limits.
10377                 noWrap: false,
10378
10379                 // @option pane: String = 'tilePane'
10380                 // `Map pane` where the grid layer will be added.
10381                 pane: 'tilePane',
10382
10383                 // @option className: String = ''
10384                 // A custom class name to assign to the tile layer. Empty by default.
10385                 className: '',
10386
10387                 // @option keepBuffer: Number = 2
10388                 // When panning the map, keep this many rows and columns of tiles before unloading them.
10389                 keepBuffer: 2
10390         },
10391
10392         initialize: function (options) {
10393                 setOptions(this, options);
10394         },
10395
10396         onAdd: function () {
10397                 this._initContainer();
10398
10399                 this._levels = {};
10400                 this._tiles = {};
10401
10402                 this._resetView();
10403                 this._update();
10404         },
10405
10406         beforeAdd: function (map) {
10407                 map._addZoomLimit(this);
10408         },
10409
10410         onRemove: function (map) {
10411                 this._removeAllTiles();
10412                 remove(this._container);
10413                 map._removeZoomLimit(this);
10414                 this._container = null;
10415                 this._tileZoom = null;
10416         },
10417
10418         // @method bringToFront: this
10419         // Brings the tile layer to the top of all tile layers.
10420         bringToFront: function () {
10421                 if (this._map) {
10422                         toFront(this._container);
10423                         this._setAutoZIndex(Math.max);
10424                 }
10425                 return this;
10426         },
10427
10428         // @method bringToBack: this
10429         // Brings the tile layer to the bottom of all tile layers.
10430         bringToBack: function () {
10431                 if (this._map) {
10432                         toBack(this._container);
10433                         this._setAutoZIndex(Math.min);
10434                 }
10435                 return this;
10436         },
10437
10438         // @method getContainer: HTMLElement
10439         // Returns the HTML element that contains the tiles for this layer.
10440         getContainer: function () {
10441                 return this._container;
10442         },
10443
10444         // @method setOpacity(opacity: Number): this
10445         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
10446         setOpacity: function (opacity) {
10447                 this.options.opacity = opacity;
10448                 this._updateOpacity();
10449                 return this;
10450         },
10451
10452         // @method setZIndex(zIndex: Number): this
10453         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
10454         setZIndex: function (zIndex) {
10455                 this.options.zIndex = zIndex;
10456                 this._updateZIndex();
10457
10458                 return this;
10459         },
10460
10461         // @method isLoading: Boolean
10462         // Returns `true` if any tile in the grid layer has not finished loading.
10463         isLoading: function () {
10464                 return this._loading;
10465         },
10466
10467         // @method redraw: this
10468         // Causes the layer to clear all the tiles and request them again.
10469         redraw: function () {
10470                 if (this._map) {
10471                         this._removeAllTiles();
10472                         this._update();
10473                 }
10474                 return this;
10475         },
10476
10477         getEvents: function () {
10478                 var events = {
10479                         viewprereset: this._invalidateAll,
10480                         viewreset: this._resetView,
10481                         zoom: this._resetView,
10482                         moveend: this._onMoveEnd
10483                 };
10484
10485                 if (!this.options.updateWhenIdle) {
10486                         // update tiles on move, but not more often than once per given interval
10487                         if (!this._onMove) {
10488                                 this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
10489                         }
10490
10491                         events.move = this._onMove;
10492                 }
10493
10494                 if (this._zoomAnimated) {
10495                         events.zoomanim = this._animateZoom;
10496                 }
10497
10498                 return events;
10499         },
10500
10501         // @section Extension methods
10502         // Layers extending `GridLayer` shall reimplement the following method.
10503         // @method createTile(coords: Object, done?: Function): HTMLElement
10504         // Called only internally, must be overriden by classes extending `GridLayer`.
10505         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
10506         // is specified, it must be called when the tile has finished loading and drawing.
10507         createTile: function () {
10508                 return document.createElement('div');
10509         },
10510
10511         // @section
10512         // @method getTileSize: Point
10513         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
10514         getTileSize: function () {
10515                 var s = this.options.tileSize;
10516                 return s instanceof Point ? s : new Point(s, s);
10517         },
10518
10519         _updateZIndex: function () {
10520                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
10521                         this._container.style.zIndex = this.options.zIndex;
10522                 }
10523         },
10524
10525         _setAutoZIndex: function (compare) {
10526                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
10527
10528                 var layers = this.getPane().children,
10529                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
10530
10531                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
10532
10533                         zIndex = layers[i].style.zIndex;
10534
10535                         if (layers[i] !== this._container && zIndex) {
10536                                 edgeZIndex = compare(edgeZIndex, +zIndex);
10537                         }
10538                 }
10539
10540                 if (isFinite(edgeZIndex)) {
10541                         this.options.zIndex = edgeZIndex + compare(-1, 1);
10542                         this._updateZIndex();
10543                 }
10544         },
10545
10546         _updateOpacity: function () {
10547                 if (!this._map) { return; }
10548
10549                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
10550                 if (ielt9) { return; }
10551
10552                 setOpacity(this._container, this.options.opacity);
10553
10554                 var now = +new Date(),
10555                     nextFrame = false,
10556                     willPrune = false;
10557
10558                 for (var key in this._tiles) {
10559                         var tile = this._tiles[key];
10560                         if (!tile.current || !tile.loaded) { continue; }
10561
10562                         var fade = Math.min(1, (now - tile.loaded) / 200);
10563
10564                         setOpacity(tile.el, fade);
10565                         if (fade < 1) {
10566                                 nextFrame = true;
10567                         } else {
10568                                 if (tile.active) {
10569                                         willPrune = true;
10570                                 } else {
10571                                         this._onOpaqueTile(tile);
10572                                 }
10573                                 tile.active = true;
10574                         }
10575                 }
10576
10577                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
10578
10579                 if (nextFrame) {
10580                         cancelAnimFrame(this._fadeFrame);
10581                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
10582                 }
10583         },
10584
10585         _onOpaqueTile: falseFn,
10586
10587         _initContainer: function () {
10588                 if (this._container) { return; }
10589
10590                 this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
10591                 this._updateZIndex();
10592
10593                 if (this.options.opacity < 1) {
10594                         this._updateOpacity();
10595                 }
10596
10597                 this.getPane().appendChild(this._container);
10598         },
10599
10600         _updateLevels: function () {
10601
10602                 var zoom = this._tileZoom,
10603                     maxZoom = this.options.maxZoom;
10604
10605                 if (zoom === undefined) { return undefined; }
10606
10607                 for (var z in this._levels) {
10608                         if (this._levels[z].el.children.length || z === zoom) {
10609                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
10610                                 this._onUpdateLevel(z);
10611                         } else {
10612                                 remove(this._levels[z].el);
10613                                 this._removeTilesAtZoom(z);
10614                                 this._onRemoveLevel(z);
10615                                 delete this._levels[z];
10616                         }
10617                 }
10618
10619                 var level = this._levels[zoom],
10620                     map = this._map;
10621
10622                 if (!level) {
10623                         level = this._levels[zoom] = {};
10624
10625                         level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
10626                         level.el.style.zIndex = maxZoom;
10627
10628                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
10629                         level.zoom = zoom;
10630
10631                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
10632
10633                         // force the browser to consider the newly added element for transition
10634                         falseFn(level.el.offsetWidth);
10635
10636                         this._onCreateLevel(level);
10637                 }
10638
10639                 this._level = level;
10640
10641                 return level;
10642         },
10643
10644         _onUpdateLevel: falseFn,
10645
10646         _onRemoveLevel: falseFn,
10647
10648         _onCreateLevel: falseFn,
10649
10650         _pruneTiles: function () {
10651                 if (!this._map) {
10652                         return;
10653                 }
10654
10655                 var key, tile;
10656
10657                 var zoom = this._map.getZoom();
10658                 if (zoom > this.options.maxZoom ||
10659                         zoom < this.options.minZoom) {
10660                         this._removeAllTiles();
10661                         return;
10662                 }
10663
10664                 for (key in this._tiles) {
10665                         tile = this._tiles[key];
10666                         tile.retain = tile.current;
10667                 }
10668
10669                 for (key in this._tiles) {
10670                         tile = this._tiles[key];
10671                         if (tile.current && !tile.active) {
10672                                 var coords = tile.coords;
10673                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
10674                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
10675                                 }
10676                         }
10677                 }
10678
10679                 for (key in this._tiles) {
10680                         if (!this._tiles[key].retain) {
10681                                 this._removeTile(key);
10682                         }
10683                 }
10684         },
10685
10686         _removeTilesAtZoom: function (zoom) {
10687                 for (var key in this._tiles) {
10688                         if (this._tiles[key].coords.z !== zoom) {
10689                                 continue;
10690                         }
10691                         this._removeTile(key);
10692                 }
10693         },
10694
10695         _removeAllTiles: function () {
10696                 for (var key in this._tiles) {
10697                         this._removeTile(key);
10698                 }
10699         },
10700
10701         _invalidateAll: function () {
10702                 for (var z in this._levels) {
10703                         remove(this._levels[z].el);
10704                         this._onRemoveLevel(z);
10705                         delete this._levels[z];
10706                 }
10707                 this._removeAllTiles();
10708
10709                 this._tileZoom = null;
10710         },
10711
10712         _retainParent: function (x, y, z, minZoom) {
10713                 var x2 = Math.floor(x / 2),
10714                     y2 = Math.floor(y / 2),
10715                     z2 = z - 1,
10716                     coords2 = new Point(+x2, +y2);
10717                 coords2.z = +z2;
10718
10719                 var key = this._tileCoordsToKey(coords2),
10720                     tile = this._tiles[key];
10721
10722                 if (tile && tile.active) {
10723                         tile.retain = true;
10724                         return true;
10725
10726                 } else if (tile && tile.loaded) {
10727                         tile.retain = true;
10728                 }
10729
10730                 if (z2 > minZoom) {
10731                         return this._retainParent(x2, y2, z2, minZoom);
10732                 }
10733
10734                 return false;
10735         },
10736
10737         _retainChildren: function (x, y, z, maxZoom) {
10738
10739                 for (var i = 2 * x; i < 2 * x + 2; i++) {
10740                         for (var j = 2 * y; j < 2 * y + 2; j++) {
10741
10742                                 var coords = new Point(i, j);
10743                                 coords.z = z + 1;
10744
10745                                 var key = this._tileCoordsToKey(coords),
10746                                     tile = this._tiles[key];
10747
10748                                 if (tile && tile.active) {
10749                                         tile.retain = true;
10750                                         continue;
10751
10752                                 } else if (tile && tile.loaded) {
10753                                         tile.retain = true;
10754                                 }
10755
10756                                 if (z + 1 < maxZoom) {
10757                                         this._retainChildren(i, j, z + 1, maxZoom);
10758                                 }
10759                         }
10760                 }
10761         },
10762
10763         _resetView: function (e) {
10764                 var animating = e && (e.pinch || e.flyTo);
10765                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
10766         },
10767
10768         _animateZoom: function (e) {
10769                 this._setView(e.center, e.zoom, true, e.noUpdate);
10770         },
10771
10772         _clampZoom: function (zoom) {
10773                 var options = this.options;
10774
10775                 if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
10776                         return options.minNativeZoom;
10777                 }
10778
10779                 if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
10780                         return options.maxNativeZoom;
10781                 }
10782
10783                 return zoom;
10784         },
10785
10786         _setView: function (center, zoom, noPrune, noUpdate) {
10787                 var tileZoom = this._clampZoom(Math.round(zoom));
10788                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
10789                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
10790                         tileZoom = undefined;
10791                 }
10792
10793                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
10794
10795                 if (!noUpdate || tileZoomChanged) {
10796
10797                         this._tileZoom = tileZoom;
10798
10799                         if (this._abortLoading) {
10800                                 this._abortLoading();
10801                         }
10802
10803                         this._updateLevels();
10804                         this._resetGrid();
10805
10806                         if (tileZoom !== undefined) {
10807                                 this._update(center);
10808                         }
10809
10810                         if (!noPrune) {
10811                                 this._pruneTiles();
10812                         }
10813
10814                         // Flag to prevent _updateOpacity from pruning tiles during
10815                         // a zoom anim or a pinch gesture
10816                         this._noPrune = !!noPrune;
10817                 }
10818
10819                 this._setZoomTransforms(center, zoom);
10820         },
10821
10822         _setZoomTransforms: function (center, zoom) {
10823                 for (var i in this._levels) {
10824                         this._setZoomTransform(this._levels[i], center, zoom);
10825                 }
10826         },
10827
10828         _setZoomTransform: function (level, center, zoom) {
10829                 var scale = this._map.getZoomScale(zoom, level.zoom),
10830                     translate = level.origin.multiplyBy(scale)
10831                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
10832
10833                 if (any3d) {
10834                         setTransform(level.el, translate, scale);
10835                 } else {
10836                         setPosition(level.el, translate);
10837                 }
10838         },
10839
10840         _resetGrid: function () {
10841                 var map = this._map,
10842                     crs = map.options.crs,
10843                     tileSize = this._tileSize = this.getTileSize(),
10844                     tileZoom = this._tileZoom;
10845
10846                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
10847                 if (bounds) {
10848                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
10849                 }
10850
10851                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
10852                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
10853                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
10854                 ];
10855                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
10856                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
10857                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
10858                 ];
10859         },
10860
10861         _onMoveEnd: function () {
10862                 if (!this._map || this._map._animatingZoom) { return; }
10863
10864                 this._update();
10865         },
10866
10867         _getTiledPixelBounds: function (center) {
10868                 var map = this._map,
10869                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
10870                     scale = map.getZoomScale(mapZoom, this._tileZoom),
10871                     pixelCenter = map.project(center, this._tileZoom).floor(),
10872                     halfSize = map.getSize().divideBy(scale * 2);
10873
10874                 return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
10875         },
10876
10877         // Private method to load tiles in the grid's active zoom level according to map bounds
10878         _update: function (center) {
10879                 var map = this._map;
10880                 if (!map) { return; }
10881                 var zoom = this._clampZoom(map.getZoom());
10882
10883                 if (center === undefined) { center = map.getCenter(); }
10884                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
10885
10886                 var pixelBounds = this._getTiledPixelBounds(center),
10887                     tileRange = this._pxBoundsToTileRange(pixelBounds),
10888                     tileCenter = tileRange.getCenter(),
10889                     queue = [],
10890                     margin = this.options.keepBuffer,
10891                     noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
10892                                               tileRange.getTopRight().add([margin, -margin]));
10893
10894                 // Sanity check: panic if the tile range contains Infinity somewhere.
10895                 if (!(isFinite(tileRange.min.x) &&
10896                       isFinite(tileRange.min.y) &&
10897                       isFinite(tileRange.max.x) &&
10898                       isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }
10899
10900                 for (var key in this._tiles) {
10901                         var c = this._tiles[key].coords;
10902                         if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
10903                                 this._tiles[key].current = false;
10904                         }
10905                 }
10906
10907                 // _update just loads more tiles. If the tile zoom level differs too much
10908                 // from the map's, let _setView reset levels and prune old tiles.
10909                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
10910
10911                 // create a queue of coordinates to load tiles from
10912                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
10913                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
10914                                 var coords = new Point(i, j);
10915                                 coords.z = this._tileZoom;
10916
10917                                 if (!this._isValidTile(coords)) { continue; }
10918
10919                                 if (!this._tiles[this._tileCoordsToKey(coords)]) {
10920                                         queue.push(coords);
10921                                 }
10922                         }
10923                 }
10924
10925                 // sort tile queue to load tiles in order of their distance to center
10926                 queue.sort(function (a, b) {
10927                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
10928                 });
10929
10930                 if (queue.length !== 0) {
10931                         // if it's the first batch of tiles to load
10932                         if (!this._loading) {
10933                                 this._loading = true;
10934                                 // @event loading: Event
10935                                 // Fired when the grid layer starts loading tiles.
10936                                 this.fire('loading');
10937                         }
10938
10939                         // create DOM fragment to append tiles in one batch
10940                         var fragment = document.createDocumentFragment();
10941
10942                         for (i = 0; i < queue.length; i++) {
10943                                 this._addTile(queue[i], fragment);
10944                         }
10945
10946                         this._level.el.appendChild(fragment);
10947                 }
10948         },
10949
10950         _isValidTile: function (coords) {
10951                 var crs = this._map.options.crs;
10952
10953                 if (!crs.infinite) {
10954                         // don't load tile if it's out of bounds and not wrapped
10955                         var bounds = this._globalTileRange;
10956                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
10957                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
10958                 }
10959
10960                 if (!this.options.bounds) { return true; }
10961
10962                 // don't load tile if it doesn't intersect the bounds in options
10963                 var tileBounds = this._tileCoordsToBounds(coords);
10964                 return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
10965         },
10966
10967         _keyToBounds: function (key) {
10968                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
10969         },
10970
10971         // converts tile coordinates to its geographical bounds
10972         _tileCoordsToBounds: function (coords) {
10973
10974                 var map = this._map,
10975                     tileSize = this.getTileSize(),
10976
10977                     nwPoint = coords.scaleBy(tileSize),
10978                     sePoint = nwPoint.add(tileSize),
10979
10980                     nw = map.unproject(nwPoint, coords.z),
10981                     se = map.unproject(sePoint, coords.z),
10982                     bounds = new LatLngBounds(nw, se);
10983
10984                 if (!this.options.noWrap) {
10985                         map.wrapLatLngBounds(bounds);
10986                 }
10987
10988                 return bounds;
10989         },
10990
10991         // converts tile coordinates to key for the tile cache
10992         _tileCoordsToKey: function (coords) {
10993                 return coords.x + ':' + coords.y + ':' + coords.z;
10994         },
10995
10996         // converts tile cache key to coordinates
10997         _keyToTileCoords: function (key) {
10998                 var k = key.split(':'),
10999                     coords = new Point(+k[0], +k[1]);
11000                 coords.z = +k[2];
11001                 return coords;
11002         },
11003
11004         _removeTile: function (key) {
11005                 var tile = this._tiles[key];
11006                 if (!tile) { return; }
11007
11008                 remove(tile.el);
11009
11010                 delete this._tiles[key];
11011
11012                 // @event tileunload: TileEvent
11013                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
11014                 this.fire('tileunload', {
11015                         tile: tile.el,
11016                         coords: this._keyToTileCoords(key)
11017                 });
11018         },
11019
11020         _initTile: function (tile) {
11021                 addClass(tile, 'leaflet-tile');
11022
11023                 var tileSize = this.getTileSize();
11024                 tile.style.width = tileSize.x + 'px';
11025                 tile.style.height = tileSize.y + 'px';
11026
11027                 tile.onselectstart = falseFn;
11028                 tile.onmousemove = falseFn;
11029
11030                 // update opacity on tiles in IE7-8 because of filter inheritance problems
11031                 if (ielt9 && this.options.opacity < 1) {
11032                         setOpacity(tile, this.options.opacity);
11033                 }
11034
11035                 // without this hack, tiles disappear after zoom on Chrome for Android
11036                 // https://github.com/Leaflet/Leaflet/issues/2078
11037                 if (android && !android23) {
11038                         tile.style.WebkitBackfaceVisibility = 'hidden';
11039                 }
11040         },
11041
11042         _addTile: function (coords, container) {
11043                 var tilePos = this._getTilePos(coords),
11044                     key = this._tileCoordsToKey(coords);
11045
11046                 var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));
11047
11048                 this._initTile(tile);
11049
11050                 // if createTile is defined with a second argument ("done" callback),
11051                 // we know that tile is async and will be ready later; otherwise
11052                 if (this.createTile.length < 2) {
11053                         // mark tile as ready, but delay one frame for opacity animation to happen
11054                         requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
11055                 }
11056
11057                 setPosition(tile, tilePos);
11058
11059                 // save tile in cache
11060                 this._tiles[key] = {
11061                         el: tile,
11062                         coords: coords,
11063                         current: true
11064                 };
11065
11066                 container.appendChild(tile);
11067                 // @event tileloadstart: TileEvent
11068                 // Fired when a tile is requested and starts loading.
11069                 this.fire('tileloadstart', {
11070                         tile: tile,
11071                         coords: coords
11072                 });
11073         },
11074
11075         _tileReady: function (coords, err, tile) {
11076                 if (!this._map) { return; }
11077
11078                 if (err) {
11079                         // @event tileerror: TileErrorEvent
11080                         // Fired when there is an error loading a tile.
11081                         this.fire('tileerror', {
11082                                 error: err,
11083                                 tile: tile,
11084                                 coords: coords
11085                         });
11086                 }
11087
11088                 var key = this._tileCoordsToKey(coords);
11089
11090                 tile = this._tiles[key];
11091                 if (!tile) { return; }
11092
11093                 tile.loaded = +new Date();
11094                 if (this._map._fadeAnimated) {
11095                         setOpacity(tile.el, 0);
11096                         cancelAnimFrame(this._fadeFrame);
11097                         this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
11098                 } else {
11099                         tile.active = true;
11100                         this._pruneTiles();
11101                 }
11102
11103                 if (!err) {
11104                         addClass(tile.el, 'leaflet-tile-loaded');
11105
11106                         // @event tileload: TileEvent
11107                         // Fired when a tile loads.
11108                         this.fire('tileload', {
11109                                 tile: tile.el,
11110                                 coords: coords
11111                         });
11112                 }
11113
11114                 if (this._noTilesToLoad()) {
11115                         this._loading = false;
11116                         // @event load: Event
11117                         // Fired when the grid layer loaded all visible tiles.
11118                         this.fire('load');
11119
11120                         if (ielt9 || !this._map._fadeAnimated) {
11121                                 requestAnimFrame(this._pruneTiles, this);
11122                         } else {
11123                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
11124                                 // to trigger a pruning.
11125                                 setTimeout(bind(this._pruneTiles, this), 250);
11126                         }
11127                 }
11128         },
11129
11130         _getTilePos: function (coords) {
11131                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
11132         },
11133
11134         _wrapCoords: function (coords) {
11135                 var newCoords = new Point(
11136                         this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
11137                         this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
11138                 newCoords.z = coords.z;
11139                 return newCoords;
11140         },
11141
11142         _pxBoundsToTileRange: function (bounds) {
11143                 var tileSize = this.getTileSize();
11144                 return new Bounds(
11145                         bounds.min.unscaleBy(tileSize).floor(),
11146                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
11147         },
11148
11149         _noTilesToLoad: function () {
11150                 for (var key in this._tiles) {
11151                         if (!this._tiles[key].loaded) { return false; }
11152                 }
11153                 return true;
11154         }
11155 });
11156
11157 // @factory L.gridLayer(options?: GridLayer options)
11158 // Creates a new instance of GridLayer with the supplied options.
11159 function gridLayer(options) {
11160         return new GridLayer(options);
11161 }
11162
11163 /*
11164  * @class TileLayer
11165  * @inherits GridLayer
11166  * @aka L.TileLayer
11167  * Used to load and display tile layers on the map. Extends `GridLayer`.
11168  *
11169  * @example
11170  *
11171  * ```js
11172  * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
11173  * ```
11174  *
11175  * @section URL template
11176  * @example
11177  *
11178  * A string of the following form:
11179  *
11180  * ```
11181  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
11182  * ```
11183  *
11184  * `{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.
11185  *
11186  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
11187  *
11188  * ```
11189  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
11190  * ```
11191  */
11192
11193
11194 var TileLayer = GridLayer.extend({
11195
11196         // @section
11197         // @aka TileLayer options
11198         options: {
11199                 // @option minZoom: Number = 0
11200                 // The minimum zoom level down to which this layer will be displayed (inclusive).
11201                 minZoom: 0,
11202
11203                 // @option maxZoom: Number = 18
11204                 // The maximum zoom level up to which this layer will be displayed (inclusive).
11205                 maxZoom: 18,
11206
11207                 // @option subdomains: String|String[] = 'abc'
11208                 // 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.
11209                 subdomains: 'abc',
11210
11211                 // @option errorTileUrl: String = ''
11212                 // URL to the tile image to show in place of the tile that failed to load.
11213                 errorTileUrl: '',
11214
11215                 // @option zoomOffset: Number = 0
11216                 // The zoom number used in tile URLs will be offset with this value.
11217                 zoomOffset: 0,
11218
11219                 // @option tms: Boolean = false
11220                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
11221                 tms: false,
11222
11223                 // @option zoomReverse: Boolean = false
11224                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
11225                 zoomReverse: false,
11226
11227                 // @option detectRetina: Boolean = false
11228                 // 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.
11229                 detectRetina: false,
11230
11231                 // @option crossOrigin: Boolean = false
11232                 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
11233                 crossOrigin: false
11234         },
11235
11236         initialize: function (url, options) {
11237
11238                 this._url = url;
11239
11240                 options = setOptions(this, options);
11241
11242                 // detecting retina displays, adjusting tileSize and zoom levels
11243                 if (options.detectRetina && retina && options.maxZoom > 0) {
11244
11245                         options.tileSize = Math.floor(options.tileSize / 2);
11246
11247                         if (!options.zoomReverse) {
11248                                 options.zoomOffset++;
11249                                 options.maxZoom--;
11250                         } else {
11251                                 options.zoomOffset--;
11252                                 options.minZoom++;
11253                         }
11254
11255                         options.minZoom = Math.max(0, options.minZoom);
11256                 }
11257
11258                 if (typeof options.subdomains === 'string') {
11259                         options.subdomains = options.subdomains.split('');
11260                 }
11261
11262                 // for https://github.com/Leaflet/Leaflet/issues/137
11263                 if (!android) {
11264                         this.on('tileunload', this._onTileRemove);
11265                 }
11266         },
11267
11268         // @method setUrl(url: String, noRedraw?: Boolean): this
11269         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
11270         setUrl: function (url, noRedraw) {
11271                 this._url = url;
11272
11273                 if (!noRedraw) {
11274                         this.redraw();
11275                 }
11276                 return this;
11277         },
11278
11279         // @method createTile(coords: Object, done?: Function): HTMLElement
11280         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
11281         // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
11282         // callback is called when the tile has been loaded.
11283         createTile: function (coords, done) {
11284                 var tile = document.createElement('img');
11285
11286                 on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
11287                 on(tile, 'error', bind(this._tileOnError, this, done, tile));
11288
11289                 if (this.options.crossOrigin) {
11290                         tile.crossOrigin = '';
11291                 }
11292
11293                 /*
11294                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
11295                  http://www.w3.org/TR/WCAG20-TECHS/H67
11296                 */
11297                 tile.alt = '';
11298
11299                 /*
11300                  Set role="presentation" to force screen readers to ignore this
11301                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
11302                 */
11303                 tile.setAttribute('role', 'presentation');
11304
11305                 tile.src = this.getTileUrl(coords);
11306
11307                 return tile;
11308         },
11309
11310         // @section Extension methods
11311         // @uninheritable
11312         // Layers extending `TileLayer` might reimplement the following method.
11313         // @method getTileUrl(coords: Object): String
11314         // Called only internally, returns the URL for a tile given its coordinates.
11315         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
11316         getTileUrl: function (coords) {
11317                 var data = {
11318                         r: retina ? '@2x' : '',
11319                         s: this._getSubdomain(coords),
11320                         x: coords.x,
11321                         y: coords.y,
11322                         z: this._getZoomForUrl()
11323                 };
11324                 if (this._map && !this._map.options.crs.infinite) {
11325                         var invertedY = this._globalTileRange.max.y - coords.y;
11326                         if (this.options.tms) {
11327                                 data['y'] = invertedY;
11328                         }
11329                         data['-y'] = invertedY;
11330                 }
11331
11332                 return template(this._url, extend(data, this.options));
11333         },
11334
11335         _tileOnLoad: function (done, tile) {
11336                 // For https://github.com/Leaflet/Leaflet/issues/3332
11337                 if (ielt9) {
11338                         setTimeout(bind(done, this, null, tile), 0);
11339                 } else {
11340                         done(null, tile);
11341                 }
11342         },
11343
11344         _tileOnError: function (done, tile, e) {
11345                 var errorUrl = this.options.errorTileUrl;
11346                 if (errorUrl && tile.src !== errorUrl) {
11347                         tile.src = errorUrl;
11348                 }
11349                 done(e, tile);
11350         },
11351
11352         _onTileRemove: function (e) {
11353                 e.tile.onload = null;
11354         },
11355
11356         _getZoomForUrl: function () {
11357                 var zoom = this._tileZoom,
11358                 maxZoom = this.options.maxZoom,
11359                 zoomReverse = this.options.zoomReverse,
11360                 zoomOffset = this.options.zoomOffset;
11361
11362                 if (zoomReverse) {
11363                         zoom = maxZoom - zoom;
11364                 }
11365
11366                 return zoom + zoomOffset;
11367         },
11368
11369         _getSubdomain: function (tilePoint) {
11370                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
11371                 return this.options.subdomains[index];
11372         },
11373
11374         // stops loading all tiles in the background layer
11375         _abortLoading: function () {
11376                 var i, tile;
11377                 for (i in this._tiles) {
11378                         if (this._tiles[i].coords.z !== this._tileZoom) {
11379                                 tile = this._tiles[i].el;
11380
11381                                 tile.onload = falseFn;
11382                                 tile.onerror = falseFn;
11383
11384                                 if (!tile.complete) {
11385                                         tile.src = emptyImageUrl;
11386                                         remove(tile);
11387                                 }
11388                         }
11389                 }
11390         }
11391 });
11392
11393
11394 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
11395 // Instantiates a tile layer object given a `URL template` and optionally an options object.
11396
11397 function tileLayer(url, options) {
11398         return new TileLayer(url, options);
11399 }
11400
11401 /*
11402  * @class TileLayer.WMS
11403  * @inherits TileLayer
11404  * @aka L.TileLayer.WMS
11405  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
11406  *
11407  * @example
11408  *
11409  * ```js
11410  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
11411  *      layers: 'nexrad-n0r-900913',
11412  *      format: 'image/png',
11413  *      transparent: true,
11414  *      attribution: "Weather data © 2012 IEM Nexrad"
11415  * });
11416  * ```
11417  */
11418
11419 var TileLayerWMS = TileLayer.extend({
11420
11421         // @section
11422         // @aka TileLayer.WMS options
11423         // If any custom options not documented here are used, they will be sent to the
11424         // WMS server as extra parameters in each request URL. This can be useful for
11425         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
11426         defaultWmsParams: {
11427                 service: 'WMS',
11428                 request: 'GetMap',
11429
11430                 // @option layers: String = ''
11431                 // **(required)** Comma-separated list of WMS layers to show.
11432                 layers: '',
11433
11434                 // @option styles: String = ''
11435                 // Comma-separated list of WMS styles.
11436                 styles: '',
11437
11438                 // @option format: String = 'image/jpeg'
11439                 // WMS image format (use `'image/png'` for layers with transparency).
11440                 format: 'image/jpeg',
11441
11442                 // @option transparent: Boolean = false
11443                 // If `true`, the WMS service will return images with transparency.
11444                 transparent: false,
11445
11446                 // @option version: String = '1.1.1'
11447                 // Version of the WMS service to use
11448                 version: '1.1.1'
11449         },
11450
11451         options: {
11452                 // @option crs: CRS = null
11453                 // Coordinate Reference System to use for the WMS requests, defaults to
11454                 // map CRS. Don't change this if you're not sure what it means.
11455                 crs: null,
11456
11457                 // @option uppercase: Boolean = false
11458                 // If `true`, WMS request parameter keys will be uppercase.
11459                 uppercase: false
11460         },
11461
11462         initialize: function (url, options) {
11463
11464                 this._url = url;
11465
11466                 var wmsParams = extend({}, this.defaultWmsParams);
11467
11468                 // all keys that are not TileLayer options go to WMS params
11469                 for (var i in options) {
11470                         if (!(i in this.options)) {
11471                                 wmsParams[i] = options[i];
11472                         }
11473                 }
11474
11475                 options = setOptions(this, options);
11476
11477                 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && retina ? 2 : 1);
11478
11479                 this.wmsParams = wmsParams;
11480         },
11481
11482         onAdd: function (map) {
11483
11484                 this._crs = this.options.crs || map.options.crs;
11485                 this._wmsVersion = parseFloat(this.wmsParams.version);
11486
11487                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
11488                 this.wmsParams[projectionKey] = this._crs.code;
11489
11490                 TileLayer.prototype.onAdd.call(this, map);
11491         },
11492
11493         getTileUrl: function (coords) {
11494
11495                 var tileBounds = this._tileCoordsToBounds(coords),
11496                     nw = this._crs.project(tileBounds.getNorthWest()),
11497                     se = this._crs.project(tileBounds.getSouthEast()),
11498
11499                     bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
11500                             [se.y, nw.x, nw.y, se.x] :
11501                             [nw.x, se.y, se.x, nw.y]).join(','),
11502
11503                     url = TileLayer.prototype.getTileUrl.call(this, coords);
11504
11505                 return url +
11506                         getParamString(this.wmsParams, url, this.options.uppercase) +
11507                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
11508         },
11509
11510         // @method setParams(params: Object, noRedraw?: Boolean): this
11511         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
11512         setParams: function (params, noRedraw) {
11513
11514                 extend(this.wmsParams, params);
11515
11516                 if (!noRedraw) {
11517                         this.redraw();
11518                 }
11519
11520                 return this;
11521         }
11522 });
11523
11524
11525 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
11526 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
11527 function tileLayerWMS(url, options) {
11528         return new TileLayerWMS(url, options);
11529 }
11530
11531 TileLayer.WMS = TileLayerWMS;
11532 tileLayer.wms = tileLayerWMS;
11533
11534 /*
11535  * @class Renderer
11536  * @inherits Layer
11537  * @aka L.Renderer
11538  *
11539  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
11540  * DOM container of the renderer, its bounds, and its zoom animation.
11541  *
11542  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
11543  * itself can be added or removed to the map. All paths use a renderer, which can
11544  * be implicit (the map will decide the type of renderer and use it automatically)
11545  * or explicit (using the [`renderer`](#path-renderer) option of the path).
11546  *
11547  * Do not use this class directly, use `SVG` and `Canvas` instead.
11548  *
11549  * @event update: Event
11550  * Fired when the renderer updates its bounds, center and zoom, for example when
11551  * its map has moved
11552  */
11553
11554 var Renderer = Layer.extend({
11555
11556         // @section
11557         // @aka Renderer options
11558         options: {
11559                 // @option padding: Number = 0.1
11560                 // How much to extend the clip area around the map view (relative to its size)
11561                 // e.g. 0.1 would be 10% of map view in each direction
11562                 padding: 0.1
11563         },
11564
11565         initialize: function (options) {
11566                 setOptions(this, options);
11567                 stamp(this);
11568                 this._layers = this._layers || {};
11569         },
11570
11571         onAdd: function () {
11572                 if (!this._container) {
11573                         this._initContainer(); // defined by renderer implementations
11574
11575                         if (this._zoomAnimated) {
11576                                 addClass(this._container, 'leaflet-zoom-animated');
11577                         }
11578                 }
11579
11580                 this.getPane().appendChild(this._container);
11581                 this._update();
11582                 this.on('update', this._updatePaths, this);
11583         },
11584
11585         onRemove: function () {
11586                 this.off('update', this._updatePaths, this);
11587                 this._destroyContainer();
11588         },
11589
11590         getEvents: function () {
11591                 var events = {
11592                         viewreset: this._reset,
11593                         zoom: this._onZoom,
11594                         moveend: this._update,
11595                         zoomend: this._onZoomEnd
11596                 };
11597                 if (this._zoomAnimated) {
11598                         events.zoomanim = this._onAnimZoom;
11599                 }
11600                 return events;
11601         },
11602
11603         _onAnimZoom: function (ev) {
11604                 this._updateTransform(ev.center, ev.zoom);
11605         },
11606
11607         _onZoom: function () {
11608                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
11609         },
11610
11611         _updateTransform: function (center, zoom) {
11612                 var scale = this._map.getZoomScale(zoom, this._zoom),
11613                     position = getPosition(this._container),
11614                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
11615                     currentCenterPoint = this._map.project(this._center, zoom),
11616                     destCenterPoint = this._map.project(center, zoom),
11617                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
11618
11619                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
11620
11621                 if (any3d) {
11622                         setTransform(this._container, topLeftOffset, scale);
11623                 } else {
11624                         setPosition(this._container, topLeftOffset);
11625                 }
11626         },
11627
11628         _reset: function () {
11629                 this._update();
11630                 this._updateTransform(this._center, this._zoom);
11631
11632                 for (var id in this._layers) {
11633                         this._layers[id]._reset();
11634                 }
11635         },
11636
11637         _onZoomEnd: function () {
11638                 for (var id in this._layers) {
11639                         this._layers[id]._project();
11640                 }
11641         },
11642
11643         _updatePaths: function () {
11644                 for (var id in this._layers) {
11645                         this._layers[id]._update();
11646                 }
11647         },
11648
11649         _update: function () {
11650                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
11651                 // Subclasses are responsible of firing the 'update' event.
11652                 var p = this.options.padding,
11653                     size = this._map.getSize(),
11654                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
11655
11656                 this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
11657
11658                 this._center = this._map.getCenter();
11659                 this._zoom = this._map.getZoom();
11660         }
11661 });
11662
11663 /*
11664  * @class Canvas
11665  * @inherits Renderer
11666  * @aka L.Canvas
11667  *
11668  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
11669  * Inherits `Renderer`.
11670  *
11671  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
11672  * available in all web browsers, notably IE8, and overlapping geometries might
11673  * not display properly in some edge cases.
11674  *
11675  * @example
11676  *
11677  * Use Canvas by default for all paths in the map:
11678  *
11679  * ```js
11680  * var map = L.map('map', {
11681  *      renderer: L.canvas()
11682  * });
11683  * ```
11684  *
11685  * Use a Canvas renderer with extra padding for specific vector geometries:
11686  *
11687  * ```js
11688  * var map = L.map('map');
11689  * var myRenderer = L.canvas({ padding: 0.5 });
11690  * var line = L.polyline( coordinates, { renderer: myRenderer } );
11691  * var circle = L.circle( center, { renderer: myRenderer } );
11692  * ```
11693  */
11694
11695 var Canvas = Renderer.extend({
11696         getEvents: function () {
11697                 var events = Renderer.prototype.getEvents.call(this);
11698                 events.viewprereset = this._onViewPreReset;
11699                 return events;
11700         },
11701
11702         _onViewPreReset: function () {
11703                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
11704                 this._postponeUpdatePaths = true;
11705         },
11706
11707         onAdd: function () {
11708                 Renderer.prototype.onAdd.call(this);
11709
11710                 // Redraw vectors since canvas is cleared upon removal,
11711                 // in case of removing the renderer itself from the map.
11712                 this._draw();
11713         },
11714
11715         _initContainer: function () {
11716                 var container = this._container = document.createElement('canvas');
11717
11718                 on(container, 'mousemove', throttle(this._onMouseMove, 32, this), this);
11719                 on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
11720                 on(container, 'mouseout', this._handleMouseOut, this);
11721
11722                 this._ctx = container.getContext('2d');
11723         },
11724
11725         _destroyContainer: function () {
11726                 delete this._ctx;
11727                 remove(this._container);
11728                 off(this._container);
11729                 delete this._container;
11730         },
11731
11732         _updatePaths: function () {
11733                 if (this._postponeUpdatePaths) { return; }
11734
11735                 var layer;
11736                 this._redrawBounds = null;
11737                 for (var id in this._layers) {
11738                         layer = this._layers[id];
11739                         layer._update();
11740                 }
11741                 this._redraw();
11742         },
11743
11744         _update: function () {
11745                 if (this._map._animatingZoom && this._bounds) { return; }
11746
11747                 this._drawnLayers = {};
11748
11749                 Renderer.prototype._update.call(this);
11750
11751                 var b = this._bounds,
11752                     container = this._container,
11753                     size = b.getSize(),
11754                     m = retina ? 2 : 1;
11755
11756                 setPosition(container, b.min);
11757
11758                 // set canvas size (also clearing it); use double size on retina
11759                 container.width = m * size.x;
11760                 container.height = m * size.y;
11761                 container.style.width = size.x + 'px';
11762                 container.style.height = size.y + 'px';
11763
11764                 if (retina) {
11765                         this._ctx.scale(2, 2);
11766                 }
11767
11768                 // translate so we use the same path coordinates after canvas element moves
11769                 this._ctx.translate(-b.min.x, -b.min.y);
11770
11771                 // Tell paths to redraw themselves
11772                 this.fire('update');
11773         },
11774
11775         _reset: function () {
11776                 Renderer.prototype._reset.call(this);
11777
11778                 if (this._postponeUpdatePaths) {
11779                         this._postponeUpdatePaths = false;
11780                         this._updatePaths();
11781                 }
11782         },
11783
11784         _initPath: function (layer) {
11785                 this._updateDashArray(layer);
11786                 this._layers[stamp(layer)] = layer;
11787
11788                 var order = layer._order = {
11789                         layer: layer,
11790                         prev: this._drawLast,
11791                         next: null
11792                 };
11793                 if (this._drawLast) { this._drawLast.next = order; }
11794                 this._drawLast = order;
11795                 this._drawFirst = this._drawFirst || this._drawLast;
11796         },
11797
11798         _addPath: function (layer) {
11799                 this._requestRedraw(layer);
11800         },
11801
11802         _removePath: function (layer) {
11803                 var order = layer._order;
11804                 var next = order.next;
11805                 var prev = order.prev;
11806
11807                 if (next) {
11808                         next.prev = prev;
11809                 } else {
11810                         this._drawLast = prev;
11811                 }
11812                 if (prev) {
11813                         prev.next = next;
11814                 } else {
11815                         this._drawFirst = next;
11816                 }
11817
11818                 delete layer._order;
11819
11820                 delete this._layers[L.stamp(layer)];
11821
11822                 this._requestRedraw(layer);
11823         },
11824
11825         _updatePath: function (layer) {
11826                 // Redraw the union of the layer's old pixel
11827                 // bounds and the new pixel bounds.
11828                 this._extendRedrawBounds(layer);
11829                 layer._project();
11830                 layer._update();
11831                 // The redraw will extend the redraw bounds
11832                 // with the new pixel bounds.
11833                 this._requestRedraw(layer);
11834         },
11835
11836         _updateStyle: function (layer) {
11837                 this._updateDashArray(layer);
11838                 this._requestRedraw(layer);
11839         },
11840
11841         _updateDashArray: function (layer) {
11842                 if (layer.options.dashArray) {
11843                         var parts = layer.options.dashArray.split(','),
11844                             dashArray = [],
11845                             i;
11846                         for (i = 0; i < parts.length; i++) {
11847                                 dashArray.push(Number(parts[i]));
11848                         }
11849                         layer.options._dashArray = dashArray;
11850                 }
11851         },
11852
11853         _requestRedraw: function (layer) {
11854                 if (!this._map) { return; }
11855
11856                 this._extendRedrawBounds(layer);
11857                 this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
11858         },
11859
11860         _extendRedrawBounds: function (layer) {
11861                 if (layer._pxBounds) {
11862                         var padding = (layer.options.weight || 0) + 1;
11863                         this._redrawBounds = this._redrawBounds || new Bounds();
11864                         this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
11865                         this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
11866                 }
11867         },
11868
11869         _redraw: function () {
11870                 this._redrawRequest = null;
11871
11872                 if (this._redrawBounds) {
11873                         this._redrawBounds.min._floor();
11874                         this._redrawBounds.max._ceil();
11875                 }
11876
11877                 this._clear(); // clear layers in redraw bounds
11878                 this._draw(); // draw layers
11879
11880                 this._redrawBounds = null;
11881         },
11882
11883         _clear: function () {
11884                 var bounds = this._redrawBounds;
11885                 if (bounds) {
11886                         var size = bounds.getSize();
11887                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
11888                 } else {
11889                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
11890                 }
11891         },
11892
11893         _draw: function () {
11894                 var layer, bounds = this._redrawBounds;
11895                 this._ctx.save();
11896                 if (bounds) {
11897                         var size = bounds.getSize();
11898                         this._ctx.beginPath();
11899                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
11900                         this._ctx.clip();
11901                 }
11902
11903                 this._drawing = true;
11904
11905                 for (var order = this._drawFirst; order; order = order.next) {
11906                         layer = order.layer;
11907                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
11908                                 layer._updatePath();
11909                         }
11910                 }
11911
11912                 this._drawing = false;
11913
11914                 this._ctx.restore();  // Restore state before clipping.
11915         },
11916
11917         _updatePoly: function (layer, closed) {
11918                 if (!this._drawing) { return; }
11919
11920                 var i, j, len2, p,
11921                     parts = layer._parts,
11922                     len = parts.length,
11923                     ctx = this._ctx;
11924
11925                 if (!len) { return; }
11926
11927                 this._drawnLayers[layer._leaflet_id] = layer;
11928
11929                 ctx.beginPath();
11930
11931                 for (i = 0; i < len; i++) {
11932                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
11933                                 p = parts[i][j];
11934                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
11935                         }
11936                         if (closed) {
11937                                 ctx.closePath();
11938                         }
11939                 }
11940
11941                 this._fillStroke(ctx, layer);
11942
11943                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
11944         },
11945
11946         _updateCircle: function (layer) {
11947
11948                 if (!this._drawing || layer._empty()) { return; }
11949
11950                 var p = layer._point,
11951                     ctx = this._ctx,
11952                     r = layer._radius,
11953                     s = (layer._radiusY || r) / r;
11954
11955                 this._drawnLayers[layer._leaflet_id] = layer;
11956
11957                 if (s !== 1) {
11958                         ctx.save();
11959                         ctx.scale(1, s);
11960                 }
11961
11962                 ctx.beginPath();
11963                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
11964
11965                 if (s !== 1) {
11966                         ctx.restore();
11967                 }
11968
11969                 this._fillStroke(ctx, layer);
11970         },
11971
11972         _fillStroke: function (ctx, layer) {
11973                 var options = layer.options;
11974
11975                 if (options.fill) {
11976                         ctx.globalAlpha = options.fillOpacity;
11977                         ctx.fillStyle = options.fillColor || options.color;
11978                         ctx.fill(options.fillRule || 'evenodd');
11979                 }
11980
11981                 if (options.stroke && options.weight !== 0) {
11982                         if (ctx.setLineDash) {
11983                                 ctx.setLineDash(layer.options && layer.options._dashArray || []);
11984                         }
11985                         ctx.globalAlpha = options.opacity;
11986                         ctx.lineWidth = options.weight;
11987                         ctx.strokeStyle = options.color;
11988                         ctx.lineCap = options.lineCap;
11989                         ctx.lineJoin = options.lineJoin;
11990                         ctx.stroke();
11991                 }
11992         },
11993
11994         // Canvas obviously doesn't have mouse events for individual drawn objects,
11995         // so we emulate that by calculating what's under the mouse on mousemove/click manually
11996
11997         _onClick: function (e) {
11998                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
11999
12000                 for (var order = this._drawFirst; order; order = order.next) {
12001                         layer = order.layer;
12002                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
12003                                 clickedLayer = layer;
12004                         }
12005                 }
12006                 if (clickedLayer)  {
12007                         fakeStop(e);
12008                         this._fireEvent([clickedLayer], e);
12009                 }
12010         },
12011
12012         _onMouseMove: function (e) {
12013                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
12014
12015                 var point = this._map.mouseEventToLayerPoint(e);
12016                 this._handleMouseHover(e, point);
12017         },
12018
12019
12020         _handleMouseOut: function (e) {
12021                 var layer = this._hoveredLayer;
12022                 if (layer) {
12023                         // if we're leaving the layer, fire mouseout
12024                         removeClass(this._container, 'leaflet-interactive');
12025                         this._fireEvent([layer], e, 'mouseout');
12026                         this._hoveredLayer = null;
12027                 }
12028         },
12029
12030         _handleMouseHover: function (e, point) {
12031                 var layer, candidateHoveredLayer;
12032
12033                 for (var order = this._drawFirst; order; order = order.next) {
12034                         layer = order.layer;
12035                         if (layer.options.interactive && layer._containsPoint(point)) {
12036                                 candidateHoveredLayer = layer;
12037                         }
12038                 }
12039
12040                 if (candidateHoveredLayer !== this._hoveredLayer) {
12041                         this._handleMouseOut(e);
12042
12043                         if (candidateHoveredLayer) {
12044                                 addClass(this._container, 'leaflet-interactive'); // change cursor
12045                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
12046                                 this._hoveredLayer = candidateHoveredLayer;
12047                         }
12048                 }
12049
12050                 if (this._hoveredLayer) {
12051                         this._fireEvent([this._hoveredLayer], e);
12052                 }
12053         },
12054
12055         _fireEvent: function (layers, e, type) {
12056                 this._map._fireDOMEvent(e, type || e.type, layers);
12057         },
12058
12059         _bringToFront: function (layer) {
12060                 var order = layer._order;
12061                 var next = order.next;
12062                 var prev = order.prev;
12063
12064                 if (next) {
12065                         next.prev = prev;
12066                 } else {
12067                         // Already last
12068                         return;
12069                 }
12070                 if (prev) {
12071                         prev.next = next;
12072                 } else if (next) {
12073                         // Update first entry unless this is the
12074                         // signle entry
12075                         this._drawFirst = next;
12076                 }
12077
12078                 order.prev = this._drawLast;
12079                 this._drawLast.next = order;
12080
12081                 order.next = null;
12082                 this._drawLast = order;
12083
12084                 this._requestRedraw(layer);
12085         },
12086
12087         _bringToBack: function (layer) {
12088                 var order = layer._order;
12089                 var next = order.next;
12090                 var prev = order.prev;
12091
12092                 if (prev) {
12093                         prev.next = next;
12094                 } else {
12095                         // Already first
12096                         return;
12097                 }
12098                 if (next) {
12099                         next.prev = prev;
12100                 } else if (prev) {
12101                         // Update last entry unless this is the
12102                         // signle entry
12103                         this._drawLast = prev;
12104                 }
12105
12106                 order.prev = null;
12107
12108                 order.next = this._drawFirst;
12109                 this._drawFirst.prev = order;
12110                 this._drawFirst = order;
12111
12112                 this._requestRedraw(layer);
12113         }
12114 });
12115
12116 // @factory L.canvas(options?: Renderer options)
12117 // Creates a Canvas renderer with the given options.
12118 function canvas$1(options) {
12119         return canvas ? new Canvas(options) : null;
12120 }
12121
12122 /*
12123  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
12124  */
12125
12126
12127 var vmlCreate = (function () {
12128         try {
12129                 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
12130                 return function (name) {
12131                         return document.createElement('<lvml:' + name + ' class="lvml">');
12132                 };
12133         } catch (e) {
12134                 return function (name) {
12135                         return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
12136                 };
12137         }
12138 })();
12139
12140
12141 /*
12142  * @class SVG
12143  *
12144  * 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.
12145  *
12146  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
12147  * with old versions of Internet Explorer.
12148  */
12149
12150 // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
12151 var vmlMixin = {
12152
12153         _initContainer: function () {
12154                 this._container = create$1('div', 'leaflet-vml-container');
12155         },
12156
12157         _update: function () {
12158                 if (this._map._animatingZoom) { return; }
12159                 Renderer.prototype._update.call(this);
12160                 this.fire('update');
12161         },
12162
12163         _initPath: function (layer) {
12164                 var container = layer._container = vmlCreate('shape');
12165
12166                 addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
12167
12168                 container.coordsize = '1 1';
12169
12170                 layer._path = vmlCreate('path');
12171                 container.appendChild(layer._path);
12172
12173                 this._updateStyle(layer);
12174                 this._layers[stamp(layer)] = layer;
12175         },
12176
12177         _addPath: function (layer) {
12178                 var container = layer._container;
12179                 this._container.appendChild(container);
12180
12181                 if (layer.options.interactive) {
12182                         layer.addInteractiveTarget(container);
12183                 }
12184         },
12185
12186         _removePath: function (layer) {
12187                 var container = layer._container;
12188                 remove(container);
12189                 layer.removeInteractiveTarget(container);
12190                 delete this._layers[stamp(layer)];
12191         },
12192
12193         _updateStyle: function (layer) {
12194                 var stroke = layer._stroke,
12195                     fill = layer._fill,
12196                     options = layer.options,
12197                     container = layer._container;
12198
12199                 container.stroked = !!options.stroke;
12200                 container.filled = !!options.fill;
12201
12202                 if (options.stroke) {
12203                         if (!stroke) {
12204                                 stroke = layer._stroke = vmlCreate('stroke');
12205                         }
12206                         container.appendChild(stroke);
12207                         stroke.weight = options.weight + 'px';
12208                         stroke.color = options.color;
12209                         stroke.opacity = options.opacity;
12210
12211                         if (options.dashArray) {
12212                                 stroke.dashStyle = isArray(options.dashArray) ?
12213                                     options.dashArray.join(' ') :
12214                                     options.dashArray.replace(/( *, *)/g, ' ');
12215                         } else {
12216                                 stroke.dashStyle = '';
12217                         }
12218                         stroke.endcap = options.lineCap.replace('butt', 'flat');
12219                         stroke.joinstyle = options.lineJoin;
12220
12221                 } else if (stroke) {
12222                         container.removeChild(stroke);
12223                         layer._stroke = null;
12224                 }
12225
12226                 if (options.fill) {
12227                         if (!fill) {
12228                                 fill = layer._fill = vmlCreate('fill');
12229                         }
12230                         container.appendChild(fill);
12231                         fill.color = options.fillColor || options.color;
12232                         fill.opacity = options.fillOpacity;
12233
12234                 } else if (fill) {
12235                         container.removeChild(fill);
12236                         layer._fill = null;
12237                 }
12238         },
12239
12240         _updateCircle: function (layer) {
12241                 var p = layer._point.round(),
12242                     r = Math.round(layer._radius),
12243                     r2 = Math.round(layer._radiusY || r);
12244
12245                 this._setPath(layer, layer._empty() ? 'M0 0' :
12246                                 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
12247         },
12248
12249         _setPath: function (layer, path) {
12250                 layer._path.v = path;
12251         },
12252
12253         _bringToFront: function (layer) {
12254                 toFront(layer._container);
12255         },
12256
12257         _bringToBack: function (layer) {
12258                 toBack(layer._container);
12259         }
12260 };
12261
12262 var create$2 = vml ? vmlCreate : svgCreate;
12263
12264 /*
12265  * @class SVG
12266  * @inherits Renderer
12267  * @aka L.SVG
12268  *
12269  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
12270  * Inherits `Renderer`.
12271  *
12272  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
12273  * available in all web browsers, notably Android 2.x and 3.x.
12274  *
12275  * Although SVG is not available on IE7 and IE8, these browsers support
12276  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
12277  * (a now deprecated technology), and the SVG renderer will fall back to VML in
12278  * this case.
12279  *
12280  * @example
12281  *
12282  * Use SVG by default for all paths in the map:
12283  *
12284  * ```js
12285  * var map = L.map('map', {
12286  *      renderer: L.svg()
12287  * });
12288  * ```
12289  *
12290  * Use a SVG renderer with extra padding for specific vector geometries:
12291  *
12292  * ```js
12293  * var map = L.map('map');
12294  * var myRenderer = L.svg({ padding: 0.5 });
12295  * var line = L.polyline( coordinates, { renderer: myRenderer } );
12296  * var circle = L.circle( center, { renderer: myRenderer } );
12297  * ```
12298  */
12299
12300 var SVG = Renderer.extend({
12301
12302         getEvents: function () {
12303                 var events = Renderer.prototype.getEvents.call(this);
12304                 events.zoomstart = this._onZoomStart;
12305                 return events;
12306         },
12307
12308         _initContainer: function () {
12309                 this._container = create$2('svg');
12310
12311                 // makes it possible to click through svg root; we'll reset it back in individual paths
12312                 this._container.setAttribute('pointer-events', 'none');
12313
12314                 this._rootGroup = create$2('g');
12315                 this._container.appendChild(this._rootGroup);
12316         },
12317
12318         _destroyContainer: function () {
12319                 remove(this._container);
12320                 off(this._container);
12321                 delete this._container;
12322                 delete this._rootGroup;
12323         },
12324
12325         _onZoomStart: function () {
12326                 // Drag-then-pinch interactions might mess up the center and zoom.
12327                 // In this case, the easiest way to prevent this is re-do the renderer
12328                 //   bounds and padding when the zooming starts.
12329                 this._update();
12330         },
12331
12332         _update: function () {
12333                 if (this._map._animatingZoom && this._bounds) { return; }
12334
12335                 Renderer.prototype._update.call(this);
12336
12337                 var b = this._bounds,
12338                     size = b.getSize(),
12339                     container = this._container;
12340
12341                 // set size of svg-container if changed
12342                 if (!this._svgSize || !this._svgSize.equals(size)) {
12343                         this._svgSize = size;
12344                         container.setAttribute('width', size.x);
12345                         container.setAttribute('height', size.y);
12346                 }
12347
12348                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
12349                 setPosition(container, b.min);
12350                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
12351
12352                 this.fire('update');
12353         },
12354
12355         // methods below are called by vector layers implementations
12356
12357         _initPath: function (layer) {
12358                 var path = layer._path = create$2('path');
12359
12360                 // @namespace Path
12361                 // @option className: String = null
12362                 // Custom class name set on an element. Only for SVG renderer.
12363                 if (layer.options.className) {
12364                         addClass(path, layer.options.className);
12365                 }
12366
12367                 if (layer.options.interactive) {
12368                         addClass(path, 'leaflet-interactive');
12369                 }
12370
12371                 this._updateStyle(layer);
12372                 this._layers[stamp(layer)] = layer;
12373         },
12374
12375         _addPath: function (layer) {
12376                 if (!this._rootGroup) { this._initContainer(); }
12377                 this._rootGroup.appendChild(layer._path);
12378                 layer.addInteractiveTarget(layer._path);
12379         },
12380
12381         _removePath: function (layer) {
12382                 remove(layer._path);
12383                 layer.removeInteractiveTarget(layer._path);
12384                 delete this._layers[stamp(layer)];
12385         },
12386
12387         _updatePath: function (layer) {
12388                 layer._project();
12389                 layer._update();
12390         },
12391
12392         _updateStyle: function (layer) {
12393                 var path = layer._path,
12394                     options = layer.options;
12395
12396                 if (!path) { return; }
12397
12398                 if (options.stroke) {
12399                         path.setAttribute('stroke', options.color);
12400                         path.setAttribute('stroke-opacity', options.opacity);
12401                         path.setAttribute('stroke-width', options.weight);
12402                         path.setAttribute('stroke-linecap', options.lineCap);
12403                         path.setAttribute('stroke-linejoin', options.lineJoin);
12404
12405                         if (options.dashArray) {
12406                                 path.setAttribute('stroke-dasharray', options.dashArray);
12407                         } else {
12408                                 path.removeAttribute('stroke-dasharray');
12409                         }
12410
12411                         if (options.dashOffset) {
12412                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
12413                         } else {
12414                                 path.removeAttribute('stroke-dashoffset');
12415                         }
12416                 } else {
12417                         path.setAttribute('stroke', 'none');
12418                 }
12419
12420                 if (options.fill) {
12421                         path.setAttribute('fill', options.fillColor || options.color);
12422                         path.setAttribute('fill-opacity', options.fillOpacity);
12423                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
12424                 } else {
12425                         path.setAttribute('fill', 'none');
12426                 }
12427         },
12428
12429         _updatePoly: function (layer, closed) {
12430                 this._setPath(layer, pointsToPath(layer._parts, closed));
12431         },
12432
12433         _updateCircle: function (layer) {
12434                 var p = layer._point,
12435                     r = layer._radius,
12436                     r2 = layer._radiusY || r,
12437                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
12438
12439                 // drawing a circle with two half-arcs
12440                 var d = layer._empty() ? 'M0 0' :
12441                                 'M' + (p.x - r) + ',' + p.y +
12442                                 arc + (r * 2) + ',0 ' +
12443                                 arc + (-r * 2) + ',0 ';
12444
12445                 this._setPath(layer, d);
12446         },
12447
12448         _setPath: function (layer, path) {
12449                 layer._path.setAttribute('d', path);
12450         },
12451
12452         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
12453         _bringToFront: function (layer) {
12454                 toFront(layer._path);
12455         },
12456
12457         _bringToBack: function (layer) {
12458                 toBack(layer._path);
12459         }
12460 });
12461
12462 if (vml) {
12463         SVG.include(vmlMixin);
12464 }
12465
12466 // @factory L.svg(options?: Renderer options)
12467 // Creates a SVG renderer with the given options.
12468 function svg$1(options) {
12469         return svg || vml ? new SVG(options) : null;
12470 }
12471
12472 Map.include({
12473         // @namespace Map; @method getRenderer(layer: Path): Renderer
12474         // Returns the instance of `Renderer` that should be used to render the given
12475         // `Path`. It will ensure that the `renderer` options of the map and paths
12476         // are respected, and that the renderers do exist on the map.
12477         getRenderer: function (layer) {
12478                 // @namespace Path; @option renderer: Renderer
12479                 // Use this specific instance of `Renderer` for this path. Takes
12480                 // precedence over the map's [default renderer](#map-renderer).
12481                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
12482
12483                 if (!renderer) {
12484                         // @namespace Map; @option preferCanvas: Boolean = false
12485                         // Whether `Path`s should be rendered on a `Canvas` renderer.
12486                         // By default, all `Path`s are rendered in a `SVG` renderer.
12487                         renderer = this._renderer = (this.options.preferCanvas && canvas$1()) || svg$1();
12488                 }
12489
12490                 if (!this.hasLayer(renderer)) {
12491                         this.addLayer(renderer);
12492                 }
12493                 return renderer;
12494         },
12495
12496         _getPaneRenderer: function (name) {
12497                 if (name === 'overlayPane' || name === undefined) {
12498                         return false;
12499                 }
12500
12501                 var renderer = this._paneRenderers[name];
12502                 if (renderer === undefined) {
12503                         renderer = (SVG && svg$1({pane: name})) || (Canvas && canvas$1({pane: name}));
12504                         this._paneRenderers[name] = renderer;
12505                 }
12506                 return renderer;
12507         }
12508 });
12509
12510 /*
12511  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
12512  */
12513
12514 /*
12515  * @class Rectangle
12516  * @aka L.Retangle
12517  * @inherits Polygon
12518  *
12519  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
12520  *
12521  * @example
12522  *
12523  * ```js
12524  * // define rectangle geographical bounds
12525  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
12526  *
12527  * // create an orange rectangle
12528  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
12529  *
12530  * // zoom the map to the rectangle bounds
12531  * map.fitBounds(bounds);
12532  * ```
12533  *
12534  */
12535
12536
12537 var Rectangle = Polygon.extend({
12538         initialize: function (latLngBounds, options) {
12539                 Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
12540         },
12541
12542         // @method setBounds(latLngBounds: LatLngBounds): this
12543         // Redraws the rectangle with the passed bounds.
12544         setBounds: function (latLngBounds) {
12545                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
12546         },
12547
12548         _boundsToLatLngs: function (latLngBounds) {
12549                 latLngBounds = toLatLngBounds(latLngBounds);
12550                 return [
12551                         latLngBounds.getSouthWest(),
12552                         latLngBounds.getNorthWest(),
12553                         latLngBounds.getNorthEast(),
12554                         latLngBounds.getSouthEast()
12555                 ];
12556         }
12557 });
12558
12559
12560 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
12561 function rectangle(latLngBounds, options) {
12562         return new Rectangle(latLngBounds, options);
12563 }
12564
12565 SVG.create = create$2;
12566 SVG.pointsToPath = pointsToPath;
12567
12568 GeoJSON.geometryToLayer = geometryToLayer;
12569 GeoJSON.coordsToLatLng = coordsToLatLng;
12570 GeoJSON.coordsToLatLngs = coordsToLatLngs;
12571 GeoJSON.latLngToCoords = latLngToCoords;
12572 GeoJSON.latLngsToCoords = latLngsToCoords;
12573 GeoJSON.getFeature = getFeature;
12574 GeoJSON.asFeature = asFeature;
12575
12576 /*
12577  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
12578  * (zoom to a selected bounding box), enabled by default.
12579  */
12580
12581 // @namespace Map
12582 // @section Interaction Options
12583 Map.mergeOptions({
12584         // @option boxZoom: Boolean = true
12585         // Whether the map can be zoomed to a rectangular area specified by
12586         // dragging the mouse while pressing the shift key.
12587         boxZoom: true
12588 });
12589
12590 var BoxZoom = Handler.extend({
12591         initialize: function (map) {
12592                 this._map = map;
12593                 this._container = map._container;
12594                 this._pane = map._panes.overlayPane;
12595                 this._resetStateTimeout = 0;
12596                 map.on('unload', this._destroy, this);
12597         },
12598
12599         addHooks: function () {
12600                 on(this._container, 'mousedown', this._onMouseDown, this);
12601         },
12602
12603         removeHooks: function () {
12604                 off(this._container, 'mousedown', this._onMouseDown, this);
12605         },
12606
12607         moved: function () {
12608                 return this._moved;
12609         },
12610
12611         _destroy: function () {
12612                 remove(this._pane);
12613                 delete this._pane;
12614         },
12615
12616         _resetState: function () {
12617                 this._resetStateTimeout = 0;
12618                 this._moved = false;
12619         },
12620
12621         _clearDeferredResetState: function () {
12622                 if (this._resetStateTimeout !== 0) {
12623                         clearTimeout(this._resetStateTimeout);
12624                         this._resetStateTimeout = 0;
12625                 }
12626         },
12627
12628         _onMouseDown: function (e) {
12629                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
12630
12631                 // Clear the deferred resetState if it hasn't executed yet, otherwise it
12632                 // will interrupt the interaction and orphan a box element in the container.
12633                 this._clearDeferredResetState();
12634                 this._resetState();
12635
12636                 disableTextSelection();
12637                 disableImageDrag();
12638
12639                 this._startPoint = this._map.mouseEventToContainerPoint(e);
12640
12641                 on(document, {
12642                         contextmenu: stop,
12643                         mousemove: this._onMouseMove,
12644                         mouseup: this._onMouseUp,
12645                         keydown: this._onKeyDown
12646                 }, this);
12647         },
12648
12649         _onMouseMove: function (e) {
12650                 if (!this._moved) {
12651                         this._moved = true;
12652
12653                         this._box = create$1('div', 'leaflet-zoom-box', this._container);
12654                         addClass(this._container, 'leaflet-crosshair');
12655
12656                         this._map.fire('boxzoomstart');
12657                 }
12658
12659                 this._point = this._map.mouseEventToContainerPoint(e);
12660
12661                 var bounds = new Bounds(this._point, this._startPoint),
12662                     size = bounds.getSize();
12663
12664                 setPosition(this._box, bounds.min);
12665
12666                 this._box.style.width  = size.x + 'px';
12667                 this._box.style.height = size.y + 'px';
12668         },
12669
12670         _finish: function () {
12671                 if (this._moved) {
12672                         remove(this._box);
12673                         removeClass(this._container, 'leaflet-crosshair');
12674                 }
12675
12676                 enableTextSelection();
12677                 enableImageDrag();
12678
12679                 off(document, {
12680                         contextmenu: stop,
12681                         mousemove: this._onMouseMove,
12682                         mouseup: this._onMouseUp,
12683                         keydown: this._onKeyDown
12684                 }, this);
12685         },
12686
12687         _onMouseUp: function (e) {
12688                 if ((e.which !== 1) && (e.button !== 1)) { return; }
12689
12690                 this._finish();
12691
12692                 if (!this._moved) { return; }
12693                 // Postpone to next JS tick so internal click event handling
12694                 // still see it as "moved".
12695                 this._clearDeferredResetState();
12696                 this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);
12697
12698                 var bounds = new LatLngBounds(
12699                         this._map.containerPointToLatLng(this._startPoint),
12700                         this._map.containerPointToLatLng(this._point));
12701
12702                 this._map
12703                         .fitBounds(bounds)
12704                         .fire('boxzoomend', {boxZoomBounds: bounds});
12705         },
12706
12707         _onKeyDown: function (e) {
12708                 if (e.keyCode === 27) {
12709                         this._finish();
12710                 }
12711         }
12712 });
12713
12714 // @section Handlers
12715 // @property boxZoom: Handler
12716 // Box (shift-drag with mouse) zoom handler.
12717 Map.addInitHook('addHandler', 'boxZoom', BoxZoom);
12718
12719 /*
12720  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
12721  */
12722
12723 // @namespace Map
12724 // @section Interaction Options
12725
12726 Map.mergeOptions({
12727         // @option doubleClickZoom: Boolean|String = true
12728         // Whether the map can be zoomed in by double clicking on it and
12729         // zoomed out by double clicking while holding shift. If passed
12730         // `'center'`, double-click zoom will zoom to the center of the
12731         //  view regardless of where the mouse was.
12732         doubleClickZoom: true
12733 });
12734
12735 var DoubleClickZoom = Handler.extend({
12736         addHooks: function () {
12737                 this._map.on('dblclick', this._onDoubleClick, this);
12738         },
12739
12740         removeHooks: function () {
12741                 this._map.off('dblclick', this._onDoubleClick, this);
12742         },
12743
12744         _onDoubleClick: function (e) {
12745                 var map = this._map,
12746                     oldZoom = map.getZoom(),
12747                     delta = map.options.zoomDelta,
12748                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
12749
12750                 if (map.options.doubleClickZoom === 'center') {
12751                         map.setZoom(zoom);
12752                 } else {
12753                         map.setZoomAround(e.containerPoint, zoom);
12754                 }
12755         }
12756 });
12757
12758 // @section Handlers
12759 //
12760 // Map properties include interaction handlers that allow you to control
12761 // interaction behavior in runtime, enabling or disabling certain features such
12762 // as dragging or touch zoom (see `Handler` methods). For example:
12763 //
12764 // ```js
12765 // map.doubleClickZoom.disable();
12766 // ```
12767 //
12768 // @property doubleClickZoom: Handler
12769 // Double click zoom handler.
12770 Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);
12771
12772 /*
12773  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
12774  */
12775
12776 // @namespace Map
12777 // @section Interaction Options
12778 Map.mergeOptions({
12779         // @option dragging: Boolean = true
12780         // Whether the map be draggable with mouse/touch or not.
12781         dragging: true,
12782
12783         // @section Panning Inertia Options
12784         // @option inertia: Boolean = *
12785         // If enabled, panning of the map will have an inertia effect where
12786         // the map builds momentum while dragging and continues moving in
12787         // the same direction for some time. Feels especially nice on touch
12788         // devices. Enabled by default unless running on old Android devices.
12789         inertia: !android23,
12790
12791         // @option inertiaDeceleration: Number = 3000
12792         // The rate with which the inertial movement slows down, in pixels/second².
12793         inertiaDeceleration: 3400, // px/s^2
12794
12795         // @option inertiaMaxSpeed: Number = Infinity
12796         // Max speed of the inertial movement, in pixels/second.
12797         inertiaMaxSpeed: Infinity, // px/s
12798
12799         // @option easeLinearity: Number = 0.2
12800         easeLinearity: 0.2,
12801
12802         // TODO refactor, move to CRS
12803         // @option worldCopyJump: Boolean = false
12804         // With this option enabled, the map tracks when you pan to another "copy"
12805         // of the world and seamlessly jumps to the original one so that all overlays
12806         // like markers and vector layers are still visible.
12807         worldCopyJump: false,
12808
12809         // @option maxBoundsViscosity: Number = 0.0
12810         // If `maxBounds` is set, this option will control how solid the bounds
12811         // are when dragging the map around. The default value of `0.0` allows the
12812         // user to drag outside the bounds at normal speed, higher values will
12813         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
12814         // solid, preventing the user from dragging outside the bounds.
12815         maxBoundsViscosity: 0.0
12816 });
12817
12818 var Drag = Handler.extend({
12819         addHooks: function () {
12820                 if (!this._draggable) {
12821                         var map = this._map;
12822
12823                         this._draggable = new Draggable(map._mapPane, map._container);
12824
12825                         this._draggable.on({
12826                                 dragstart: this._onDragStart,
12827                                 drag: this._onDrag,
12828                                 dragend: this._onDragEnd
12829                         }, this);
12830
12831                         this._draggable.on('predrag', this._onPreDragLimit, this);
12832                         if (map.options.worldCopyJump) {
12833                                 this._draggable.on('predrag', this._onPreDragWrap, this);
12834                                 map.on('zoomend', this._onZoomEnd, this);
12835
12836                                 map.whenReady(this._onZoomEnd, this);
12837                         }
12838                 }
12839                 addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
12840                 this._draggable.enable();
12841                 this._positions = [];
12842                 this._times = [];
12843         },
12844
12845         removeHooks: function () {
12846                 removeClass(this._map._container, 'leaflet-grab');
12847                 removeClass(this._map._container, 'leaflet-touch-drag');
12848                 this._draggable.disable();
12849         },
12850
12851         moved: function () {
12852                 return this._draggable && this._draggable._moved;
12853         },
12854
12855         moving: function () {
12856                 return this._draggable && this._draggable._moving;
12857         },
12858
12859         _onDragStart: function () {
12860                 var map = this._map;
12861
12862                 map._stop();
12863                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
12864                         var bounds = toLatLngBounds(this._map.options.maxBounds);
12865
12866                         this._offsetLimit = toBounds(
12867                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
12868                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
12869                                         .add(this._map.getSize()));
12870
12871                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
12872                 } else {
12873                         this._offsetLimit = null;
12874                 }
12875
12876                 map
12877                     .fire('movestart')
12878                     .fire('dragstart');
12879
12880                 if (map.options.inertia) {
12881                         this._positions = [];
12882                         this._times = [];
12883                 }
12884         },
12885
12886         _onDrag: function (e) {
12887                 if (this._map.options.inertia) {
12888                         var time = this._lastTime = +new Date(),
12889                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
12890
12891                         this._positions.push(pos);
12892                         this._times.push(time);
12893
12894                         if (time - this._times[0] > 50) {
12895                                 this._positions.shift();
12896                                 this._times.shift();
12897                         }
12898                 }
12899
12900                 this._map
12901                     .fire('move', e)
12902                     .fire('drag', e);
12903         },
12904
12905         _onZoomEnd: function () {
12906                 var pxCenter = this._map.getSize().divideBy(2),
12907                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
12908
12909                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
12910                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
12911         },
12912
12913         _viscousLimit: function (value, threshold) {
12914                 return value - (value - threshold) * this._viscosity;
12915         },
12916
12917         _onPreDragLimit: function () {
12918                 if (!this._viscosity || !this._offsetLimit) { return; }
12919
12920                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
12921
12922                 var limit = this._offsetLimit;
12923                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
12924                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
12925                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
12926                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
12927
12928                 this._draggable._newPos = this._draggable._startPos.add(offset);
12929         },
12930
12931         _onPreDragWrap: function () {
12932                 // TODO refactor to be able to adjust map pane position after zoom
12933                 var worldWidth = this._worldWidth,
12934                     halfWidth = Math.round(worldWidth / 2),
12935                     dx = this._initialWorldOffset,
12936                     x = this._draggable._newPos.x,
12937                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
12938                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
12939                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
12940
12941                 this._draggable._absPos = this._draggable._newPos.clone();
12942                 this._draggable._newPos.x = newX;
12943         },
12944
12945         _onDragEnd: function (e) {
12946                 var map = this._map,
12947                     options = map.options,
12948
12949                     noInertia = !options.inertia || this._times.length < 2;
12950
12951                 map.fire('dragend', e);
12952
12953                 if (noInertia) {
12954                         map.fire('moveend');
12955
12956                 } else {
12957
12958                         var direction = this._lastPos.subtract(this._positions[0]),
12959                             duration = (this._lastTime - this._times[0]) / 1000,
12960                             ease = options.easeLinearity,
12961
12962                             speedVector = direction.multiplyBy(ease / duration),
12963                             speed = speedVector.distanceTo([0, 0]),
12964
12965                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
12966                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
12967
12968                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
12969                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
12970
12971                         if (!offset.x && !offset.y) {
12972                                 map.fire('moveend');
12973
12974                         } else {
12975                                 offset = map._limitOffset(offset, map.options.maxBounds);
12976
12977                                 requestAnimFrame(function () {
12978                                         map.panBy(offset, {
12979                                                 duration: decelerationDuration,
12980                                                 easeLinearity: ease,
12981                                                 noMoveStart: true,
12982                                                 animate: true
12983                                         });
12984                                 });
12985                         }
12986                 }
12987         }
12988 });
12989
12990 // @section Handlers
12991 // @property dragging: Handler
12992 // Map dragging handler (by both mouse and touch).
12993 Map.addInitHook('addHandler', 'dragging', Drag);
12994
12995 /*
12996  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
12997  */
12998
12999 // @namespace Map
13000 // @section Keyboard Navigation Options
13001 Map.mergeOptions({
13002         // @option keyboard: Boolean = true
13003         // Makes the map focusable and allows users to navigate the map with keyboard
13004         // arrows and `+`/`-` keys.
13005         keyboard: true,
13006
13007         // @option keyboardPanDelta: Number = 80
13008         // Amount of pixels to pan when pressing an arrow key.
13009         keyboardPanDelta: 80
13010 });
13011
13012 var Keyboard = Handler.extend({
13013
13014         keyCodes: {
13015                 left:    [37],
13016                 right:   [39],
13017                 down:    [40],
13018                 up:      [38],
13019                 zoomIn:  [187, 107, 61, 171],
13020                 zoomOut: [189, 109, 54, 173]
13021         },
13022
13023         initialize: function (map) {
13024                 this._map = map;
13025
13026                 this._setPanDelta(map.options.keyboardPanDelta);
13027                 this._setZoomDelta(map.options.zoomDelta);
13028         },
13029
13030         addHooks: function () {
13031                 var container = this._map._container;
13032
13033                 // make the container focusable by tabbing
13034                 if (container.tabIndex <= 0) {
13035                         container.tabIndex = '0';
13036                 }
13037
13038                 on(container, {
13039                         focus: this._onFocus,
13040                         blur: this._onBlur,
13041                         mousedown: this._onMouseDown
13042                 }, this);
13043
13044                 this._map.on({
13045                         focus: this._addHooks,
13046                         blur: this._removeHooks
13047                 }, this);
13048         },
13049
13050         removeHooks: function () {
13051                 this._removeHooks();
13052
13053                 off(this._map._container, {
13054                         focus: this._onFocus,
13055                         blur: this._onBlur,
13056                         mousedown: this._onMouseDown
13057                 }, this);
13058
13059                 this._map.off({
13060                         focus: this._addHooks,
13061                         blur: this._removeHooks
13062                 }, this);
13063         },
13064
13065         _onMouseDown: function () {
13066                 if (this._focused) { return; }
13067
13068                 var body = document.body,
13069                     docEl = document.documentElement,
13070                     top = body.scrollTop || docEl.scrollTop,
13071                     left = body.scrollLeft || docEl.scrollLeft;
13072
13073                 this._map._container.focus();
13074
13075                 window.scrollTo(left, top);
13076         },
13077
13078         _onFocus: function () {
13079                 this._focused = true;
13080                 this._map.fire('focus');
13081         },
13082
13083         _onBlur: function () {
13084                 this._focused = false;
13085                 this._map.fire('blur');
13086         },
13087
13088         _setPanDelta: function (panDelta) {
13089                 var keys = this._panKeys = {},
13090                     codes = this.keyCodes,
13091                     i, len;
13092
13093                 for (i = 0, len = codes.left.length; i < len; i++) {
13094                         keys[codes.left[i]] = [-1 * panDelta, 0];
13095                 }
13096                 for (i = 0, len = codes.right.length; i < len; i++) {
13097                         keys[codes.right[i]] = [panDelta, 0];
13098                 }
13099                 for (i = 0, len = codes.down.length; i < len; i++) {
13100                         keys[codes.down[i]] = [0, panDelta];
13101                 }
13102                 for (i = 0, len = codes.up.length; i < len; i++) {
13103                         keys[codes.up[i]] = [0, -1 * panDelta];
13104                 }
13105         },
13106
13107         _setZoomDelta: function (zoomDelta) {
13108                 var keys = this._zoomKeys = {},
13109                     codes = this.keyCodes,
13110                     i, len;
13111
13112                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
13113                         keys[codes.zoomIn[i]] = zoomDelta;
13114                 }
13115                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
13116                         keys[codes.zoomOut[i]] = -zoomDelta;
13117                 }
13118         },
13119
13120         _addHooks: function () {
13121                 on(document, 'keydown', this._onKeyDown, this);
13122         },
13123
13124         _removeHooks: function () {
13125                 off(document, 'keydown', this._onKeyDown, this);
13126         },
13127
13128         _onKeyDown: function (e) {
13129                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
13130
13131                 var key = e.keyCode,
13132                     map = this._map,
13133                     offset;
13134
13135                 if (key in this._panKeys) {
13136
13137                         if (map._panAnim && map._panAnim._inProgress) { return; }
13138
13139                         offset = this._panKeys[key];
13140                         if (e.shiftKey) {
13141                                 offset = toPoint(offset).multiplyBy(3);
13142                         }
13143
13144                         map.panBy(offset);
13145
13146                         if (map.options.maxBounds) {
13147                                 map.panInsideBounds(map.options.maxBounds);
13148                         }
13149
13150                 } else if (key in this._zoomKeys) {
13151                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
13152
13153                 } else if (key === 27 && map._popup) {
13154                         map.closePopup();
13155
13156                 } else {
13157                         return;
13158                 }
13159
13160                 stop(e);
13161         }
13162 });
13163
13164 // @section Handlers
13165 // @section Handlers
13166 // @property keyboard: Handler
13167 // Keyboard navigation handler.
13168 Map.addInitHook('addHandler', 'keyboard', Keyboard);
13169
13170 /*
13171  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
13172  */
13173
13174 // @namespace Map
13175 // @section Interaction Options
13176 Map.mergeOptions({
13177         // @section Mousewheel options
13178         // @option scrollWheelZoom: Boolean|String = true
13179         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
13180         // it will zoom to the center of the view regardless of where the mouse was.
13181         scrollWheelZoom: true,
13182
13183         // @option wheelDebounceTime: Number = 40
13184         // Limits the rate at which a wheel can fire (in milliseconds). By default
13185         // user can't zoom via wheel more often than once per 40 ms.
13186         wheelDebounceTime: 40,
13187
13188         // @option wheelPxPerZoomLevel: Number = 60
13189         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
13190         // mean a change of one full zoom level. Smaller values will make wheel-zooming
13191         // faster (and vice versa).
13192         wheelPxPerZoomLevel: 60
13193 });
13194
13195 var ScrollWheelZoom = Handler.extend({
13196         addHooks: function () {
13197                 on(this._map._container, 'mousewheel', this._onWheelScroll, this);
13198
13199                 this._delta = 0;
13200         },
13201
13202         removeHooks: function () {
13203                 off(this._map._container, 'mousewheel', this._onWheelScroll, this);
13204         },
13205
13206         _onWheelScroll: function (e) {
13207                 var delta = getWheelDelta(e);
13208
13209                 var debounce = this._map.options.wheelDebounceTime;
13210
13211                 this._delta += delta;
13212                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
13213
13214                 if (!this._startTime) {
13215                         this._startTime = +new Date();
13216                 }
13217
13218                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
13219
13220                 clearTimeout(this._timer);
13221                 this._timer = setTimeout(bind(this._performZoom, this), left);
13222
13223                 stop(e);
13224         },
13225
13226         _performZoom: function () {
13227                 var map = this._map,
13228                     zoom = map.getZoom(),
13229                     snap = this._map.options.zoomSnap || 0;
13230
13231                 map._stop(); // stop panning and fly animations if any
13232
13233                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
13234                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
13235                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
13236                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
13237                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
13238
13239                 this._delta = 0;
13240                 this._startTime = null;
13241
13242                 if (!delta) { return; }
13243
13244                 if (map.options.scrollWheelZoom === 'center') {
13245                         map.setZoom(zoom + delta);
13246                 } else {
13247                         map.setZoomAround(this._lastMousePos, zoom + delta);
13248                 }
13249         }
13250 });
13251
13252 // @section Handlers
13253 // @property scrollWheelZoom: Handler
13254 // Scroll wheel zoom handler.
13255 Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);
13256
13257 /*
13258  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
13259  */
13260
13261 // @namespace Map
13262 // @section Interaction Options
13263 Map.mergeOptions({
13264         // @section Touch interaction options
13265         // @option tap: Boolean = true
13266         // Enables mobile hacks for supporting instant taps (fixing 200ms click
13267         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
13268         tap: true,
13269
13270         // @option tapTolerance: Number = 15
13271         // The max number of pixels a user can shift his finger during touch
13272         // for it to be considered a valid tap.
13273         tapTolerance: 15
13274 });
13275
13276 var Tap = Handler.extend({
13277         addHooks: function () {
13278                 on(this._map._container, 'touchstart', this._onDown, this);
13279         },
13280
13281         removeHooks: function () {
13282                 off(this._map._container, 'touchstart', this._onDown, this);
13283         },
13284
13285         _onDown: function (e) {
13286                 if (!e.touches) { return; }
13287
13288                 preventDefault(e);
13289
13290                 this._fireClick = true;
13291
13292                 // don't simulate click or track longpress if more than 1 touch
13293                 if (e.touches.length > 1) {
13294                         this._fireClick = false;
13295                         clearTimeout(this._holdTimeout);
13296                         return;
13297                 }
13298
13299                 var first = e.touches[0],
13300                     el = first.target;
13301
13302                 this._startPos = this._newPos = new Point(first.clientX, first.clientY);
13303
13304                 // if touching a link, highlight it
13305                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
13306                         addClass(el, 'leaflet-active');
13307                 }
13308
13309                 // simulate long hold but setting a timeout
13310                 this._holdTimeout = setTimeout(bind(function () {
13311                         if (this._isTapValid()) {
13312                                 this._fireClick = false;
13313                                 this._onUp();
13314                                 this._simulateEvent('contextmenu', first);
13315                         }
13316                 }, this), 1000);
13317
13318                 this._simulateEvent('mousedown', first);
13319
13320                 on(document, {
13321                         touchmove: this._onMove,
13322                         touchend: this._onUp
13323                 }, this);
13324         },
13325
13326         _onUp: function (e) {
13327                 clearTimeout(this._holdTimeout);
13328
13329                 off(document, {
13330                         touchmove: this._onMove,
13331                         touchend: this._onUp
13332                 }, this);
13333
13334                 if (this._fireClick && e && e.changedTouches) {
13335
13336                         var first = e.changedTouches[0],
13337                             el = first.target;
13338
13339                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
13340                                 removeClass(el, 'leaflet-active');
13341                         }
13342
13343                         this._simulateEvent('mouseup', first);
13344
13345                         // simulate click if the touch didn't move too much
13346                         if (this._isTapValid()) {
13347                                 this._simulateEvent('click', first);
13348                         }
13349                 }
13350         },
13351
13352         _isTapValid: function () {
13353                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
13354         },
13355
13356         _onMove: function (e) {
13357                 var first = e.touches[0];
13358                 this._newPos = new Point(first.clientX, first.clientY);
13359                 this._simulateEvent('mousemove', first);
13360         },
13361
13362         _simulateEvent: function (type, e) {
13363                 var simulatedEvent = document.createEvent('MouseEvents');
13364
13365                 simulatedEvent._simulated = true;
13366                 e.target._simulatedClick = true;
13367
13368                 simulatedEvent.initMouseEvent(
13369                         type, true, true, window, 1,
13370                         e.screenX, e.screenY,
13371                         e.clientX, e.clientY,
13372                         false, false, false, false, 0, null);
13373
13374                 e.target.dispatchEvent(simulatedEvent);
13375         }
13376 });
13377
13378 // @section Handlers
13379 // @property tap: Handler
13380 // Mobile touch hacks (quick tap and touch hold) handler.
13381 if (touch && !pointer) {
13382         Map.addInitHook('addHandler', 'tap', Tap);
13383 }
13384
13385 /*
13386  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
13387  */
13388
13389 // @namespace Map
13390 // @section Interaction Options
13391 Map.mergeOptions({
13392         // @section Touch interaction options
13393         // @option touchZoom: Boolean|String = *
13394         // Whether the map can be zoomed by touch-dragging with two fingers. If
13395         // passed `'center'`, it will zoom to the center of the view regardless of
13396         // where the touch events (fingers) were. Enabled for touch-capable web
13397         // browsers except for old Androids.
13398         touchZoom: touch && !android23,
13399
13400         // @option bounceAtZoomLimits: Boolean = true
13401         // Set it to false if you don't want the map to zoom beyond min/max zoom
13402         // and then bounce back when pinch-zooming.
13403         bounceAtZoomLimits: true
13404 });
13405
13406 var TouchZoom = Handler.extend({
13407         addHooks: function () {
13408                 addClass(this._map._container, 'leaflet-touch-zoom');
13409                 on(this._map._container, 'touchstart', this._onTouchStart, this);
13410         },
13411
13412         removeHooks: function () {
13413                 removeClass(this._map._container, 'leaflet-touch-zoom');
13414                 off(this._map._container, 'touchstart', this._onTouchStart, this);
13415         },
13416
13417         _onTouchStart: function (e) {
13418                 var map = this._map;
13419                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
13420
13421                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
13422                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
13423
13424                 this._centerPoint = map.getSize()._divideBy(2);
13425                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
13426                 if (map.options.touchZoom !== 'center') {
13427                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
13428                 }
13429
13430                 this._startDist = p1.distanceTo(p2);
13431                 this._startZoom = map.getZoom();
13432
13433                 this._moved = false;
13434                 this._zooming = true;
13435
13436                 map._stop();
13437
13438                 on(document, 'touchmove', this._onTouchMove, this);
13439                 on(document, 'touchend', this._onTouchEnd, this);
13440
13441                 preventDefault(e);
13442         },
13443
13444         _onTouchMove: function (e) {
13445                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
13446
13447                 var map = this._map,
13448                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
13449                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
13450                     scale = p1.distanceTo(p2) / this._startDist;
13451
13452                 this._zoom = map.getScaleZoom(scale, this._startZoom);
13453
13454                 if (!map.options.bounceAtZoomLimits && (
13455                         (this._zoom < map.getMinZoom() && scale < 1) ||
13456                         (this._zoom > map.getMaxZoom() && scale > 1))) {
13457                         this._zoom = map._limitZoom(this._zoom);
13458                 }
13459
13460                 if (map.options.touchZoom === 'center') {
13461                         this._center = this._startLatLng;
13462                         if (scale === 1) { return; }
13463                 } else {
13464                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
13465                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
13466                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
13467                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
13468                 }
13469
13470                 if (!this._moved) {
13471                         map._moveStart(true);
13472                         this._moved = true;
13473                 }
13474
13475                 cancelAnimFrame(this._animRequest);
13476
13477                 var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
13478                 this._animRequest = requestAnimFrame(moveFn, this, true);
13479
13480                 preventDefault(e);
13481         },
13482
13483         _onTouchEnd: function () {
13484                 if (!this._moved || !this._zooming) {
13485                         this._zooming = false;
13486                         return;
13487                 }
13488
13489                 this._zooming = false;
13490                 cancelAnimFrame(this._animRequest);
13491
13492                 off(document, 'touchmove', this._onTouchMove);
13493                 off(document, 'touchend', this._onTouchEnd);
13494
13495                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
13496                 if (this._map.options.zoomAnimation) {
13497                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
13498                 } else {
13499                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
13500                 }
13501         }
13502 });
13503
13504 // @section Handlers
13505 // @property touchZoom: Handler
13506 // Touch zoom handler.
13507 Map.addInitHook('addHandler', 'touchZoom', TouchZoom);
13508
13509 Map.BoxZoom = BoxZoom;
13510 Map.DoubleClickZoom = DoubleClickZoom;
13511 Map.Drag = Drag;
13512 Map.Keyboard = Keyboard;
13513 Map.ScrollWheelZoom = ScrollWheelZoom;
13514 Map.Tap = Tap;
13515 Map.TouchZoom = TouchZoom;
13516
13517 // misc
13518
13519 var oldL = window.L;
13520 function noConflict() {
13521         window.L = oldL;
13522         return this;
13523 }
13524
13525 // Always export us to window global (see #2364)
13526 window.L = exports;
13527
13528 Object.freeze = freeze;
13529
13530 exports.version = version;
13531 exports.noConflict = noConflict;
13532 exports.Control = Control;
13533 exports.control = control;
13534 exports.Browser = Browser;
13535 exports.Evented = Evented;
13536 exports.Mixin = Mixin;
13537 exports.Util = Util;
13538 exports.Class = Class;
13539 exports.Handler = Handler;
13540 exports.extend = extend;
13541 exports.bind = bind;
13542 exports.stamp = stamp;
13543 exports.setOptions = setOptions;
13544 exports.DomEvent = DomEvent;
13545 exports.DomUtil = DomUtil;
13546 exports.PosAnimation = PosAnimation;
13547 exports.Draggable = Draggable;
13548 exports.LineUtil = LineUtil;
13549 exports.PolyUtil = PolyUtil;
13550 exports.Point = Point;
13551 exports.point = toPoint;
13552 exports.Bounds = Bounds;
13553 exports.bounds = toBounds;
13554 exports.Transformation = Transformation;
13555 exports.transformation = toTransformation;
13556 exports.Projection = index;
13557 exports.LatLng = LatLng;
13558 exports.latLng = toLatLng;
13559 exports.LatLngBounds = LatLngBounds;
13560 exports.latLngBounds = toLatLngBounds;
13561 exports.CRS = CRS;
13562 exports.GeoJSON = GeoJSON;
13563 exports.geoJSON = geoJSON;
13564 exports.geoJson = geoJson;
13565 exports.Layer = Layer;
13566 exports.LayerGroup = LayerGroup;
13567 exports.layerGroup = layerGroup;
13568 exports.FeatureGroup = FeatureGroup;
13569 exports.featureGroup = featureGroup;
13570 exports.ImageOverlay = ImageOverlay;
13571 exports.imageOverlay = imageOverlay;
13572 exports.VideoOverlay = VideoOverlay;
13573 exports.videoOverlay = videoOverlay;
13574 exports.DivOverlay = DivOverlay;
13575 exports.Popup = Popup;
13576 exports.popup = popup;
13577 exports.Tooltip = Tooltip;
13578 exports.tooltip = tooltip;
13579 exports.Icon = Icon;
13580 exports.icon = icon;
13581 exports.DivIcon = DivIcon;
13582 exports.divIcon = divIcon;
13583 exports.Marker = Marker;
13584 exports.marker = marker;
13585 exports.TileLayer = TileLayer;
13586 exports.tileLayer = tileLayer;
13587 exports.GridLayer = GridLayer;
13588 exports.gridLayer = gridLayer;
13589 exports.SVG = SVG;
13590 exports.svg = svg$1;
13591 exports.Renderer = Renderer;
13592 exports.Canvas = Canvas;
13593 exports.canvas = canvas$1;
13594 exports.Path = Path;
13595 exports.CircleMarker = CircleMarker;
13596 exports.circleMarker = circleMarker;
13597 exports.Circle = Circle;
13598 exports.circle = circle;
13599 exports.Polyline = Polyline;
13600 exports.polyline = polyline;
13601 exports.Polygon = Polygon;
13602 exports.polygon = polygon;
13603 exports.Rectangle = Rectangle;
13604 exports.rectangle = rectangle;
13605 exports.Map = Map;
13606 exports.map = createMap;
13607
13608 })));
13609 //# sourceMappingURL=leaflet-src.js.map