]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Merge remote-tracking branch 'openstreetmap/pull/1367'
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /*
2  Leaflet 1.0.2, a JS library for interactive maps. http://leafletjs.com
3  (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5 (function (window, document, undefined) {
6 var L = {
7         version: "1.0.2"
8 };
9
10 function expose() {
11         var oldL = window.L;
12
13         L.noConflict = function () {
14                 window.L = oldL;
15                 return this;
16         };
17
18         window.L = L;
19 }
20
21 // define Leaflet for Node module pattern loaders, including Browserify
22 if (typeof module === 'object' && typeof module.exports === 'object') {
23         module.exports = L;
24
25 // define Leaflet as an AMD module
26 } else if (typeof define === 'function' && define.amd) {
27         define(L);
28 }
29
30 // define Leaflet as a global L variable, saving the original L to restore later if needed
31 if (typeof window !== 'undefined') {
32         expose();
33 }
34
35
36
37 /*
38  * @namespace Util
39  *
40  * Various utility functions, used by Leaflet internally.
41  */
42
43 L.Util = {
44
45         // @function extend(dest: Object, src?: Object): Object
46         // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
47         extend: function (dest) {
48                 var i, j, len, src;
49
50                 for (j = 1, len = arguments.length; j < len; j++) {
51                         src = arguments[j];
52                         for (i in src) {
53                                 dest[i] = src[i];
54                         }
55                 }
56                 return dest;
57         },
58
59         // @function create(proto: Object, properties?: Object): Object
60         // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
61         create: Object.create || (function () {
62                 function F() {}
63                 return function (proto) {
64                         F.prototype = proto;
65                         return new F();
66                 };
67         })(),
68
69         // @function bind(fn: Function, …): Function
70         // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
71         // Has a `L.bind()` shortcut.
72         bind: function (fn, obj) {
73                 var slice = Array.prototype.slice;
74
75                 if (fn.bind) {
76                         return fn.bind.apply(fn, slice.call(arguments, 1));
77                 }
78
79                 var args = slice.call(arguments, 2);
80
81                 return function () {
82                         return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
83                 };
84         },
85
86         // @function stamp(obj: Object): Number
87         // Returns the unique ID of an object, assiging it one if it doesn't have it.
88         stamp: function (obj) {
89                 /*eslint-disable */
90                 obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
91                 return obj._leaflet_id;
92                 /*eslint-enable */
93         },
94
95         // @property lastId: Number
96         // Last unique ID used by [`stamp()`](#util-stamp)
97         lastId: 0,
98
99         // @function throttle(fn: Function, time: Number, context: Object): Function
100         // Returns a function which executes function `fn` with the given scope `context`
101         // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
102         // `fn` will be called no more than one time per given amount of `time`. The arguments
103         // received by the bound function will be any arguments passed when binding the
104         // function, followed by any arguments passed when invoking the bound function.
105         // Has an `L.bind` shortcut.
106         throttle: function (fn, time, context) {
107                 var lock, args, wrapperFn, later;
108
109                 later = function () {
110                         // reset lock and call if queued
111                         lock = false;
112                         if (args) {
113                                 wrapperFn.apply(context, args);
114                                 args = false;
115                         }
116                 };
117
118                 wrapperFn = function () {
119                         if (lock) {
120                                 // called too soon, queue to call later
121                                 args = arguments;
122
123                         } else {
124                                 // call and lock until later
125                                 fn.apply(context, arguments);
126                                 setTimeout(later, time);
127                                 lock = true;
128                         }
129                 };
130
131                 return wrapperFn;
132         },
133
134         // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
135         // Returns the number `num` modulo `range` in such a way so it lies within
136         // `range[0]` and `range[1]`. The returned value will be always smaller than
137         // `range[1]` unless `includeMax` is set to `true`.
138         wrapNum: function (x, range, includeMax) {
139                 var max = range[1],
140                     min = range[0],
141                     d = max - min;
142                 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
143         },
144
145         // @function falseFn(): Function
146         // Returns a function which always returns `false`.
147         falseFn: function () { return false; },
148
149         // @function formatNum(num: Number, digits?: Number): Number
150         // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
151         formatNum: function (num, digits) {
152                 var pow = Math.pow(10, digits || 5);
153                 return Math.round(num * pow) / pow;
154         },
155
156         // @function trim(str: String): String
157         // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
158         trim: function (str) {
159                 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
160         },
161
162         // @function splitWords(str: String): String[]
163         // Trims and splits the string on whitespace and returns the array of parts.
164         splitWords: function (str) {
165                 return L.Util.trim(str).split(/\s+/);
166         },
167
168         // @function setOptions(obj: Object, options: Object): Object
169         // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
170         setOptions: function (obj, options) {
171                 if (!obj.hasOwnProperty('options')) {
172                         obj.options = obj.options ? L.Util.create(obj.options) : {};
173                 }
174                 for (var i in options) {
175                         obj.options[i] = options[i];
176                 }
177                 return obj.options;
178         },
179
180         // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
181         // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
182         // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
183         // be appended at the end. If `uppercase` is `true`, the parameter names will
184         // be uppercased (e.g. `'?A=foo&B=bar'`)
185         getParamString: function (obj, existingUrl, uppercase) {
186                 var params = [];
187                 for (var i in obj) {
188                         params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
189                 }
190                 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
191         },
192
193         // @function template(str: String, data: Object): String
194         // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
195         // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
196         // `('Hello foo, bar')`. You can also specify functions instead of strings for
197         // data values — they will be evaluated passing `data` as an argument.
198         template: function (str, data) {
199                 return str.replace(L.Util.templateRe, function (str, key) {
200                         var value = data[key];
201
202                         if (value === undefined) {
203                                 throw new Error('No value provided for variable ' + str);
204
205                         } else if (typeof value === 'function') {
206                                 value = value(data);
207                         }
208                         return value;
209                 });
210         },
211
212         templateRe: /\{ *([\w_\-]+) *\}/g,
213
214         // @function isArray(obj): Boolean
215         // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
216         isArray: Array.isArray || function (obj) {
217                 return (Object.prototype.toString.call(obj) === '[object Array]');
218         },
219
220         // @function indexOf(array: Array, el: Object): Number
221         // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
222         indexOf: function (array, el) {
223                 for (var i = 0; i < array.length; i++) {
224                         if (array[i] === el) { return i; }
225                 }
226                 return -1;
227         },
228
229         // @property emptyImageUrl: String
230         // Data URI string containing a base64-encoded empty GIF image.
231         // Used as a hack to free memory from unused images on WebKit-powered
232         // mobile devices (by setting image `src` to this string).
233         emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
234 };
235
236 (function () {
237         // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
238
239         function getPrefixed(name) {
240                 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
241         }
242
243         var lastTime = 0;
244
245         // fallback for IE 7-8
246         function timeoutDefer(fn) {
247                 var time = +new Date(),
248                     timeToCall = Math.max(0, 16 - (time - lastTime));
249
250                 lastTime = time + timeToCall;
251                 return window.setTimeout(fn, timeToCall);
252         }
253
254         var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255             cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
256                        getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
257
258
259         // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
260         // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
261         // `context` if given. When `immediate` is set, `fn` is called immediately if
262         // the browser doesn't have native support for
263         // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
264         // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
265         L.Util.requestAnimFrame = function (fn, context, immediate) {
266                 if (immediate && requestFn === timeoutDefer) {
267                         fn.call(context);
268                 } else {
269                         return requestFn.call(window, L.bind(fn, context));
270                 }
271         };
272
273         // @function cancelAnimFrame(id: Number): undefined
274         // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
275         L.Util.cancelAnimFrame = function (id) {
276                 if (id) {
277                         cancelFn.call(window, id);
278                 }
279         };
280 })();
281
282 // shortcuts for most used utility functions
283 L.extend = L.Util.extend;
284 L.bind = L.Util.bind;
285 L.stamp = L.Util.stamp;
286 L.setOptions = L.Util.setOptions;
287
288
289
290
291 // @class Class
292 // @aka L.Class
293
294 // @section
295 // @uninheritable
296
297 // Thanks to John Resig and Dean Edwards for inspiration!
298
299 L.Class = function () {};
300
301 L.Class.extend = function (props) {
302
303         // @function extend(props: Object): Function
304         // [Extends the current class](#class-inheritance) given the properties to be included.
305         // Returns a Javascript function that is a class constructor (to be called with `new`).
306         var NewClass = function () {
307
308                 // call the constructor
309                 if (this.initialize) {
310                         this.initialize.apply(this, arguments);
311                 }
312
313                 // call all constructor hooks
314                 this.callInitHooks();
315         };
316
317         var parentProto = NewClass.__super__ = this.prototype;
318
319         var proto = L.Util.create(parentProto);
320         proto.constructor = NewClass;
321
322         NewClass.prototype = proto;
323
324         // inherit parent's statics
325         for (var i in this) {
326                 if (this.hasOwnProperty(i) && i !== 'prototype') {
327                         NewClass[i] = this[i];
328                 }
329         }
330
331         // mix static properties into the class
332         if (props.statics) {
333                 L.extend(NewClass, props.statics);
334                 delete props.statics;
335         }
336
337         // mix includes into the prototype
338         if (props.includes) {
339                 L.Util.extend.apply(null, [proto].concat(props.includes));
340                 delete props.includes;
341         }
342
343         // merge options
344         if (proto.options) {
345                 props.options = L.Util.extend(L.Util.create(proto.options), props.options);
346         }
347
348         // mix given properties into the prototype
349         L.extend(proto, props);
350
351         proto._initHooks = [];
352
353         // add method for calling all hooks
354         proto.callInitHooks = function () {
355
356                 if (this._initHooksCalled) { return; }
357
358                 if (parentProto.callInitHooks) {
359                         parentProto.callInitHooks.call(this);
360                 }
361
362                 this._initHooksCalled = true;
363
364                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
365                         proto._initHooks[i].call(this);
366                 }
367         };
368
369         return NewClass;
370 };
371
372
373 // @function include(properties: Object): this
374 // [Includes a mixin](#class-includes) into the current class.
375 L.Class.include = function (props) {
376         L.extend(this.prototype, props);
377         return this;
378 };
379
380 // @function mergeOptions(options: Object): this
381 // [Merges `options`](#class-options) into the defaults of the class.
382 L.Class.mergeOptions = function (options) {
383         L.extend(this.prototype.options, options);
384         return this;
385 };
386
387 // @function addInitHook(fn: Function): this
388 // Adds a [constructor hook](#class-constructor-hooks) to the class.
389 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
390         var args = Array.prototype.slice.call(arguments, 1);
391
392         var init = typeof fn === 'function' ? fn : function () {
393                 this[fn].apply(this, args);
394         };
395
396         this.prototype._initHooks = this.prototype._initHooks || [];
397         this.prototype._initHooks.push(init);
398         return this;
399 };
400
401
402
403 /*
404  * @class Evented
405  * @aka L.Evented
406  * @inherits Class
407  *
408  * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
409  *
410  * @example
411  *
412  * ```js
413  * map.on('click', function(e) {
414  *      alert(e.latlng);
415  * } );
416  * ```
417  *
418  * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
419  *
420  * ```js
421  * function onClick(e) { ... }
422  *
423  * map.on('click', onClick);
424  * map.off('click', onClick);
425  * ```
426  */
427
428
429 L.Evented = L.Class.extend({
430
431         /* @method on(type: String, fn: Function, context?: Object): this
432          * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
433          *
434          * @alternative
435          * @method on(eventMap: Object): this
436          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
437          */
438         on: function (types, fn, context) {
439
440                 // types can be a map of types/handlers
441                 if (typeof types === 'object') {
442                         for (var type in types) {
443                                 // we don't process space-separated events here for performance;
444                                 // it's a hot path since Layer uses the on(obj) syntax
445                                 this._on(type, types[type], fn);
446                         }
447
448                 } else {
449                         // types can be a string of space-separated words
450                         types = L.Util.splitWords(types);
451
452                         for (var i = 0, len = types.length; i < len; i++) {
453                                 this._on(types[i], fn, context);
454                         }
455                 }
456
457                 return this;
458         },
459
460         /* @method off(type: String, fn?: Function, context?: Object): this
461          * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
462          *
463          * @alternative
464          * @method off(eventMap: Object): this
465          * Removes a set of type/listener pairs.
466          *
467          * @alternative
468          * @method off: this
469          * Removes all listeners to all events on the object.
470          */
471         off: function (types, fn, context) {
472
473                 if (!types) {
474                         // clear all listeners if called without arguments
475                         delete this._events;
476
477                 } else if (typeof types === 'object') {
478                         for (var type in types) {
479                                 this._off(type, types[type], fn);
480                         }
481
482                 } else {
483                         types = L.Util.splitWords(types);
484
485                         for (var i = 0, len = types.length; i < len; i++) {
486                                 this._off(types[i], fn, context);
487                         }
488                 }
489
490                 return this;
491         },
492
493         // attach listener (without syntactic sugar now)
494         _on: function (type, fn, context) {
495                 this._events = this._events || {};
496
497                 /* get/init listeners for type */
498                 var typeListeners = this._events[type];
499                 if (!typeListeners) {
500                         typeListeners = [];
501                         this._events[type] = typeListeners;
502                 }
503
504                 if (context === this) {
505                         // Less memory footprint.
506                         context = undefined;
507                 }
508                 var newListener = {fn: fn, ctx: context},
509                     listeners = typeListeners;
510
511                 // check if fn already there
512                 for (var i = 0, len = listeners.length; i < len; i++) {
513                         if (listeners[i].fn === fn && listeners[i].ctx === context) {
514                                 return;
515                         }
516                 }
517
518                 listeners.push(newListener);
519                 typeListeners.count++;
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 = L.Util.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 = L.Util.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 = L.Util.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 = L.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[L.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[L.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, L.extend({layer: e.target}, e), true);
661                 }
662         }
663 });
664
665 var proto = L.Evented.prototype;
666
667 // aliases; we should ditch those eventually
668
669 // @method addEventListener(…): this
670 // Alias to [`on(…)`](#evented-on)
671 proto.addEventListener = proto.on;
672
673 // @method removeEventListener(…): this
674 // Alias to [`off(…)`](#evented-off)
675
676 // @method clearAllEventListeners(…): this
677 // Alias to [`off()`](#evented-off)
678 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
679
680 // @method addOneTimeEventListener(…): this
681 // Alias to [`once(…)`](#evented-once)
682 proto.addOneTimeEventListener = proto.once;
683
684 // @method fireEvent(…): this
685 // Alias to [`fire(…)`](#evented-fire)
686 proto.fireEvent = proto.fire;
687
688 // @method hasEventListeners(…): Boolean
689 // Alias to [`listens(…)`](#evented-listens)
690 proto.hasEventListeners = proto.listens;
691
692 L.Mixin = {Events: proto};
693
694
695
696 /*
697  * @namespace Browser
698  * @aka L.Browser
699  *
700  * A namespace with static properties for browser/feature detection used by Leaflet internally.
701  *
702  * @example
703  *
704  * ```js
705  * if (L.Browser.ielt9) {
706  *   alert('Upgrade your browser, dude!');
707  * }
708  * ```
709  */
710
711 (function () {
712
713         var ua = navigator.userAgent.toLowerCase(),
714             doc = document.documentElement,
715
716             ie = 'ActiveXObject' in window,
717
718             webkit    = ua.indexOf('webkit') !== -1,
719             phantomjs = ua.indexOf('phantom') !== -1,
720             android23 = ua.search('android [23]') !== -1,
721             chrome    = ua.indexOf('chrome') !== -1,
722             gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,
723
724             win = navigator.platform.indexOf('Win') === 0,
725
726             mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
727             msPointer = !window.PointerEvent && window.MSPointerEvent,
728             pointer = window.PointerEvent || msPointer,
729
730             ie3d = ie && ('transition' in doc.style),
731             webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
732             gecko3d = 'MozPerspective' in doc.style,
733             opera12 = 'OTransition' in doc.style;
734
735
736         var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
737                         (window.DocumentTouch && document instanceof window.DocumentTouch));
738
739         L.Browser = {
740
741                 // @property ie: Boolean
742                 // `true` for all Internet Explorer versions (not Edge).
743                 ie: ie,
744
745                 // @property ielt9: Boolean
746                 // `true` for Internet Explorer versions less than 9.
747                 ielt9: ie && !document.addEventListener,
748
749                 // @property edge: Boolean
750                 // `true` for the Edge web browser.
751                 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
752
753                 // @property webkit: Boolean
754                 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
755                 webkit: webkit,
756
757                 // @property gecko: Boolean
758                 // `true` for gecko-based browsers like Firefox.
759                 gecko: gecko,
760
761                 // @property android: Boolean
762                 // `true` for any browser running on an Android platform.
763                 android: ua.indexOf('android') !== -1,
764
765                 // @property android23: Boolean
766                 // `true` for browsers running on Android 2 or Android 3.
767                 android23: android23,
768
769                 // @property chrome: Boolean
770                 // `true` for the Chrome browser.
771                 chrome: chrome,
772
773                 // @property safari: Boolean
774                 // `true` for the Safari browser.
775                 safari: !chrome && ua.indexOf('safari') !== -1,
776
777
778                 // @property win: Boolean
779                 // `true` when the browser is running in a Windows platform
780                 win: win,
781
782
783                 // @property ie3d: Boolean
784                 // `true` for all Internet Explorer versions supporting CSS transforms.
785                 ie3d: ie3d,
786
787                 // @property webkit3d: Boolean
788                 // `true` for webkit-based browsers supporting CSS transforms.
789                 webkit3d: webkit3d,
790
791                 // @property gecko3d: Boolean
792                 // `true` for gecko-based browsers supporting CSS transforms.
793                 gecko3d: gecko3d,
794
795                 // @property opera12: Boolean
796                 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
797                 opera12: opera12,
798
799                 // @property any3d: Boolean
800                 // `true` for all browsers supporting CSS transforms.
801                 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
802
803
804                 // @property mobile: Boolean
805                 // `true` for all browsers running in a mobile device.
806                 mobile: mobile,
807
808                 // @property mobileWebkit: Boolean
809                 // `true` for all webkit-based browsers in a mobile device.
810                 mobileWebkit: mobile && webkit,
811
812                 // @property mobileWebkit3d: Boolean
813                 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
814                 mobileWebkit3d: mobile && webkit3d,
815
816                 // @property mobileOpera: Boolean
817                 // `true` for the Opera browser in a mobile device.
818                 mobileOpera: mobile && window.opera,
819
820                 // @property mobileGecko: Boolean
821                 // `true` for gecko-based browsers running in a mobile device.
822                 mobileGecko: mobile && gecko,
823
824
825                 // @property touch: Boolean
826                 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
827                 touch: !!touch,
828
829                 // @property msPointer: Boolean
830                 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
831                 msPointer: !!msPointer,
832
833                 // @property pointer: Boolean
834                 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
835                 pointer: !!pointer,
836
837
838                 // @property retina: Boolean
839                 // `true` for browsers on a high-resolution "retina" screen.
840                 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
841         };
842
843 }());
844
845
846
847 /*
848  * @class Point
849  * @aka L.Point
850  *
851  * Represents a point with `x` and `y` coordinates in pixels.
852  *
853  * @example
854  *
855  * ```js
856  * var point = L.point(200, 300);
857  * ```
858  *
859  * 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:
860  *
861  * ```js
862  * map.panBy([200, 300]);
863  * map.panBy(L.point(200, 300));
864  * ```
865  */
866
867 L.Point = function (x, y, round) {
868         // @property x: Number; The `x` coordinate of the point
869         this.x = (round ? Math.round(x) : x);
870         // @property y: Number; The `y` coordinate of the point
871         this.y = (round ? Math.round(y) : y);
872 };
873
874 L.Point.prototype = {
875
876         // @method clone(): Point
877         // Returns a copy of the current point.
878         clone: function () {
879                 return new L.Point(this.x, this.y);
880         },
881
882         // @method add(otherPoint: Point): Point
883         // Returns the result of addition of the current and the given points.
884         add: function (point) {
885                 // non-destructive, returns a new point
886                 return this.clone()._add(L.point(point));
887         },
888
889         _add: function (point) {
890                 // destructive, used directly for performance in situations where it's safe to modify existing point
891                 this.x += point.x;
892                 this.y += point.y;
893                 return this;
894         },
895
896         // @method subtract(otherPoint: Point): Point
897         // Returns the result of subtraction of the given point from the current.
898         subtract: function (point) {
899                 return this.clone()._subtract(L.point(point));
900         },
901
902         _subtract: function (point) {
903                 this.x -= point.x;
904                 this.y -= point.y;
905                 return this;
906         },
907
908         // @method divideBy(num: Number): Point
909         // Returns the result of division of the current point by the given number.
910         divideBy: function (num) {
911                 return this.clone()._divideBy(num);
912         },
913
914         _divideBy: function (num) {
915                 this.x /= num;
916                 this.y /= num;
917                 return this;
918         },
919
920         // @method multiplyBy(num: Number): Point
921         // Returns the result of multiplication of the current point by the given number.
922         multiplyBy: function (num) {
923                 return this.clone()._multiplyBy(num);
924         },
925
926         _multiplyBy: function (num) {
927                 this.x *= num;
928                 this.y *= num;
929                 return this;
930         },
931
932         // @method scaleBy(scale: Point): Point
933         // Multiply each coordinate of the current point by each coordinate of
934         // `scale`. In linear algebra terms, multiply the point by the
935         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
936         // defined by `scale`.
937         scaleBy: function (point) {
938                 return new L.Point(this.x * point.x, this.y * point.y);
939         },
940
941         // @method unscaleBy(scale: Point): Point
942         // Inverse of `scaleBy`. Divide each coordinate of the current point by
943         // each coordinate of `scale`.
944         unscaleBy: function (point) {
945                 return new L.Point(this.x / point.x, this.y / point.y);
946         },
947
948         // @method round(): Point
949         // Returns a copy of the current point with rounded coordinates.
950         round: function () {
951                 return this.clone()._round();
952         },
953
954         _round: function () {
955                 this.x = Math.round(this.x);
956                 this.y = Math.round(this.y);
957                 return this;
958         },
959
960         // @method floor(): Point
961         // Returns a copy of the current point with floored coordinates (rounded down).
962         floor: function () {
963                 return this.clone()._floor();
964         },
965
966         _floor: function () {
967                 this.x = Math.floor(this.x);
968                 this.y = Math.floor(this.y);
969                 return this;
970         },
971
972         // @method ceil(): Point
973         // Returns a copy of the current point with ceiled coordinates (rounded up).
974         ceil: function () {
975                 return this.clone()._ceil();
976         },
977
978         _ceil: function () {
979                 this.x = Math.ceil(this.x);
980                 this.y = Math.ceil(this.y);
981                 return this;
982         },
983
984         // @method distanceTo(otherPoint: Point): Number
985         // Returns the cartesian distance between the current and the given points.
986         distanceTo: function (point) {
987                 point = L.point(point);
988
989                 var x = point.x - this.x,
990                     y = point.y - this.y;
991
992                 return Math.sqrt(x * x + y * y);
993         },
994
995         // @method equals(otherPoint: Point): Boolean
996         // Returns `true` if the given point has the same coordinates.
997         equals: function (point) {
998                 point = L.point(point);
999
1000                 return point.x === this.x &&
1001                        point.y === this.y;
1002         },
1003
1004         // @method contains(otherPoint: Point): Boolean
1005         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1006         contains: function (point) {
1007                 point = L.point(point);
1008
1009                 return Math.abs(point.x) <= Math.abs(this.x) &&
1010                        Math.abs(point.y) <= Math.abs(this.y);
1011         },
1012
1013         // @method toString(): String
1014         // Returns a string representation of the point for debugging purposes.
1015         toString: function () {
1016                 return 'Point(' +
1017                         L.Util.formatNum(this.x) + ', ' +
1018                         L.Util.formatNum(this.y) + ')';
1019         }
1020 };
1021
1022 // @factory L.point(x: Number, y: Number, round?: Boolean)
1023 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1024
1025 // @alternative
1026 // @factory L.point(coords: Number[])
1027 // Expects an array of the form `[x, y]` instead.
1028
1029 // @alternative
1030 // @factory L.point(coords: Object)
1031 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1032 L.point = function (x, y, round) {
1033         if (x instanceof L.Point) {
1034                 return x;
1035         }
1036         if (L.Util.isArray(x)) {
1037                 return new L.Point(x[0], x[1]);
1038         }
1039         if (x === undefined || x === null) {
1040                 return x;
1041         }
1042         if (typeof x === 'object' && 'x' in x && 'y' in x) {
1043                 return new L.Point(x.x, x.y);
1044         }
1045         return new L.Point(x, y, round);
1046 };
1047
1048
1049
1050 /*
1051  * @class Bounds
1052  * @aka L.Bounds
1053  *
1054  * Represents a rectangular area in pixel coordinates.
1055  *
1056  * @example
1057  *
1058  * ```js
1059  * var p1 = L.point(10, 10),
1060  * p2 = L.point(40, 60),
1061  * bounds = L.bounds(p1, p2);
1062  * ```
1063  *
1064  * 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:
1065  *
1066  * ```js
1067  * otherBounds.intersects([[10, 10], [40, 60]]);
1068  * ```
1069  */
1070
1071 L.Bounds = function (a, b) {
1072         if (!a) { return; }
1073
1074         var points = b ? [a, b] : a;
1075
1076         for (var i = 0, len = points.length; i < len; i++) {
1077                 this.extend(points[i]);
1078         }
1079 };
1080
1081 L.Bounds.prototype = {
1082         // @method extend(point: Point): this
1083         // Extends the bounds to contain the given point.
1084         extend: function (point) { // (Point)
1085                 point = L.point(point);
1086
1087                 // @property min: Point
1088                 // The top left corner of the rectangle.
1089                 // @property max: Point
1090                 // The bottom right corner of the rectangle.
1091                 if (!this.min && !this.max) {
1092                         this.min = point.clone();
1093                         this.max = point.clone();
1094                 } else {
1095                         this.min.x = Math.min(point.x, this.min.x);
1096                         this.max.x = Math.max(point.x, this.max.x);
1097                         this.min.y = Math.min(point.y, this.min.y);
1098                         this.max.y = Math.max(point.y, this.max.y);
1099                 }
1100                 return this;
1101         },
1102
1103         // @method getCenter(round?: Boolean): Point
1104         // Returns the center point of the bounds.
1105         getCenter: function (round) {
1106                 return new L.Point(
1107                         (this.min.x + this.max.x) / 2,
1108                         (this.min.y + this.max.y) / 2, round);
1109         },
1110
1111         // @method getBottomLeft(): Point
1112         // Returns the bottom-left point of the bounds.
1113         getBottomLeft: function () {
1114                 return new L.Point(this.min.x, this.max.y);
1115         },
1116
1117         // @method getTopRight(): Point
1118         // Returns the top-right point of the bounds.
1119         getTopRight: function () { // -> Point
1120                 return new L.Point(this.max.x, this.min.y);
1121         },
1122
1123         // @method getSize(): Point
1124         // Returns the size of the given bounds
1125         getSize: function () {
1126                 return this.max.subtract(this.min);
1127         },
1128
1129         // @method contains(otherBounds: Bounds): Boolean
1130         // Returns `true` if the rectangle contains the given one.
1131         // @alternative
1132         // @method contains(point: Point): Boolean
1133         // Returns `true` if the rectangle contains the given point.
1134         contains: function (obj) {
1135                 var min, max;
1136
1137                 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1138                         obj = L.point(obj);
1139                 } else {
1140                         obj = L.bounds(obj);
1141                 }
1142
1143                 if (obj instanceof L.Bounds) {
1144                         min = obj.min;
1145                         max = obj.max;
1146                 } else {
1147                         min = max = obj;
1148                 }
1149
1150                 return (min.x >= this.min.x) &&
1151                        (max.x <= this.max.x) &&
1152                        (min.y >= this.min.y) &&
1153                        (max.y <= this.max.y);
1154         },
1155
1156         // @method intersects(otherBounds: Bounds): Boolean
1157         // Returns `true` if the rectangle intersects the given bounds. Two bounds
1158         // intersect if they have at least one point in common.
1159         intersects: function (bounds) { // (Bounds) -> Boolean
1160                 bounds = L.bounds(bounds);
1161
1162                 var min = this.min,
1163                     max = this.max,
1164                     min2 = bounds.min,
1165                     max2 = bounds.max,
1166                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1167                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1168
1169                 return xIntersects && yIntersects;
1170         },
1171
1172         // @method overlaps(otherBounds: Bounds): Boolean
1173         // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1174         // overlap if their intersection is an area.
1175         overlaps: function (bounds) { // (Bounds) -> Boolean
1176                 bounds = L.bounds(bounds);
1177
1178                 var min = this.min,
1179                     max = this.max,
1180                     min2 = bounds.min,
1181                     max2 = bounds.max,
1182                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1183                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1184
1185                 return xOverlaps && yOverlaps;
1186         },
1187
1188         isValid: function () {
1189                 return !!(this.min && this.max);
1190         }
1191 };
1192
1193
1194 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1195 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1196 // @alternative
1197 // @factory L.bounds(points: Point[])
1198 // Creates a Bounds object from the points it contains
1199 L.bounds = function (a, b) {
1200         if (!a || a instanceof L.Bounds) {
1201                 return a;
1202         }
1203         return new L.Bounds(a, b);
1204 };
1205
1206
1207
1208 /*
1209  * @class Transformation
1210  * @aka L.Transformation
1211  *
1212  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1213  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1214  * the reverse. Used by Leaflet in its projections code.
1215  *
1216  * @example
1217  *
1218  * ```js
1219  * var transformation = new L.Transformation(2, 5, -1, 10),
1220  *      p = L.point(1, 2),
1221  *      p2 = transformation.transform(p), //  L.point(7, 8)
1222  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1223  * ```
1224  */
1225
1226
1227 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1228 // Creates a `Transformation` object with the given coefficients.
1229 L.Transformation = function (a, b, c, d) {
1230         this._a = a;
1231         this._b = b;
1232         this._c = c;
1233         this._d = d;
1234 };
1235
1236 L.Transformation.prototype = {
1237         // @method transform(point: Point, scale?: Number): Point
1238         // Returns a transformed point, optionally multiplied by the given scale.
1239         // Only accepts actual `L.Point` instances, not arrays.
1240         transform: function (point, scale) { // (Point, Number) -> Point
1241                 return this._transform(point.clone(), scale);
1242         },
1243
1244         // destructive transform (faster)
1245         _transform: function (point, scale) {
1246                 scale = scale || 1;
1247                 point.x = scale * (this._a * point.x + this._b);
1248                 point.y = scale * (this._c * point.y + this._d);
1249                 return point;
1250         },
1251
1252         // @method untransform(point: Point, scale?: Number): Point
1253         // Returns the reverse transformation of the given point, optionally divided
1254         // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1255         untransform: function (point, scale) {
1256                 scale = scale || 1;
1257                 return new L.Point(
1258                         (point.x / scale - this._b) / this._a,
1259                         (point.y / scale - this._d) / this._c);
1260         }
1261 };
1262
1263
1264
1265 /*
1266  * @namespace DomUtil
1267  *
1268  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1269  * tree, used by Leaflet internally.
1270  *
1271  * Most functions expecting or returning a `HTMLElement` also work for
1272  * SVG elements. The only difference is that classes refer to CSS classes
1273  * in HTML and SVG classes in SVG.
1274  */
1275
1276 L.DomUtil = {
1277
1278         // @function get(id: String|HTMLElement): HTMLElement
1279         // Returns an element given its DOM id, or returns the element itself
1280         // if it was passed directly.
1281         get: function (id) {
1282                 return typeof id === 'string' ? document.getElementById(id) : id;
1283         },
1284
1285         // @function getStyle(el: HTMLElement, styleAttrib: String): String
1286         // Returns the value for a certain style attribute on an element,
1287         // including computed values or values set through CSS.
1288         getStyle: function (el, style) {
1289
1290                 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1291
1292                 if ((!value || value === 'auto') && document.defaultView) {
1293                         var css = document.defaultView.getComputedStyle(el, null);
1294                         value = css ? css[style] : null;
1295                 }
1296
1297                 return value === 'auto' ? null : value;
1298         },
1299
1300         // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1301         // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1302         create: function (tagName, className, container) {
1303
1304                 var el = document.createElement(tagName);
1305                 el.className = className || '';
1306
1307                 if (container) {
1308                         container.appendChild(el);
1309                 }
1310
1311                 return el;
1312         },
1313
1314         // @function remove(el: HTMLElement)
1315         // Removes `el` from its parent element
1316         remove: function (el) {
1317                 var parent = el.parentNode;
1318                 if (parent) {
1319                         parent.removeChild(el);
1320                 }
1321         },
1322
1323         // @function empty(el: HTMLElement)
1324         // Removes all of `el`'s children elements from `el`
1325         empty: function (el) {
1326                 while (el.firstChild) {
1327                         el.removeChild(el.firstChild);
1328                 }
1329         },
1330
1331         // @function toFront(el: HTMLElement)
1332         // Makes `el` the last children of its parent, so it renders in front of the other children.
1333         toFront: function (el) {
1334                 el.parentNode.appendChild(el);
1335         },
1336
1337         // @function toBack(el: HTMLElement)
1338         // Makes `el` the first children of its parent, so it renders back from the other children.
1339         toBack: function (el) {
1340                 var parent = el.parentNode;
1341                 parent.insertBefore(el, parent.firstChild);
1342         },
1343
1344         // @function hasClass(el: HTMLElement, name: String): Boolean
1345         // Returns `true` if the element's class attribute contains `name`.
1346         hasClass: function (el, name) {
1347                 if (el.classList !== undefined) {
1348                         return el.classList.contains(name);
1349                 }
1350                 var className = L.DomUtil.getClass(el);
1351                 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1352         },
1353
1354         // @function addClass(el: HTMLElement, name: String)
1355         // Adds `name` to the element's class attribute.
1356         addClass: function (el, name) {
1357                 if (el.classList !== undefined) {
1358                         var classes = L.Util.splitWords(name);
1359                         for (var i = 0, len = classes.length; i < len; i++) {
1360                                 el.classList.add(classes[i]);
1361                         }
1362                 } else if (!L.DomUtil.hasClass(el, name)) {
1363                         var className = L.DomUtil.getClass(el);
1364                         L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1365                 }
1366         },
1367
1368         // @function removeClass(el: HTMLElement, name: String)
1369         // Removes `name` from the element's class attribute.
1370         removeClass: function (el, name) {
1371                 if (el.classList !== undefined) {
1372                         el.classList.remove(name);
1373                 } else {
1374                         L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1375                 }
1376         },
1377
1378         // @function setClass(el: HTMLElement, name: String)
1379         // Sets the element's class.
1380         setClass: function (el, name) {
1381                 if (el.className.baseVal === undefined) {
1382                         el.className = name;
1383                 } else {
1384                         // in case of SVG element
1385                         el.className.baseVal = name;
1386                 }
1387         },
1388
1389         // @function getClass(el: HTMLElement): String
1390         // Returns the element's class.
1391         getClass: function (el) {
1392                 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1393         },
1394
1395         // @function setOpacity(el: HTMLElement, opacity: Number)
1396         // Set the opacity of an element (including old IE support).
1397         // `opacity` must be a number from `0` to `1`.
1398         setOpacity: function (el, value) {
1399
1400                 if ('opacity' in el.style) {
1401                         el.style.opacity = value;
1402
1403                 } else if ('filter' in el.style) {
1404                         L.DomUtil._setOpacityIE(el, value);
1405                 }
1406         },
1407
1408         _setOpacityIE: function (el, value) {
1409                 var filter = false,
1410                     filterName = 'DXImageTransform.Microsoft.Alpha';
1411
1412                 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1413                 try {
1414                         filter = el.filters.item(filterName);
1415                 } catch (e) {
1416                         // don't set opacity to 1 if we haven't already set an opacity,
1417                         // it isn't needed and breaks transparent pngs.
1418                         if (value === 1) { return; }
1419                 }
1420
1421                 value = Math.round(value * 100);
1422
1423                 if (filter) {
1424                         filter.Enabled = (value !== 100);
1425                         filter.Opacity = value;
1426                 } else {
1427                         el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1428                 }
1429         },
1430
1431         // @function testProp(props: String[]): String|false
1432         // Goes through the array of style names and returns the first name
1433         // that is a valid style name for an element. If no such name is found,
1434         // it returns false. Useful for vendor-prefixed styles like `transform`.
1435         testProp: function (props) {
1436
1437                 var style = document.documentElement.style;
1438
1439                 for (var i = 0; i < props.length; i++) {
1440                         if (props[i] in style) {
1441                                 return props[i];
1442                         }
1443                 }
1444                 return false;
1445         },
1446
1447         // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1448         // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1449         // and optionally scaled by `scale`. Does not have an effect if the
1450         // browser doesn't support 3D CSS transforms.
1451         setTransform: function (el, offset, scale) {
1452                 var pos = offset || new L.Point(0, 0);
1453
1454                 el.style[L.DomUtil.TRANSFORM] =
1455                         (L.Browser.ie3d ?
1456                                 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1457                                 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1458                         (scale ? ' scale(' + scale + ')' : '');
1459         },
1460
1461         // @function setPosition(el: HTMLElement, position: Point)
1462         // Sets the position of `el` to coordinates specified by `position`,
1463         // using CSS translate or top/left positioning depending on the browser
1464         // (used by Leaflet internally to position its layers).
1465         setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1466
1467                 /*eslint-disable */
1468                 el._leaflet_pos = point;
1469                 /*eslint-enable */
1470
1471                 if (L.Browser.any3d) {
1472                         L.DomUtil.setTransform(el, point);
1473                 } else {
1474                         el.style.left = point.x + 'px';
1475                         el.style.top = point.y + 'px';
1476                 }
1477         },
1478
1479         // @function getPosition(el: HTMLElement): Point
1480         // Returns the coordinates of an element previously positioned with setPosition.
1481         getPosition: function (el) {
1482                 // this method is only used for elements previously positioned using setPosition,
1483                 // so it's safe to cache the position for performance
1484
1485                 return el._leaflet_pos || new L.Point(0, 0);
1486         }
1487 };
1488
1489
1490 (function () {
1491         // prefix style property names
1492
1493         // @property TRANSFORM: String
1494         // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1495         L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1496                         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1497
1498
1499         // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1500         // the same for the transitionend event, in particular the Android 4.1 stock browser
1501
1502         // @property TRANSITION: String
1503         // Vendor-prefixed transform style name.
1504         var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1505                         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1506
1507         L.DomUtil.TRANSITION_END =
1508                         transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1509
1510         // @function disableTextSelection()
1511         // Prevents the user from generating `selectstart` DOM events, usually generated
1512         // when the user drags the mouse through a page with text. Used internally
1513         // by Leaflet to override the behaviour of any click-and-drag interaction on
1514         // the map. Affects drag interactions on the whole document.
1515
1516         // @function enableTextSelection()
1517         // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1518         if ('onselectstart' in document) {
1519                 L.DomUtil.disableTextSelection = function () {
1520                         L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1521                 };
1522                 L.DomUtil.enableTextSelection = function () {
1523                         L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1524                 };
1525
1526         } else {
1527                 var userSelectProperty = L.DomUtil.testProp(
1528                         ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1529
1530                 L.DomUtil.disableTextSelection = function () {
1531                         if (userSelectProperty) {
1532                                 var style = document.documentElement.style;
1533                                 this._userSelect = style[userSelectProperty];
1534                                 style[userSelectProperty] = 'none';
1535                         }
1536                 };
1537                 L.DomUtil.enableTextSelection = function () {
1538                         if (userSelectProperty) {
1539                                 document.documentElement.style[userSelectProperty] = this._userSelect;
1540                                 delete this._userSelect;
1541                         }
1542                 };
1543         }
1544
1545         // @function disableImageDrag()
1546         // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1547         // for `dragstart` DOM events, usually generated when the user drags an image.
1548         L.DomUtil.disableImageDrag = function () {
1549                 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1550         };
1551
1552         // @function enableImageDrag()
1553         // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1554         L.DomUtil.enableImageDrag = function () {
1555                 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1556         };
1557
1558         // @function preventOutline(el: HTMLElement)
1559         // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1560         // of the element `el` invisible. Used internally by Leaflet to prevent
1561         // focusable elements from displaying an outline when the user performs a
1562         // drag interaction on them.
1563         L.DomUtil.preventOutline = function (element) {
1564                 while (element.tabIndex === -1) {
1565                         element = element.parentNode;
1566                 }
1567                 if (!element || !element.style) { return; }
1568                 L.DomUtil.restoreOutline();
1569                 this._outlineElement = element;
1570                 this._outlineStyle = element.style.outline;
1571                 element.style.outline = 'none';
1572                 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1573         };
1574
1575         // @function restoreOutline()
1576         // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1577         L.DomUtil.restoreOutline = function () {
1578                 if (!this._outlineElement) { return; }
1579                 this._outlineElement.style.outline = this._outlineStyle;
1580                 delete this._outlineElement;
1581                 delete this._outlineStyle;
1582                 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1583         };
1584 })();
1585
1586
1587
1588 /* @class LatLng
1589  * @aka L.LatLng
1590  *
1591  * Represents a geographical point with a certain latitude and longitude.
1592  *
1593  * @example
1594  *
1595  * ```
1596  * var latlng = L.latLng(50.5, 30.5);
1597  * ```
1598  *
1599  * 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:
1600  *
1601  * ```
1602  * map.panTo([50, 30]);
1603  * map.panTo({lon: 30, lat: 50});
1604  * map.panTo({lat: 50, lng: 30});
1605  * map.panTo(L.latLng(50, 30));
1606  * ```
1607  */
1608
1609 L.LatLng = function (lat, lng, alt) {
1610         if (isNaN(lat) || isNaN(lng)) {
1611                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1612         }
1613
1614         // @property lat: Number
1615         // Latitude in degrees
1616         this.lat = +lat;
1617
1618         // @property lng: Number
1619         // Longitude in degrees
1620         this.lng = +lng;
1621
1622         // @property alt: Number
1623         // Altitude in meters (optional)
1624         if (alt !== undefined) {
1625                 this.alt = +alt;
1626         }
1627 };
1628
1629 L.LatLng.prototype = {
1630         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1631         // 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.
1632         equals: function (obj, maxMargin) {
1633                 if (!obj) { return false; }
1634
1635                 obj = L.latLng(obj);
1636
1637                 var margin = Math.max(
1638                         Math.abs(this.lat - obj.lat),
1639                         Math.abs(this.lng - obj.lng));
1640
1641                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1642         },
1643
1644         // @method toString(): String
1645         // Returns a string representation of the point (for debugging purposes).
1646         toString: function (precision) {
1647                 return 'LatLng(' +
1648                         L.Util.formatNum(this.lat, precision) + ', ' +
1649                         L.Util.formatNum(this.lng, precision) + ')';
1650         },
1651
1652         // @method distanceTo(otherLatLng: LatLng): Number
1653         // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1654         distanceTo: function (other) {
1655                 return L.CRS.Earth.distance(this, L.latLng(other));
1656         },
1657
1658         // @method wrap(): LatLng
1659         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1660         wrap: function () {
1661                 return L.CRS.Earth.wrapLatLng(this);
1662         },
1663
1664         // @method toBounds(sizeInMeters: Number): LatLngBounds
1665         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`.
1666         toBounds: function (sizeInMeters) {
1667                 var latAccuracy = 180 * sizeInMeters / 40075017,
1668                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1669
1670                 return L.latLngBounds(
1671                         [this.lat - latAccuracy, this.lng - lngAccuracy],
1672                         [this.lat + latAccuracy, this.lng + lngAccuracy]);
1673         },
1674
1675         clone: function () {
1676                 return new L.LatLng(this.lat, this.lng, this.alt);
1677         }
1678 };
1679
1680
1681
1682 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1683 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1684
1685 // @alternative
1686 // @factory L.latLng(coords: Array): LatLng
1687 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1688
1689 // @alternative
1690 // @factory L.latLng(coords: Object): LatLng
1691 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1692
1693 L.latLng = function (a, b, c) {
1694         if (a instanceof L.LatLng) {
1695                 return a;
1696         }
1697         if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1698                 if (a.length === 3) {
1699                         return new L.LatLng(a[0], a[1], a[2]);
1700                 }
1701                 if (a.length === 2) {
1702                         return new L.LatLng(a[0], a[1]);
1703                 }
1704                 return null;
1705         }
1706         if (a === undefined || a === null) {
1707                 return a;
1708         }
1709         if (typeof a === 'object' && 'lat' in a) {
1710                 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1711         }
1712         if (b === undefined) {
1713                 return null;
1714         }
1715         return new L.LatLng(a, b, c);
1716 };
1717
1718
1719
1720 /*
1721  * @class LatLngBounds
1722  * @aka L.LatLngBounds
1723  *
1724  * Represents a rectangular geographical area on a map.
1725  *
1726  * @example
1727  *
1728  * ```js
1729  * var corner1 = L.latLng(40.712, -74.227),
1730  * corner2 = L.latLng(40.774, -74.125),
1731  * bounds = L.latLngBounds(corner1, corner2);
1732  * ```
1733  *
1734  * 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:
1735  *
1736  * ```js
1737  * map.fitBounds([
1738  *      [40.712, -74.227],
1739  *      [40.774, -74.125]
1740  * ]);
1741  * ```
1742  *
1743  * 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.
1744  */
1745
1746 L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1747         if (!corner1) { return; }
1748
1749         var latlngs = corner2 ? [corner1, corner2] : corner1;
1750
1751         for (var i = 0, len = latlngs.length; i < len; i++) {
1752                 this.extend(latlngs[i]);
1753         }
1754 };
1755
1756 L.LatLngBounds.prototype = {
1757
1758         // @method extend(latlng: LatLng): this
1759         // Extend the bounds to contain the given point
1760
1761         // @alternative
1762         // @method extend(otherBounds: LatLngBounds): this
1763         // Extend the bounds to contain the given bounds
1764         extend: function (obj) {
1765                 var sw = this._southWest,
1766                     ne = this._northEast,
1767                     sw2, ne2;
1768
1769                 if (obj instanceof L.LatLng) {
1770                         sw2 = obj;
1771                         ne2 = obj;
1772
1773                 } else if (obj instanceof L.LatLngBounds) {
1774                         sw2 = obj._southWest;
1775                         ne2 = obj._northEast;
1776
1777                         if (!sw2 || !ne2) { return this; }
1778
1779                 } else {
1780                         return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1781                 }
1782
1783                 if (!sw && !ne) {
1784                         this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1785                         this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1786                 } else {
1787                         sw.lat = Math.min(sw2.lat, sw.lat);
1788                         sw.lng = Math.min(sw2.lng, sw.lng);
1789                         ne.lat = Math.max(ne2.lat, ne.lat);
1790                         ne.lng = Math.max(ne2.lng, ne.lng);
1791                 }
1792
1793                 return this;
1794         },
1795
1796         // @method pad(bufferRatio: Number): LatLngBounds
1797         // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1798         pad: function (bufferRatio) {
1799                 var sw = this._southWest,
1800                     ne = this._northEast,
1801                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1802                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1803
1804                 return new L.LatLngBounds(
1805                         new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1806                         new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1807         },
1808
1809         // @method getCenter(): LatLng
1810         // Returns the center point of the bounds.
1811         getCenter: function () {
1812                 return new L.LatLng(
1813                         (this._southWest.lat + this._northEast.lat) / 2,
1814                         (this._southWest.lng + this._northEast.lng) / 2);
1815         },
1816
1817         // @method getSouthWest(): LatLng
1818         // Returns the south-west point of the bounds.
1819         getSouthWest: function () {
1820                 return this._southWest;
1821         },
1822
1823         // @method getNorthEast(): LatLng
1824         // Returns the north-east point of the bounds.
1825         getNorthEast: function () {
1826                 return this._northEast;
1827         },
1828
1829         // @method getNorthWest(): LatLng
1830         // Returns the north-west point of the bounds.
1831         getNorthWest: function () {
1832                 return new L.LatLng(this.getNorth(), this.getWest());
1833         },
1834
1835         // @method getSouthEast(): LatLng
1836         // Returns the south-east point of the bounds.
1837         getSouthEast: function () {
1838                 return new L.LatLng(this.getSouth(), this.getEast());
1839         },
1840
1841         // @method getWest(): Number
1842         // Returns the west longitude of the bounds
1843         getWest: function () {
1844                 return this._southWest.lng;
1845         },
1846
1847         // @method getSouth(): Number
1848         // Returns the south latitude of the bounds
1849         getSouth: function () {
1850                 return this._southWest.lat;
1851         },
1852
1853         // @method getEast(): Number
1854         // Returns the east longitude of the bounds
1855         getEast: function () {
1856                 return this._northEast.lng;
1857         },
1858
1859         // @method getNorth(): Number
1860         // Returns the north latitude of the bounds
1861         getNorth: function () {
1862                 return this._northEast.lat;
1863         },
1864
1865         // @method contains(otherBounds: LatLngBounds): Boolean
1866         // Returns `true` if the rectangle contains the given one.
1867
1868         // @alternative
1869         // @method contains (latlng: LatLng): Boolean
1870         // Returns `true` if the rectangle contains the given point.
1871         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1872                 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1873                         obj = L.latLng(obj);
1874                 } else {
1875                         obj = L.latLngBounds(obj);
1876                 }
1877
1878                 var sw = this._southWest,
1879                     ne = this._northEast,
1880                     sw2, ne2;
1881
1882                 if (obj instanceof L.LatLngBounds) {
1883                         sw2 = obj.getSouthWest();
1884                         ne2 = obj.getNorthEast();
1885                 } else {
1886                         sw2 = ne2 = obj;
1887                 }
1888
1889                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1890                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1891         },
1892
1893         // @method intersects(otherBounds: LatLngBounds): Boolean
1894         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1895         intersects: function (bounds) {
1896                 bounds = L.latLngBounds(bounds);
1897
1898                 var sw = this._southWest,
1899                     ne = this._northEast,
1900                     sw2 = bounds.getSouthWest(),
1901                     ne2 = bounds.getNorthEast(),
1902
1903                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1904                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1905
1906                 return latIntersects && lngIntersects;
1907         },
1908
1909         // @method overlaps(otherBounds: Bounds): Boolean
1910         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1911         overlaps: function (bounds) {
1912                 bounds = L.latLngBounds(bounds);
1913
1914                 var sw = this._southWest,
1915                     ne = this._northEast,
1916                     sw2 = bounds.getSouthWest(),
1917                     ne2 = bounds.getNorthEast(),
1918
1919                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1920                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1921
1922                 return latOverlaps && lngOverlaps;
1923         },
1924
1925         // @method toBBoxString(): String
1926         // 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.
1927         toBBoxString: function () {
1928                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1929         },
1930
1931         // @method equals(otherBounds: LatLngBounds): Boolean
1932         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1933         equals: function (bounds) {
1934                 if (!bounds) { return false; }
1935
1936                 bounds = L.latLngBounds(bounds);
1937
1938                 return this._southWest.equals(bounds.getSouthWest()) &&
1939                        this._northEast.equals(bounds.getNorthEast());
1940         },
1941
1942         // @method isValid(): Boolean
1943         // Returns `true` if the bounds are properly initialized.
1944         isValid: function () {
1945                 return !!(this._southWest && this._northEast);
1946         }
1947 };
1948
1949 // TODO International date line?
1950
1951 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1952 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1953
1954 // @alternative
1955 // @factory L.latLngBounds(latlngs: LatLng[])
1956 // 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).
1957 L.latLngBounds = function (a, b) {
1958         if (a instanceof L.LatLngBounds) {
1959                 return a;
1960         }
1961         return new L.LatLngBounds(a, b);
1962 };
1963
1964
1965
1966 /*
1967  * @namespace Projection
1968  * @section
1969  * Leaflet comes with a set of already defined Projections out of the box:
1970  *
1971  * @projection L.Projection.LonLat
1972  *
1973  * Equirectangular, or Plate Carree projection — the most simple projection,
1974  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1975  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1976  * `EPSG:3395` and `Simple` CRS.
1977  */
1978
1979 L.Projection = {};
1980
1981 L.Projection.LonLat = {
1982         project: function (latlng) {
1983                 return new L.Point(latlng.lng, latlng.lat);
1984         },
1985
1986         unproject: function (point) {
1987                 return new L.LatLng(point.y, point.x);
1988         },
1989
1990         bounds: L.bounds([-180, -90], [180, 90])
1991 };
1992
1993
1994
1995 /*
1996  * @namespace Projection
1997  * @projection L.Projection.SphericalMercator
1998  *
1999  * Spherical Mercator projection — the most common projection for online maps,
2000  * used by almost all free and commercial tile providers. Assumes that Earth is
2001  * a sphere. Used by the `EPSG:3857` CRS.
2002  */
2003
2004 L.Projection.SphericalMercator = {
2005
2006         R: 6378137,
2007         MAX_LATITUDE: 85.0511287798,
2008
2009         project: function (latlng) {
2010                 var d = Math.PI / 180,
2011                     max = this.MAX_LATITUDE,
2012                     lat = Math.max(Math.min(max, latlng.lat), -max),
2013                     sin = Math.sin(lat * d);
2014
2015                 return new L.Point(
2016                                 this.R * latlng.lng * d,
2017                                 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2018         },
2019
2020         unproject: function (point) {
2021                 var d = 180 / Math.PI;
2022
2023                 return new L.LatLng(
2024                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2025                         point.x * d / this.R);
2026         },
2027
2028         bounds: (function () {
2029                 var d = 6378137 * Math.PI;
2030                 return L.bounds([-d, -d], [d, d]);
2031         })()
2032 };
2033
2034
2035
2036 /*
2037  * @class CRS
2038  * @aka L.CRS
2039  * Abstract class that defines coordinate reference systems for projecting
2040  * geographical points into pixel (screen) coordinates and back (and to
2041  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2042  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2043  *
2044  * Leaflet defines the most usual CRSs by default. If you want to use a
2045  * CRS not defined by default, take a look at the
2046  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2047  */
2048
2049 L.CRS = {
2050         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2051         // Projects geographical coordinates into pixel coordinates for a given zoom.
2052         latLngToPoint: function (latlng, zoom) {
2053                 var projectedPoint = this.projection.project(latlng),
2054                     scale = this.scale(zoom);
2055
2056                 return this.transformation._transform(projectedPoint, scale);
2057         },
2058
2059         // @method pointToLatLng(point: Point, zoom: Number): LatLng
2060         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2061         // zoom into geographical coordinates.
2062         pointToLatLng: function (point, zoom) {
2063                 var scale = this.scale(zoom),
2064                     untransformedPoint = this.transformation.untransform(point, scale);
2065
2066                 return this.projection.unproject(untransformedPoint);
2067         },
2068
2069         // @method project(latlng: LatLng): Point
2070         // Projects geographical coordinates into coordinates in units accepted for
2071         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2072         project: function (latlng) {
2073                 return this.projection.project(latlng);
2074         },
2075
2076         // @method unproject(point: Point): LatLng
2077         // Given a projected coordinate returns the corresponding LatLng.
2078         // The inverse of `project`.
2079         unproject: function (point) {
2080                 return this.projection.unproject(point);
2081         },
2082
2083         // @method scale(zoom: Number): Number
2084         // Returns the scale used when transforming projected coordinates into
2085         // pixel coordinates for a particular zoom. For example, it returns
2086         // `256 * 2^zoom` for Mercator-based CRS.
2087         scale: function (zoom) {
2088                 return 256 * Math.pow(2, zoom);
2089         },
2090
2091         // @method zoom(scale: Number): Number
2092         // Inverse of `scale()`, returns the zoom level corresponding to a scale
2093         // factor of `scale`.
2094         zoom: function (scale) {
2095                 return Math.log(scale / 256) / Math.LN2;
2096         },
2097
2098         // @method getProjectedBounds(zoom: Number): Bounds
2099         // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2100         getProjectedBounds: function (zoom) {
2101                 if (this.infinite) { return null; }
2102
2103                 var b = this.projection.bounds,
2104                     s = this.scale(zoom),
2105                     min = this.transformation.transform(b.min, s),
2106                     max = this.transformation.transform(b.max, s);
2107
2108                 return L.bounds(min, max);
2109         },
2110
2111         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2112         // Returns the distance between two geographical coordinates.
2113
2114         // @property code: String
2115         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2116         //
2117         // @property wrapLng: Number[]
2118         // An array of two numbers defining whether the longitude (horizontal) coordinate
2119         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2120         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2121         //
2122         // @property wrapLat: Number[]
2123         // Like `wrapLng`, but for the latitude (vertical) axis.
2124
2125         // wrapLng: [min, max],
2126         // wrapLat: [min, max],
2127
2128         // @property infinite: Boolean
2129         // If true, the coordinate space will be unbounded (infinite in both axes)
2130         infinite: false,
2131
2132         // @method wrapLatLng(latlng: LatLng): LatLng
2133         // Returns a `LatLng` where lat and lng has been wrapped according to the
2134         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2135         wrapLatLng: function (latlng) {
2136                 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2137                     lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2138                     alt = latlng.alt;
2139
2140                 return L.latLng(lat, lng, alt);
2141         }
2142 };
2143
2144
2145
2146 /*
2147  * @namespace CRS
2148  * @crs L.CRS.Simple
2149  *
2150  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2151  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2152  * axis should still be inverted (going from bottom to top). `distance()` returns
2153  * simple euclidean distance.
2154  */
2155
2156 L.CRS.Simple = L.extend({}, L.CRS, {
2157         projection: L.Projection.LonLat,
2158         transformation: new L.Transformation(1, 0, -1, 0),
2159
2160         scale: function (zoom) {
2161                 return Math.pow(2, zoom);
2162         },
2163
2164         zoom: function (scale) {
2165                 return Math.log(scale) / Math.LN2;
2166         },
2167
2168         distance: function (latlng1, latlng2) {
2169                 var dx = latlng2.lng - latlng1.lng,
2170                     dy = latlng2.lat - latlng1.lat;
2171
2172                 return Math.sqrt(dx * dx + dy * dy);
2173         },
2174
2175         infinite: true
2176 });
2177
2178
2179
2180 /*
2181  * @namespace CRS
2182  * @crs L.CRS.Earth
2183  *
2184  * Serves as the base for CRS that are global such that they cover the earth.
2185  * Can only be used as the base for other CRS and cannot be used directly,
2186  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2187  * meters.
2188  */
2189
2190 L.CRS.Earth = L.extend({}, L.CRS, {
2191         wrapLng: [-180, 180],
2192
2193         // Mean Earth Radius, as recommended for use by
2194         // the International Union of Geodesy and Geophysics,
2195         // see http://rosettacode.org/wiki/Haversine_formula
2196         R: 6371000,
2197
2198         // distance between two geographical points using spherical law of cosines approximation
2199         distance: function (latlng1, latlng2) {
2200                 var rad = Math.PI / 180,
2201                     lat1 = latlng1.lat * rad,
2202                     lat2 = latlng2.lat * rad,
2203                     a = Math.sin(lat1) * Math.sin(lat2) +
2204                         Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2205
2206                 return this.R * Math.acos(Math.min(a, 1));
2207         }
2208 });
2209
2210
2211
2212 /*
2213  * @namespace CRS
2214  * @crs L.CRS.EPSG3857
2215  *
2216  * The most common CRS for online maps, used by almost all free and commercial
2217  * tile providers. Uses Spherical Mercator projection. Set in by default in
2218  * Map's `crs` option.
2219  */
2220
2221 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2222         code: 'EPSG:3857',
2223         projection: L.Projection.SphericalMercator,
2224
2225         transformation: (function () {
2226                 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2227                 return new L.Transformation(scale, 0.5, -scale, 0.5);
2228         }())
2229 });
2230
2231 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2232         code: 'EPSG:900913'
2233 });
2234
2235
2236
2237 /*
2238  * @namespace CRS
2239  * @crs L.CRS.EPSG4326
2240  *
2241  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2242  *
2243  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
2244  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
2245  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
2246  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
2247  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
2248  */
2249
2250 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2251         code: 'EPSG:4326',
2252         projection: L.Projection.LonLat,
2253         transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2254 });
2255
2256
2257
2258 /*
2259  * @class Map
2260  * @aka L.Map
2261  * @inherits Evented
2262  *
2263  * The central class of the API — it is used to create a map on a page and manipulate it.
2264  *
2265  * @example
2266  *
2267  * ```js
2268  * // initialize the map on the "map" div with a given center and zoom
2269  * var map = L.map('map', {
2270  *      center: [51.505, -0.09],
2271  *      zoom: 13
2272  * });
2273  * ```
2274  *
2275  */
2276
2277 L.Map = L.Evented.extend({
2278
2279         options: {
2280                 // @section Map State Options
2281                 // @option crs: CRS = L.CRS.EPSG3857
2282                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2283                 // sure what it means.
2284                 crs: L.CRS.EPSG3857,
2285
2286                 // @option center: LatLng = undefined
2287                 // Initial geographic center of the map
2288                 center: undefined,
2289
2290                 // @option zoom: Number = undefined
2291                 // Initial map zoom level
2292                 zoom: undefined,
2293
2294                 // @option minZoom: Number = undefined
2295                 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2296                 minZoom: undefined,
2297
2298                 // @option maxZoom: Number = undefined
2299                 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2300                 maxZoom: undefined,
2301
2302                 // @option layers: Layer[] = []
2303                 // Array of layers that will be added to the map initially
2304                 layers: [],
2305
2306                 // @option maxBounds: LatLngBounds = null
2307                 // When this option is set, the map restricts the view to the given
2308                 // geographical bounds, bouncing the user back when he tries to pan
2309                 // outside the view. To set the restriction dynamically, use
2310                 // [`setMaxBounds`](#map-setmaxbounds) method.
2311                 maxBounds: undefined,
2312
2313                 // @option renderer: Renderer = *
2314                 // The default method for drawing vector layers on the map. `L.SVG`
2315                 // or `L.Canvas` by default depending on browser support.
2316                 renderer: undefined,
2317
2318
2319                 // @section Animation Options
2320                 // @option zoomAnimation: Boolean = true
2321                 // Whether the map zoom animation is enabled. By default it's enabled
2322                 // in all browsers that support CSS3 Transitions except Android.
2323                 zoomAnimation: true,
2324
2325                 // @option zoomAnimationThreshold: Number = 4
2326                 // Won't animate zoom if the zoom difference exceeds this value.
2327                 zoomAnimationThreshold: 4,
2328
2329                 // @option fadeAnimation: Boolean = true
2330                 // Whether the tile fade animation is enabled. By default it's enabled
2331                 // in all browsers that support CSS3 Transitions except Android.
2332                 fadeAnimation: true,
2333
2334                 // @option markerZoomAnimation: Boolean = true
2335                 // Whether markers animate their zoom with the zoom animation, if disabled
2336                 // they will disappear for the length of the animation. By default it's
2337                 // enabled in all browsers that support CSS3 Transitions except Android.
2338                 markerZoomAnimation: true,
2339
2340                 // @option transform3DLimit: Number = 2^23
2341                 // Defines the maximum size of a CSS translation transform. The default
2342                 // value should not be changed unless a web browser positions layers in
2343                 // the wrong place after doing a large `panBy`.
2344                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2345
2346                 // @section Interaction Options
2347                 // @option zoomSnap: Number = 1
2348                 // Forces the map's zoom level to always be a multiple of this, particularly
2349                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2350                 // By default, the zoom level snaps to the nearest integer; lower values
2351                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2352                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2353                 zoomSnap: 1,
2354
2355                 // @option zoomDelta: Number = 1
2356                 // Controls how much the map's zoom level will change after a
2357                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2358                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2359                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2360                 zoomDelta: 1,
2361
2362                 // @option trackResize: Boolean = true
2363                 // Whether the map automatically handles browser window resize to update itself.
2364                 trackResize: true
2365         },
2366
2367         initialize: function (id, options) { // (HTMLElement or String, Object)
2368                 options = L.setOptions(this, options);
2369
2370                 this._initContainer(id);
2371                 this._initLayout();
2372
2373                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2374                 this._onResize = L.bind(this._onResize, this);
2375
2376                 this._initEvents();
2377
2378                 if (options.maxBounds) {
2379                         this.setMaxBounds(options.maxBounds);
2380                 }
2381
2382                 if (options.zoom !== undefined) {
2383                         this._zoom = this._limitZoom(options.zoom);
2384                 }
2385
2386                 if (options.center && options.zoom !== undefined) {
2387                         this.setView(L.latLng(options.center), options.zoom, {reset: true});
2388                 }
2389
2390                 this._handlers = [];
2391                 this._layers = {};
2392                 this._zoomBoundLayers = {};
2393                 this._sizeChanged = true;
2394
2395                 this.callInitHooks();
2396
2397                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
2398                 this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
2399                                 this.options.zoomAnimation;
2400
2401                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
2402                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
2403                 if (this._zoomAnimated) {
2404                         this._createAnimProxy();
2405                         L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
2406                 }
2407
2408                 this._addLayers(this.options.layers);
2409         },
2410
2411
2412         // @section Methods for modifying map state
2413
2414         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2415         // Sets the view of the map (geographical center and zoom) with the given
2416         // animation options.
2417         setView: function (center, zoom, options) {
2418
2419                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
2420                 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
2421                 options = options || {};
2422
2423                 this._stop();
2424
2425                 if (this._loaded && !options.reset && options !== true) {
2426
2427                         if (options.animate !== undefined) {
2428                                 options.zoom = L.extend({animate: options.animate}, options.zoom);
2429                                 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
2430                         }
2431
2432                         // try animating pan or zoom
2433                         var moved = (this._zoom !== zoom) ?
2434                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
2435                                 this._tryAnimatedPan(center, options.pan);
2436
2437                         if (moved) {
2438                                 // prevent resize handler call, the view will refresh after animation anyway
2439                                 clearTimeout(this._sizeTimer);
2440                                 return this;
2441                         }
2442                 }
2443
2444                 // animation didn't start, just reset the map view
2445                 this._resetView(center, zoom);
2446
2447                 return this;
2448         },
2449
2450         // @method setZoom(zoom: Number, options: Zoom/pan options): this
2451         // Sets the zoom of the map.
2452         setZoom: function (zoom, options) {
2453                 if (!this._loaded) {
2454                         this._zoom = zoom;
2455                         return this;
2456                 }
2457                 return this.setView(this.getCenter(), zoom, {zoom: options});
2458         },
2459
2460         // @method zoomIn(delta?: Number, options?: Zoom options): this
2461         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2462         zoomIn: function (delta, options) {
2463                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2464                 return this.setZoom(this._zoom + delta, options);
2465         },
2466
2467         // @method zoomOut(delta?: Number, options?: Zoom options): this
2468         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2469         zoomOut: function (delta, options) {
2470                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2471                 return this.setZoom(this._zoom - delta, options);
2472         },
2473
2474         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2475         // Zooms the map while keeping a specified geographical point on the map
2476         // stationary (e.g. used internally for scroll zoom and double-click zoom).
2477         // @alternative
2478         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2479         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2480         setZoomAround: function (latlng, zoom, options) {
2481                 var scale = this.getZoomScale(zoom),
2482                     viewHalf = this.getSize().divideBy(2),
2483                     containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2484
2485                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2486                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2487
2488                 return this.setView(newCenter, zoom, {zoom: options});
2489         },
2490
2491         _getBoundsCenterZoom: function (bounds, options) {
2492
2493                 options = options || {};
2494                 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2495
2496                 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2497                     paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2498
2499                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2500
2501                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2502
2503                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2504
2505                     swPoint = this.project(bounds.getSouthWest(), zoom),
2506                     nePoint = this.project(bounds.getNorthEast(), zoom),
2507                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2508
2509                 return {
2510                         center: center,
2511                         zoom: zoom
2512                 };
2513         },
2514
2515         // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this
2516         // Sets a map view that contains the given geographical bounds with the
2517         // maximum zoom level possible.
2518         fitBounds: function (bounds, options) {
2519
2520                 bounds = L.latLngBounds(bounds);
2521
2522                 if (!bounds.isValid()) {
2523                         throw new Error('Bounds are not valid.');
2524                 }
2525
2526                 var target = this._getBoundsCenterZoom(bounds, options);
2527                 return this.setView(target.center, target.zoom, options);
2528         },
2529
2530         // @method fitWorld(options?: fitBounds options): this
2531         // Sets a map view that mostly contains the whole world with the maximum
2532         // zoom level possible.
2533         fitWorld: function (options) {
2534                 return this.fitBounds([[-90, -180], [90, 180]], options);
2535         },
2536
2537         // @method panTo(latlng: LatLng, options?: Pan options): this
2538         // Pans the map to a given center.
2539         panTo: function (center, options) { // (LatLng)
2540                 return this.setView(center, this._zoom, {pan: options});
2541         },
2542
2543         // @method panBy(offset: Point): this
2544         // Pans the map by a given number of pixels (animated).
2545         panBy: function (offset, options) {
2546                 offset = L.point(offset).round();
2547                 options = options || {};
2548
2549                 if (!offset.x && !offset.y) {
2550                         return this.fire('moveend');
2551                 }
2552                 // If we pan too far, Chrome gets issues with tiles
2553                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
2554                 if (options.animate !== true && !this.getSize().contains(offset)) {
2555                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
2556                         return this;
2557                 }
2558
2559                 if (!this._panAnim) {
2560                         this._panAnim = new L.PosAnimation();
2561
2562                         this._panAnim.on({
2563                                 'step': this._onPanTransitionStep,
2564                                 'end': this._onPanTransitionEnd
2565                         }, this);
2566                 }
2567
2568                 // don't fire movestart if animating inertia
2569                 if (!options.noMoveStart) {
2570                         this.fire('movestart');
2571                 }
2572
2573                 // animate pan unless animate: false specified
2574                 if (options.animate !== false) {
2575                         L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
2576
2577                         var newPos = this._getMapPanePos().subtract(offset).round();
2578                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
2579                 } else {
2580                         this._rawPanBy(offset);
2581                         this.fire('move').fire('moveend');
2582                 }
2583
2584                 return this;
2585         },
2586
2587         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
2588         // Sets the view of the map (geographical center and zoom) performing a smooth
2589         // pan-zoom animation.
2590         flyTo: function (targetCenter, targetZoom, options) {
2591
2592                 options = options || {};
2593                 if (options.animate === false || !L.Browser.any3d) {
2594                         return this.setView(targetCenter, targetZoom, options);
2595                 }
2596
2597                 this._stop();
2598
2599                 var from = this.project(this.getCenter()),
2600                     to = this.project(targetCenter),
2601                     size = this.getSize(),
2602                     startZoom = this._zoom;
2603
2604                 targetCenter = L.latLng(targetCenter);
2605                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
2606
2607                 var w0 = Math.max(size.x, size.y),
2608                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
2609                     u1 = (to.distanceTo(from)) || 1,
2610                     rho = 1.42,
2611                     rho2 = rho * rho;
2612
2613                 function r(i) {
2614                         var s1 = i ? -1 : 1,
2615                             s2 = i ? w1 : w0,
2616                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
2617                             b1 = 2 * s2 * rho2 * u1,
2618                             b = t1 / b1,
2619                             sq = Math.sqrt(b * b + 1) - b;
2620
2621                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
2622                             // thus triggering an infinite loop in flyTo
2623                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
2624
2625                         return log;
2626                 }
2627
2628                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
2629                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
2630                 function tanh(n) { return sinh(n) / cosh(n); }
2631
2632                 var r0 = r(0);
2633
2634                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
2635                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
2636
2637                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
2638
2639                 var start = Date.now(),
2640                     S = (r(1) - r0) / rho,
2641                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
2642
2643                 function frame() {
2644                         var t = (Date.now() - start) / duration,
2645                             s = easeOut(t) * S;
2646
2647                         if (t <= 1) {
2648                                 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
2649
2650                                 this._move(
2651                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
2652                                         this.getScaleZoom(w0 / w(s), startZoom),
2653                                         {flyTo: true});
2654
2655                         } else {
2656                                 this
2657                                         ._move(targetCenter, targetZoom)
2658                                         ._moveEnd(true);
2659                         }
2660                 }
2661
2662                 this._moveStart(true);
2663
2664                 frame.call(this);
2665                 return this;
2666         },
2667
2668         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
2669         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
2670         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
2671         flyToBounds: function (bounds, options) {
2672                 var target = this._getBoundsCenterZoom(bounds, options);
2673                 return this.flyTo(target.center, target.zoom, options);
2674         },
2675
2676         // @method setMaxBounds(bounds: Bounds): this
2677         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2678         setMaxBounds: function (bounds) {
2679                 bounds = L.latLngBounds(bounds);
2680
2681                 if (!bounds.isValid()) {
2682                         this.options.maxBounds = null;
2683                         return this.off('moveend', this._panInsideMaxBounds);
2684                 } else if (this.options.maxBounds) {
2685                         this.off('moveend', this._panInsideMaxBounds);
2686                 }
2687
2688                 this.options.maxBounds = bounds;
2689
2690                 if (this._loaded) {
2691                         this._panInsideMaxBounds();
2692                 }
2693
2694                 return this.on('moveend', this._panInsideMaxBounds);
2695         },
2696
2697         // @method setMinZoom(zoom: Number): this
2698         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2699         setMinZoom: function (zoom) {
2700                 this.options.minZoom = zoom;
2701
2702                 if (this._loaded && this.getZoom() < this.options.minZoom) {
2703                         return this.setZoom(zoom);
2704                 }
2705
2706                 return this;
2707         },
2708
2709         // @method setMaxZoom(zoom: Number): this
2710         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2711         setMaxZoom: function (zoom) {
2712                 this.options.maxZoom = zoom;
2713
2714                 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2715                         return this.setZoom(zoom);
2716                 }
2717
2718                 return this;
2719         },
2720
2721         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2722         // 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.
2723         panInsideBounds: function (bounds, options) {
2724                 this._enforcingBounds = true;
2725                 var center = this.getCenter(),
2726                     newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2727
2728                 if (!center.equals(newCenter)) {
2729                         this.panTo(newCenter, options);
2730                 }
2731
2732                 this._enforcingBounds = false;
2733                 return this;
2734         },
2735
2736         // @method invalidateSize(options: Zoom/Pan options): this
2737         // Checks if the map container size changed and updates the map if so —
2738         // call it after you've changed the map size dynamically, also animating
2739         // pan by default. If `options.pan` is `false`, panning will not occur.
2740         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2741         // that it doesn't happen often even if the method is called many
2742         // times in a row.
2743
2744         // @alternative
2745         // @method invalidateSize(animate: Boolean): this
2746         // Checks if the map container size changed and updates the map if so —
2747         // call it after you've changed the map size dynamically, also animating
2748         // pan by default.
2749         invalidateSize: function (options) {
2750                 if (!this._loaded) { return this; }
2751
2752                 options = L.extend({
2753                         animate: false,
2754                         pan: true
2755                 }, options === true ? {animate: true} : options);
2756
2757                 var oldSize = this.getSize();
2758                 this._sizeChanged = true;
2759                 this._lastCenter = null;
2760
2761                 var newSize = this.getSize(),
2762                     oldCenter = oldSize.divideBy(2).round(),
2763                     newCenter = newSize.divideBy(2).round(),
2764                     offset = oldCenter.subtract(newCenter);
2765
2766                 if (!offset.x && !offset.y) { return this; }
2767
2768                 if (options.animate && options.pan) {
2769                         this.panBy(offset);
2770
2771                 } else {
2772                         if (options.pan) {
2773                                 this._rawPanBy(offset);
2774                         }
2775
2776                         this.fire('move');
2777
2778                         if (options.debounceMoveend) {
2779                                 clearTimeout(this._sizeTimer);
2780                                 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2781                         } else {
2782                                 this.fire('moveend');
2783                         }
2784                 }
2785
2786                 // @section Map state change events
2787                 // @event resize: ResizeEvent
2788                 // Fired when the map is resized.
2789                 return this.fire('resize', {
2790                         oldSize: oldSize,
2791                         newSize: newSize
2792                 });
2793         },
2794
2795         // @section Methods for modifying map state
2796         // @method stop(): this
2797         // Stops the currently running `panTo` or `flyTo` animation, if any.
2798         stop: function () {
2799                 this.setZoom(this._limitZoom(this._zoom));
2800                 if (!this.options.zoomSnap) {
2801                         this.fire('viewreset');
2802                 }
2803                 return this._stop();
2804         },
2805
2806         // @section Geolocation methods
2807         // @method locate(options?: Locate options): this
2808         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
2809         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
2810         // and optionally sets the map view to the user's location with respect to
2811         // detection accuracy (or to the world view if geolocation failed).
2812         // Note that, if your page doesn't use HTTPS, this method will fail in
2813         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
2814         // See `Locate options` for more details.
2815         locate: function (options) {
2816
2817                 options = this._locateOptions = L.extend({
2818                         timeout: 10000,
2819                         watch: false
2820                         // setView: false
2821                         // maxZoom: <Number>
2822                         // maximumAge: 0
2823                         // enableHighAccuracy: false
2824                 }, options);
2825
2826                 if (!('geolocation' in navigator)) {
2827                         this._handleGeolocationError({
2828                                 code: 0,
2829                                 message: 'Geolocation not supported.'
2830                         });
2831                         return this;
2832                 }
2833
2834                 var onResponse = L.bind(this._handleGeolocationResponse, this),
2835                     onError = L.bind(this._handleGeolocationError, this);
2836
2837                 if (options.watch) {
2838                         this._locationWatchId =
2839                                 navigator.geolocation.watchPosition(onResponse, onError, options);
2840                 } else {
2841                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
2842                 }
2843                 return this;
2844         },
2845
2846         // @method stopLocate(): this
2847         // Stops watching location previously initiated by `map.locate({watch: true})`
2848         // and aborts resetting the map view if map.locate was called with
2849         // `{setView: true}`.
2850         stopLocate: function () {
2851                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
2852                         navigator.geolocation.clearWatch(this._locationWatchId);
2853                 }
2854                 if (this._locateOptions) {
2855                         this._locateOptions.setView = false;
2856                 }
2857                 return this;
2858         },
2859
2860         _handleGeolocationError: function (error) {
2861                 var c = error.code,
2862                     message = error.message ||
2863                             (c === 1 ? 'permission denied' :
2864                             (c === 2 ? 'position unavailable' : 'timeout'));
2865
2866                 if (this._locateOptions.setView && !this._loaded) {
2867                         this.fitWorld();
2868                 }
2869
2870                 // @section Location events
2871                 // @event locationerror: ErrorEvent
2872                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
2873                 this.fire('locationerror', {
2874                         code: c,
2875                         message: 'Geolocation error: ' + message + '.'
2876                 });
2877         },
2878
2879         _handleGeolocationResponse: function (pos) {
2880                 var lat = pos.coords.latitude,
2881                     lng = pos.coords.longitude,
2882                     latlng = new L.LatLng(lat, lng),
2883                     bounds = latlng.toBounds(pos.coords.accuracy),
2884                     options = this._locateOptions;
2885
2886                 if (options.setView) {
2887                         var zoom = this.getBoundsZoom(bounds);
2888                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
2889                 }
2890
2891                 var data = {
2892                         latlng: latlng,
2893                         bounds: bounds,
2894                         timestamp: pos.timestamp
2895                 };
2896
2897                 for (var i in pos.coords) {
2898                         if (typeof pos.coords[i] === 'number') {
2899                                 data[i] = pos.coords[i];
2900                         }
2901                 }
2902
2903                 // @event locationfound: LocationEvent
2904                 // Fired when geolocation (using the [`locate`](#map-locate) method)
2905                 // went successfully.
2906                 this.fire('locationfound', data);
2907         },
2908
2909         // TODO handler.addTo
2910         // TODO Appropiate docs section?
2911         // @section Other Methods
2912         // @method addHandler(name: String, HandlerClass: Function): this
2913         // Adds a new `Handler` to the map, given its name and constructor function.
2914         addHandler: function (name, HandlerClass) {
2915                 if (!HandlerClass) { return this; }
2916
2917                 var handler = this[name] = new HandlerClass(this);
2918
2919                 this._handlers.push(handler);
2920
2921                 if (this.options[name]) {
2922                         handler.enable();
2923                 }
2924
2925                 return this;
2926         },
2927
2928         // @method remove(): this
2929         // Destroys the map and clears all related event listeners.
2930         remove: function () {
2931
2932                 this._initEvents(true);
2933
2934                 if (this._containerId !== this._container._leaflet_id) {
2935                         throw new Error('Map container is being reused by another instance');
2936                 }
2937
2938                 try {
2939                         // throws error in IE6-8
2940                         delete this._container._leaflet_id;
2941                         delete this._containerId;
2942                 } catch (e) {
2943                         /*eslint-disable */
2944                         this._container._leaflet_id = undefined;
2945                         /*eslint-enable */
2946                         this._containerId = undefined;
2947                 }
2948
2949                 L.DomUtil.remove(this._mapPane);
2950
2951                 if (this._clearControlPos) {
2952                         this._clearControlPos();
2953                 }
2954
2955                 this._clearHandlers();
2956
2957                 if (this._loaded) {
2958                         // @section Map state change events
2959                         // @event unload: Event
2960                         // Fired when the map is destroyed with [remove](#map-remove) method.
2961                         this.fire('unload');
2962                 }
2963
2964                 for (var i in this._layers) {
2965                         this._layers[i].remove();
2966                 }
2967
2968                 return this;
2969         },
2970
2971         // @section Other Methods
2972         // @method createPane(name: String, container?: HTMLElement): HTMLElement
2973         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2974         // then returns it. The pane is created as a children of `container`, or
2975         // as a children of the main map pane if not set.
2976         createPane: function (name, container) {
2977                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
2978                     pane = L.DomUtil.create('div', className, container || this._mapPane);
2979
2980                 if (name) {
2981                         this._panes[name] = pane;
2982                 }
2983                 return pane;
2984         },
2985
2986         // @section Methods for Getting Map State
2987
2988         // @method getCenter(): LatLng
2989         // Returns the geographical center of the map view
2990         getCenter: function () {
2991                 this._checkIfLoaded();
2992
2993                 if (this._lastCenter && !this._moved()) {
2994                         return this._lastCenter;
2995                 }
2996                 return this.layerPointToLatLng(this._getCenterLayerPoint());
2997         },
2998
2999         // @method getZoom(): Number
3000         // Returns the current zoom level of the map view
3001         getZoom: function () {
3002                 return this._zoom;
3003         },
3004
3005         // @method getBounds(): LatLngBounds
3006         // Returns the geographical bounds visible in the current map view
3007         getBounds: function () {
3008                 var bounds = this.getPixelBounds(),
3009                     sw = this.unproject(bounds.getBottomLeft()),
3010                     ne = this.unproject(bounds.getTopRight());
3011
3012                 return new L.LatLngBounds(sw, ne);
3013         },
3014
3015         // @method getMinZoom(): Number
3016         // 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.
3017         getMinZoom: function () {
3018                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3019         },
3020
3021         // @method getMaxZoom(): Number
3022         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3023         getMaxZoom: function () {
3024                 return this.options.maxZoom === undefined ?
3025                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3026                         this.options.maxZoom;
3027         },
3028
3029         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3030         // Returns the maximum zoom level on which the given bounds fit to the map
3031         // view in its entirety. If `inside` (optional) is set to `true`, the method
3032         // instead returns the minimum zoom level on which the map view fits into
3033         // the given bounds in its entirety.
3034         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3035                 bounds = L.latLngBounds(bounds);
3036                 padding = L.point(padding || [0, 0]);
3037
3038                 var zoom = this.getZoom() || 0,
3039                     min = this.getMinZoom(),
3040                     max = this.getMaxZoom(),
3041                     nw = bounds.getNorthWest(),
3042                     se = bounds.getSouthEast(),
3043                     size = this.getSize().subtract(padding),
3044                     boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)),
3045                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3046
3047                 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
3048                 zoom = this.getScaleZoom(scale, zoom);
3049
3050                 if (snap) {
3051                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3052                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3053                 }
3054
3055                 return Math.max(min, Math.min(max, zoom));
3056         },
3057
3058         // @method getSize(): Point
3059         // Returns the current size of the map container (in pixels).
3060         getSize: function () {
3061                 if (!this._size || this._sizeChanged) {
3062                         this._size = new L.Point(
3063                                 this._container.clientWidth,
3064                                 this._container.clientHeight);
3065
3066                         this._sizeChanged = false;
3067                 }
3068                 return this._size.clone();
3069         },
3070
3071         // @method getPixelBounds(): Bounds
3072         // Returns the bounds of the current map view in projected pixel
3073         // coordinates (sometimes useful in layer and overlay implementations).
3074         getPixelBounds: function (center, zoom) {
3075                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3076                 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3077         },
3078
3079         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3080         // the map pane? "left point of the map layer" can be confusing, specially
3081         // since there can be negative offsets.
3082         // @method getPixelOrigin(): Point
3083         // Returns the projected pixel coordinates of the top left point of
3084         // the map layer (useful in custom layer and overlay implementations).
3085         getPixelOrigin: function () {
3086                 this._checkIfLoaded();
3087                 return this._pixelOrigin;
3088         },
3089
3090         // @method getPixelWorldBounds(zoom?: Number): Bounds
3091         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3092         // If `zoom` is omitted, the map's current zoom level is used.
3093         getPixelWorldBounds: function (zoom) {
3094                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3095         },
3096
3097         // @section Other Methods
3098
3099         // @method getPane(pane: String|HTMLElement): HTMLElement
3100         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3101         getPane: function (pane) {
3102                 return typeof pane === 'string' ? this._panes[pane] : pane;
3103         },
3104
3105         // @method getPanes(): Object
3106         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3107         // the panes as values.
3108         getPanes: function () {
3109                 return this._panes;
3110         },
3111
3112         // @method getContainer: HTMLElement
3113         // Returns the HTML element that contains the map.
3114         getContainer: function () {
3115                 return this._container;
3116         },
3117
3118
3119         // @section Conversion Methods
3120
3121         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3122         // Returns the scale factor to be applied to a map transition from zoom level
3123         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3124         getZoomScale: function (toZoom, fromZoom) {
3125                 // TODO replace with universal implementation after refactoring projections
3126                 var crs = this.options.crs;
3127                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3128                 return crs.scale(toZoom) / crs.scale(fromZoom);
3129         },
3130
3131         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3132         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3133         // level and everything is scaled by a factor of `scale`. Inverse of
3134         // [`getZoomScale`](#map-getZoomScale).
3135         getScaleZoom: function (scale, fromZoom) {
3136                 var crs = this.options.crs;
3137                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3138                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3139                 return isNaN(zoom) ? Infinity : zoom;
3140         },
3141
3142         // @method project(latlng: LatLng, zoom: Number): Point
3143         // Projects a geographical coordinate `LatLng` according to the projection
3144         // of the map's CRS, then scales it according to `zoom` and the CRS's
3145         // `Transformation`. The result is pixel coordinate relative to
3146         // the CRS origin.
3147         project: function (latlng, zoom) {
3148                 zoom = zoom === undefined ? this._zoom : zoom;
3149                 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
3150         },
3151
3152         // @method unproject(point: Point, zoom: Number): LatLng
3153         // Inverse of [`project`](#map-project).
3154         unproject: function (point, zoom) {
3155                 zoom = zoom === undefined ? this._zoom : zoom;
3156                 return this.options.crs.pointToLatLng(L.point(point), zoom);
3157         },
3158
3159         // @method layerPointToLatLng(point: Point): LatLng
3160         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3161         // returns the corresponding geographical coordinate (for the current zoom level).
3162         layerPointToLatLng: function (point) {
3163                 var projectedPoint = L.point(point).add(this.getPixelOrigin());
3164                 return this.unproject(projectedPoint);
3165         },
3166
3167         // @method latLngToLayerPoint(latlng: LatLng): Point
3168         // Given a geographical coordinate, returns the corresponding pixel coordinate
3169         // relative to the [origin pixel](#map-getpixelorigin).
3170         latLngToLayerPoint: function (latlng) {
3171                 var projectedPoint = this.project(L.latLng(latlng))._round();
3172                 return projectedPoint._subtract(this.getPixelOrigin());
3173         },
3174
3175         // @method wrapLatLng(latlng: LatLng): LatLng
3176         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3177         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3178         // CRS's bounds.
3179         // By default this means longitude is wrapped around the dateline so its
3180         // value is between -180 and +180 degrees.
3181         wrapLatLng: function (latlng) {
3182                 return this.options.crs.wrapLatLng(L.latLng(latlng));
3183         },
3184
3185         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3186         // Returns the distance between two geographical coordinates according to
3187         // the map's CRS. By default this measures distance in meters.
3188         distance: function (latlng1, latlng2) {
3189                 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
3190         },
3191
3192         // @method containerPointToLayerPoint(point: Point): Point
3193         // Given a pixel coordinate relative to the map container, returns the corresponding
3194         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3195         containerPointToLayerPoint: function (point) { // (Point)
3196                 return L.point(point).subtract(this._getMapPanePos());
3197         },
3198
3199         // @method layerPointToContainerPoint(point: Point): Point
3200         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3201         // returns the corresponding pixel coordinate relative to the map container.
3202         layerPointToContainerPoint: function (point) { // (Point)
3203                 return L.point(point).add(this._getMapPanePos());
3204         },
3205
3206         // @method containerPointToLatLng(point: Point): Point
3207         // Given a pixel coordinate relative to the map container, returns
3208         // the corresponding geographical coordinate (for the current zoom level).
3209         containerPointToLatLng: function (point) {
3210                 var layerPoint = this.containerPointToLayerPoint(L.point(point));
3211                 return this.layerPointToLatLng(layerPoint);
3212         },
3213
3214         // @method latLngToContainerPoint(latlng: LatLng): Point
3215         // Given a geographical coordinate, returns the corresponding pixel coordinate
3216         // relative to the map container.
3217         latLngToContainerPoint: function (latlng) {
3218                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
3219         },
3220
3221         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3222         // Given a MouseEvent object, returns the pixel coordinate relative to the
3223         // map container where the event took place.
3224         mouseEventToContainerPoint: function (e) {
3225                 return L.DomEvent.getMousePosition(e, this._container);
3226         },
3227
3228         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3229         // Given a MouseEvent object, returns the pixel coordinate relative to
3230         // the [origin pixel](#map-getpixelorigin) where the event took place.
3231         mouseEventToLayerPoint: function (e) {
3232                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3233         },
3234
3235         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3236         // Given a MouseEvent object, returns geographical coordinate where the
3237         // event took place.
3238         mouseEventToLatLng: function (e) { // (MouseEvent)
3239                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3240         },
3241
3242
3243         // map initialization methods
3244
3245         _initContainer: function (id) {
3246                 var container = this._container = L.DomUtil.get(id);
3247
3248                 if (!container) {
3249                         throw new Error('Map container not found.');
3250                 } else if (container._leaflet_id) {
3251                         throw new Error('Map container is already initialized.');
3252                 }
3253
3254                 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
3255                 this._containerId = L.Util.stamp(container);
3256         },
3257
3258         _initLayout: function () {
3259                 var container = this._container;
3260
3261                 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
3262
3263                 L.DomUtil.addClass(container, 'leaflet-container' +
3264                         (L.Browser.touch ? ' leaflet-touch' : '') +
3265                         (L.Browser.retina ? ' leaflet-retina' : '') +
3266                         (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
3267                         (L.Browser.safari ? ' leaflet-safari' : '') +
3268                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3269
3270                 var position = L.DomUtil.getStyle(container, 'position');
3271
3272                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3273                         container.style.position = 'relative';
3274                 }
3275
3276                 this._initPanes();
3277
3278                 if (this._initControlPos) {
3279                         this._initControlPos();
3280                 }
3281         },
3282
3283         _initPanes: function () {
3284                 var panes = this._panes = {};
3285                 this._paneRenderers = {};
3286
3287                 // @section
3288                 //
3289                 // Panes are DOM elements used to control the ordering of layers on the map. You
3290                 // can access panes with [`map.getPane`](#map-getpane) or
3291                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3292                 // [`map.createPane`](#map-createpane) method.
3293                 //
3294                 // Every map has the following default panes that differ only in zIndex.
3295                 //
3296                 // @pane mapPane: HTMLElement = 'auto'
3297                 // Pane that contains all other map panes
3298
3299                 this._mapPane = this.createPane('mapPane', this._container);
3300                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3301
3302                 // @pane tilePane: HTMLElement = 200
3303                 // Pane for `GridLayer`s and `TileLayer`s
3304                 this.createPane('tilePane');
3305                 // @pane overlayPane: HTMLElement = 400
3306                 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3307                 this.createPane('shadowPane');
3308                 // @pane shadowPane: HTMLElement = 500
3309                 // Pane for overlay shadows (e.g. `Marker` shadows)
3310                 this.createPane('overlayPane');
3311                 // @pane markerPane: HTMLElement = 600
3312                 // Pane for `Icon`s of `Marker`s
3313                 this.createPane('markerPane');
3314                 // @pane tooltipPane: HTMLElement = 650
3315                 // Pane for tooltip.
3316                 this.createPane('tooltipPane');
3317                 // @pane popupPane: HTMLElement = 700
3318                 // Pane for `Popup`s.
3319                 this.createPane('popupPane');
3320
3321                 if (!this.options.markerZoomAnimation) {
3322                         L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3323                         L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3324                 }
3325         },
3326
3327
3328         // private methods that modify map state
3329
3330         // @section Map state change events
3331         _resetView: function (center, zoom) {
3332                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3333
3334                 var loading = !this._loaded;
3335                 this._loaded = true;
3336                 zoom = this._limitZoom(zoom);
3337
3338                 this.fire('viewprereset');
3339
3340                 var zoomChanged = this._zoom !== zoom;
3341                 this
3342                         ._moveStart(zoomChanged)
3343                         ._move(center, zoom)
3344                         ._moveEnd(zoomChanged);
3345
3346                 // @event viewreset: Event
3347                 // Fired when the map needs to redraw its content (this usually happens
3348                 // on map zoom or load). Very useful for creating custom overlays.
3349                 this.fire('viewreset');
3350
3351                 // @event load: Event
3352                 // Fired when the map is initialized (when its center and zoom are set
3353                 // for the first time).
3354                 if (loading) {
3355                         this.fire('load');
3356                 }
3357         },
3358
3359         _moveStart: function (zoomChanged) {
3360                 // @event zoomstart: Event
3361                 // Fired when the map zoom is about to change (e.g. before zoom animation).
3362                 // @event movestart: Event
3363                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3364                 if (zoomChanged) {
3365                         this.fire('zoomstart');
3366                 }
3367                 return this.fire('movestart');
3368         },
3369
3370         _move: function (center, zoom, data) {
3371                 if (zoom === undefined) {
3372                         zoom = this._zoom;
3373                 }
3374                 var zoomChanged = this._zoom !== zoom;
3375
3376                 this._zoom = zoom;
3377                 this._lastCenter = center;
3378                 this._pixelOrigin = this._getNewPixelOrigin(center);
3379
3380                 // @event zoom: Event
3381                 // Fired repeatedly during any change in zoom level, including zoom
3382                 // and fly animations.
3383                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
3384                         this.fire('zoom', data);
3385                 }
3386
3387                 // @event move: Event
3388                 // Fired repeatedly during any movement of the map, including pan and
3389                 // fly animations.
3390                 return this.fire('move', data);
3391         },
3392
3393         _moveEnd: function (zoomChanged) {
3394                 // @event zoomend: Event
3395                 // Fired when the map has changed, after any animations.
3396                 if (zoomChanged) {
3397                         this.fire('zoomend');
3398                 }
3399
3400                 // @event moveend: Event
3401                 // Fired when the center of the map stops changing (e.g. user stopped
3402                 // dragging the map).
3403                 return this.fire('moveend');
3404         },
3405
3406         _stop: function () {
3407                 L.Util.cancelAnimFrame(this._flyToFrame);
3408                 if (this._panAnim) {
3409                         this._panAnim.stop();
3410                 }
3411                 return this;
3412         },
3413
3414         _rawPanBy: function (offset) {
3415                 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3416         },
3417
3418         _getZoomSpan: function () {
3419                 return this.getMaxZoom() - this.getMinZoom();
3420         },
3421
3422         _panInsideMaxBounds: function () {
3423                 if (!this._enforcingBounds) {
3424                         this.panInsideBounds(this.options.maxBounds);
3425                 }
3426         },
3427
3428         _checkIfLoaded: function () {
3429                 if (!this._loaded) {
3430                         throw new Error('Set map center and zoom first.');
3431                 }
3432         },
3433
3434         // DOM event handling
3435
3436         // @section Interaction events
3437         _initEvents: function (remove) {
3438                 if (!L.DomEvent) { return; }
3439
3440                 this._targets = {};
3441                 this._targets[L.stamp(this._container)] = this;
3442
3443                 var onOff = remove ? 'off' : 'on';
3444
3445                 // @event click: MouseEvent
3446                 // Fired when the user clicks (or taps) the map.
3447                 // @event dblclick: MouseEvent
3448                 // Fired when the user double-clicks (or double-taps) the map.
3449                 // @event mousedown: MouseEvent
3450                 // Fired when the user pushes the mouse button on the map.
3451                 // @event mouseup: MouseEvent
3452                 // Fired when the user releases the mouse button on the map.
3453                 // @event mouseover: MouseEvent
3454                 // Fired when the mouse enters the map.
3455                 // @event mouseout: MouseEvent
3456                 // Fired when the mouse leaves the map.
3457                 // @event mousemove: MouseEvent
3458                 // Fired while the mouse moves over the map.
3459                 // @event contextmenu: MouseEvent
3460                 // Fired when the user pushes the right mouse button on the map, prevents
3461                 // default browser context menu from showing if there are listeners on
3462                 // this event. Also fired on mobile when the user holds a single touch
3463                 // for a second (also called long press).
3464                 // @event keypress: KeyboardEvent
3465                 // Fired when the user presses a key from the keyboard while the map is focused.
3466                 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3467                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3468
3469                 if (this.options.trackResize) {
3470                         L.DomEvent[onOff](window, 'resize', this._onResize, this);
3471                 }
3472
3473                 if (L.Browser.any3d && this.options.transform3DLimit) {
3474                         this[onOff]('moveend', this._onMoveEnd);
3475                 }
3476         },
3477
3478         _onResize: function () {
3479                 L.Util.cancelAnimFrame(this._resizeRequest);
3480                 this._resizeRequest = L.Util.requestAnimFrame(
3481                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
3482         },
3483
3484         _onScroll: function () {
3485                 this._container.scrollTop  = 0;
3486                 this._container.scrollLeft = 0;
3487         },
3488
3489         _onMoveEnd: function () {
3490                 var pos = this._getMapPanePos();
3491                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3492                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3493                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3494                         this._resetView(this.getCenter(), this.getZoom());
3495                 }
3496         },
3497
3498         _findEventTargets: function (e, type) {
3499                 var targets = [],
3500                     target,
3501                     isHover = type === 'mouseout' || type === 'mouseover',
3502                     src = e.target || e.srcElement,
3503                     dragging = false;
3504
3505                 while (src) {
3506                         target = this._targets[L.stamp(src)];
3507                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3508                                 // Prevent firing click after you just dragged an object.
3509                                 dragging = true;
3510                                 break;
3511                         }
3512                         if (target && target.listens(type, true)) {
3513                                 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3514                                 targets.push(target);
3515                                 if (isHover) { break; }
3516                         }
3517                         if (src === this._container) { break; }
3518                         src = src.parentNode;
3519                 }
3520                 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3521                         targets = [this];
3522                 }
3523                 return targets;
3524         },
3525
3526         _handleDOMEvent: function (e) {
3527                 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3528
3529                 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3530
3531                 if (type === 'mousedown') {
3532                         // prevents outline when clicking on keyboard-focusable element
3533                         L.DomUtil.preventOutline(e.target || e.srcElement);
3534                 }
3535
3536                 this._fireDOMEvent(e, type);
3537         },
3538
3539         _fireDOMEvent: function (e, type, targets) {
3540
3541                 if (e.type === 'click') {
3542                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3543                         // @event preclick: MouseEvent
3544                         // Fired before mouse click on the map (sometimes useful when you
3545                         // want something to happen on click before any existing click
3546                         // handlers start running).
3547                         var synth = L.Util.extend({}, e);
3548                         synth.type = 'preclick';
3549                         this._fireDOMEvent(synth, synth.type, targets);
3550                 }
3551
3552                 if (e._stopped) { return; }
3553
3554                 // Find the layer the event is propagating from and its parents.
3555                 targets = (targets || []).concat(this._findEventTargets(e, type));
3556
3557                 if (!targets.length) { return; }
3558
3559                 var target = targets[0];
3560                 if (type === 'contextmenu' && target.listens(type, true)) {
3561                         L.DomEvent.preventDefault(e);
3562                 }
3563
3564                 var data = {
3565                         originalEvent: e
3566                 };
3567
3568                 if (e.type !== 'keypress') {
3569                         var isMarker = target instanceof L.Marker;
3570                         data.containerPoint = isMarker ?
3571                                         this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3572                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3573                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3574                 }
3575
3576                 for (var i = 0; i < targets.length; i++) {
3577                         targets[i].fire(type, data, true);
3578                         if (data.originalEvent._stopped ||
3579                                 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3580                 }
3581         },
3582
3583         _draggableMoved: function (obj) {
3584                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3585                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3586         },
3587
3588         _clearHandlers: function () {
3589                 for (var i = 0, len = this._handlers.length; i < len; i++) {
3590                         this._handlers[i].disable();
3591                 }
3592         },
3593
3594         // @section Other Methods
3595
3596         // @method whenReady(fn: Function, context?: Object): this
3597         // Runs the given function `fn` when the map gets initialized with
3598         // a view (center and zoom) and at least one layer, or immediately
3599         // if it's already initialized, optionally passing a function context.
3600         whenReady: function (callback, context) {
3601                 if (this._loaded) {
3602                         callback.call(context || this, {target: this});
3603                 } else {
3604                         this.on('load', callback, context);
3605                 }
3606                 return this;
3607         },
3608
3609
3610         // private methods for getting map state
3611
3612         _getMapPanePos: function () {
3613                 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3614         },
3615
3616         _moved: function () {
3617                 var pos = this._getMapPanePos();
3618                 return pos && !pos.equals([0, 0]);
3619         },
3620
3621         _getTopLeftPoint: function (center, zoom) {
3622                 var pixelOrigin = center && zoom !== undefined ?
3623                         this._getNewPixelOrigin(center, zoom) :
3624                         this.getPixelOrigin();
3625                 return pixelOrigin.subtract(this._getMapPanePos());
3626         },
3627
3628         _getNewPixelOrigin: function (center, zoom) {
3629                 var viewHalf = this.getSize()._divideBy(2);
3630                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3631         },
3632
3633         _latLngToNewLayerPoint: function (latlng, zoom, center) {
3634                 var topLeft = this._getNewPixelOrigin(center, zoom);
3635                 return this.project(latlng, zoom)._subtract(topLeft);
3636         },
3637
3638         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
3639                 var topLeft = this._getNewPixelOrigin(center, zoom);
3640                 return L.bounds([
3641                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
3642                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
3643                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
3644                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
3645                 ]);
3646         },
3647
3648         // layer point of the current center
3649         _getCenterLayerPoint: function () {
3650                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3651         },
3652
3653         // offset of the specified place to the current center in pixels
3654         _getCenterOffset: function (latlng) {
3655                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3656         },
3657
3658         // adjust center for view to get inside bounds
3659         _limitCenter: function (center, zoom, bounds) {
3660
3661                 if (!bounds) { return center; }
3662
3663                 var centerPoint = this.project(center, zoom),
3664                     viewHalf = this.getSize().divideBy(2),
3665                     viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3666                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3667
3668                 // If offset is less than a pixel, ignore.
3669                 // This prevents unstable projections from getting into
3670                 // an infinite loop of tiny offsets.
3671                 if (offset.round().equals([0, 0])) {
3672                         return center;
3673                 }
3674
3675                 return this.unproject(centerPoint.add(offset), zoom);
3676         },
3677
3678         // adjust offset for view to get inside bounds
3679         _limitOffset: function (offset, bounds) {
3680                 if (!bounds) { return offset; }
3681
3682                 var viewBounds = this.getPixelBounds(),
3683                     newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3684
3685                 return offset.add(this._getBoundsOffset(newBounds, bounds));
3686         },
3687
3688         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3689         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3690                 var projectedMaxBounds = L.bounds(
3691                         this.project(maxBounds.getNorthEast(), zoom),
3692                         this.project(maxBounds.getSouthWest(), zoom)
3693                     ),
3694                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3695                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3696
3697                     dx = this._rebound(minOffset.x, -maxOffset.x),
3698                     dy = this._rebound(minOffset.y, -maxOffset.y);
3699
3700                 return new L.Point(dx, dy);
3701         },
3702
3703         _rebound: function (left, right) {
3704                 return left + right > 0 ?
3705                         Math.round(left - right) / 2 :
3706                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3707         },
3708
3709         _limitZoom: function (zoom) {
3710                 var min = this.getMinZoom(),
3711                     max = this.getMaxZoom(),
3712                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3713                 if (snap) {
3714                         zoom = Math.round(zoom / snap) * snap;
3715                 }
3716                 return Math.max(min, Math.min(max, zoom));
3717         },
3718
3719         _onPanTransitionStep: function () {
3720                 this.fire('move');
3721         },
3722
3723         _onPanTransitionEnd: function () {
3724                 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
3725                 this.fire('moveend');
3726         },
3727
3728         _tryAnimatedPan: function (center, options) {
3729                 // difference between the new and current centers in pixels
3730                 var offset = this._getCenterOffset(center)._floor();
3731
3732                 // don't animate too far unless animate: true specified in options
3733                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
3734
3735                 this.panBy(offset, options);
3736
3737                 return true;
3738         },
3739
3740         _createAnimProxy: function () {
3741
3742                 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
3743                 this._panes.mapPane.appendChild(proxy);
3744
3745                 this.on('zoomanim', function (e) {
3746                         var prop = L.DomUtil.TRANSFORM,
3747                             transform = proxy.style[prop];
3748
3749                         L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
3750
3751                         // workaround for case when transform is the same and so transitionend event is not fired
3752                         if (transform === proxy.style[prop] && this._animatingZoom) {
3753                                 this._onZoomTransitionEnd();
3754                         }
3755                 }, this);
3756
3757                 this.on('load moveend', function () {
3758                         var c = this.getCenter(),
3759                             z = this.getZoom();
3760                         L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
3761                 }, this);
3762         },
3763
3764         _catchTransitionEnd: function (e) {
3765                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
3766                         this._onZoomTransitionEnd();
3767                 }
3768         },
3769
3770         _nothingToAnimate: function () {
3771                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
3772         },
3773
3774         _tryAnimatedZoom: function (center, zoom, options) {
3775
3776                 if (this._animatingZoom) { return true; }
3777
3778                 options = options || {};
3779
3780                 // don't animate if disabled, not supported or zoom difference is too large
3781                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
3782                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
3783
3784                 // offset is the pixel coords of the zoom origin relative to the current center
3785                 var scale = this.getZoomScale(zoom),
3786                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
3787
3788                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
3789                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
3790
3791                 L.Util.requestAnimFrame(function () {
3792                         this
3793                             ._moveStart(true)
3794                             ._animateZoom(center, zoom, true);
3795                 }, this);
3796
3797                 return true;
3798         },
3799
3800         _animateZoom: function (center, zoom, startAnim, noUpdate) {
3801                 if (startAnim) {
3802                         this._animatingZoom = true;
3803
3804                         // remember what center/zoom to set after animation
3805                         this._animateToCenter = center;
3806                         this._animateToZoom = zoom;
3807
3808                         L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
3809                 }
3810
3811                 // @event zoomanim: ZoomAnimEvent
3812                 // Fired on every frame of a zoom animation
3813                 this.fire('zoomanim', {
3814                         center: center,
3815                         zoom: zoom,
3816                         noUpdate: noUpdate
3817                 });
3818
3819                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
3820                 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
3821         },
3822
3823         _onZoomTransitionEnd: function () {
3824                 if (!this._animatingZoom) { return; }
3825
3826                 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
3827
3828                 this._animatingZoom = false;
3829
3830                 this._move(this._animateToCenter, this._animateToZoom);
3831
3832                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
3833                 L.Util.requestAnimFrame(function () {
3834                         this._moveEnd(true);
3835                 }, this);
3836         }
3837 });
3838
3839 // @section
3840
3841 // @factory L.map(id: String, options?: Map options)
3842 // Instantiates a map object given the DOM ID of a `<div>` element
3843 // and optionally an object literal with `Map options`.
3844 //
3845 // @alternative
3846 // @factory L.map(el: HTMLElement, options?: Map options)
3847 // Instantiates a map object given an instance of a `<div>` HTML element
3848 // and optionally an object literal with `Map options`.
3849 L.map = function (id, options) {
3850         return new L.Map(id, options);
3851 };
3852
3853
3854
3855
3856 /*
3857  * @class Layer
3858  * @inherits Evented
3859  * @aka L.Layer
3860  * @aka ILayer
3861  *
3862  * A set of methods from the Layer base class that all Leaflet layers use.
3863  * Inherits all methods, options and events from `L.Evented`.
3864  *
3865  * @example
3866  *
3867  * ```js
3868  * var layer = L.Marker(latlng).addTo(map);
3869  * layer.addTo(map);
3870  * layer.remove();
3871  * ```
3872  *
3873  * @event add: Event
3874  * Fired after the layer is added to a map
3875  *
3876  * @event remove: Event
3877  * Fired after the layer is removed from a map
3878  */
3879
3880
3881 L.Layer = L.Evented.extend({
3882
3883         // Classes extending `L.Layer` will inherit the following options:
3884         options: {
3885                 // @option pane: String = 'overlayPane'
3886                 // 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.
3887                 pane: 'overlayPane',
3888                 nonBubblingEvents: [],  // Array of events that should not be bubbled to DOM parents (like the map),
3889
3890                 // @option attribution: String = null
3891                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3892                 attribution: null,
3893         },
3894
3895         /* @section
3896          * Classes extending `L.Layer` will inherit the following methods:
3897          *
3898          * @method addTo(map: Map): this
3899          * Adds the layer to the given map
3900          */
3901         addTo: function (map) {
3902                 map.addLayer(this);
3903                 return this;
3904         },
3905
3906         // @method remove: this
3907         // Removes the layer from the map it is currently active on.
3908         remove: function () {
3909                 return this.removeFrom(this._map || this._mapToAdd);
3910         },
3911
3912         // @method removeFrom(map: Map): this
3913         // Removes the layer from the given map
3914         removeFrom: function (obj) {
3915                 if (obj) {
3916                         obj.removeLayer(this);
3917                 }
3918                 return this;
3919         },
3920
3921         // @method getPane(name? : String): HTMLElement
3922         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3923         getPane: function (name) {
3924                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3925         },
3926
3927         addInteractiveTarget: function (targetEl) {
3928                 this._map._targets[L.stamp(targetEl)] = this;
3929                 return this;
3930         },
3931
3932         removeInteractiveTarget: function (targetEl) {
3933                 delete this._map._targets[L.stamp(targetEl)];
3934                 return this;
3935         },
3936
3937         // @method getAttribution: String
3938         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3939         getAttribution: function () {
3940                 return this.options.attribution;
3941         },
3942
3943         _layerAdd: function (e) {
3944                 var map = e.target;
3945
3946                 // check in case layer gets added and then removed before the map is ready
3947                 if (!map.hasLayer(this)) { return; }
3948
3949                 this._map = map;
3950                 this._zoomAnimated = map._zoomAnimated;
3951
3952                 if (this.getEvents) {
3953                         var events = this.getEvents();
3954                         map.on(events, this);
3955                         this.once('remove', function () {
3956                                 map.off(events, this);
3957                         }, this);
3958                 }
3959
3960                 this.onAdd(map);
3961
3962                 if (this.getAttribution && this._map.attributionControl) {
3963                         this._map.attributionControl.addAttribution(this.getAttribution());
3964                 }
3965
3966                 this.fire('add');
3967                 map.fire('layeradd', {layer: this});
3968         }
3969 });
3970
3971 /* @section Extension methods
3972  * @uninheritable
3973  *
3974  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
3975  *
3976  * @method onAdd(map: Map): this
3977  * 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).
3978  *
3979  * @method onRemove(map: Map): this
3980  * 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).
3981  *
3982  * @method getEvents(): Object
3983  * 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.
3984  *
3985  * @method getAttribution(): String
3986  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
3987  *
3988  * @method beforeAdd(map: Map): this
3989  * 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.
3990  */
3991
3992
3993 /* @namespace Map
3994  * @section Layer events
3995  *
3996  * @event layeradd: LayerEvent
3997  * Fired when a new layer is added to the map.
3998  *
3999  * @event layerremove: LayerEvent
4000  * Fired when some layer is removed from the map
4001  *
4002  * @section Methods for Layers and Controls
4003  */
4004 L.Map.include({
4005         // @method addLayer(layer: Layer): this
4006         // Adds the given layer to the map
4007         addLayer: function (layer) {
4008                 var id = L.stamp(layer);
4009                 if (this._layers[id]) { return this; }
4010                 this._layers[id] = layer;
4011
4012                 layer._mapToAdd = this;
4013
4014                 if (layer.beforeAdd) {
4015                         layer.beforeAdd(this);
4016                 }
4017
4018                 this.whenReady(layer._layerAdd, layer);
4019
4020                 return this;
4021         },
4022
4023         // @method removeLayer(layer: Layer): this
4024         // Removes the given layer from the map.
4025         removeLayer: function (layer) {
4026                 var id = L.stamp(layer);
4027
4028                 if (!this._layers[id]) { return this; }
4029
4030                 if (this._loaded) {
4031                         layer.onRemove(this);
4032                 }
4033
4034                 if (layer.getAttribution && this.attributionControl) {
4035                         this.attributionControl.removeAttribution(layer.getAttribution());
4036                 }
4037
4038                 delete this._layers[id];
4039
4040                 if (this._loaded) {
4041                         this.fire('layerremove', {layer: layer});
4042                         layer.fire('remove');
4043                 }
4044
4045                 layer._map = layer._mapToAdd = null;
4046
4047                 return this;
4048         },
4049
4050         // @method hasLayer(layer: Layer): Boolean
4051         // Returns `true` if the given layer is currently added to the map
4052         hasLayer: function (layer) {
4053                 return !!layer && (L.stamp(layer) in this._layers);
4054         },
4055
4056         /* @method eachLayer(fn: Function, context?: Object): this
4057          * Iterates over the layers of the map, optionally specifying context of the iterator function.
4058          * ```
4059          * map.eachLayer(function(layer){
4060          *     layer.bindPopup('Hello');
4061          * });
4062          * ```
4063          */
4064         eachLayer: function (method, context) {
4065                 for (var i in this._layers) {
4066                         method.call(context, this._layers[i]);
4067                 }
4068                 return this;
4069         },
4070
4071         _addLayers: function (layers) {
4072                 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
4073
4074                 for (var i = 0, len = layers.length; i < len; i++) {
4075                         this.addLayer(layers[i]);
4076                 }
4077         },
4078
4079         _addZoomLimit: function (layer) {
4080                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
4081                         this._zoomBoundLayers[L.stamp(layer)] = layer;
4082                         this._updateZoomLevels();
4083                 }
4084         },
4085
4086         _removeZoomLimit: function (layer) {
4087                 var id = L.stamp(layer);
4088
4089                 if (this._zoomBoundLayers[id]) {
4090                         delete this._zoomBoundLayers[id];
4091                         this._updateZoomLevels();
4092                 }
4093         },
4094
4095         _updateZoomLevels: function () {
4096                 var minZoom = Infinity,
4097                     maxZoom = -Infinity,
4098                     oldZoomSpan = this._getZoomSpan();
4099
4100                 for (var i in this._zoomBoundLayers) {
4101                         var options = this._zoomBoundLayers[i].options;
4102
4103                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
4104                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
4105                 }
4106
4107                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
4108                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
4109
4110                 // @section Map state change events
4111                 // @event zoomlevelschange: Event
4112                 // Fired when the number of zoomlevels on the map is changed due
4113                 // to adding or removing a layer.
4114                 if (oldZoomSpan !== this._getZoomSpan()) {
4115                         this.fire('zoomlevelschange');
4116                 }
4117
4118                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
4119                         this.setZoom(this._layersMaxZoom);
4120                 }
4121                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
4122                         this.setZoom(this._layersMinZoom);
4123                 }
4124         }
4125 });
4126
4127
4128
4129 /*
4130  * @namespace DomEvent
4131  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
4132  */
4133
4134 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
4135
4136
4137
4138 var eventsKey = '_leaflet_events';
4139
4140 L.DomEvent = {
4141
4142         // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
4143         // Adds a listener function (`fn`) to a particular DOM event type of the
4144         // element `el`. You can optionally specify the context of the listener
4145         // (object the `this` keyword will point to). You can also pass several
4146         // space-separated types (e.g. `'click dblclick'`).
4147
4148         // @alternative
4149         // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
4150         // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4151         on: function (obj, types, fn, context) {
4152
4153                 if (typeof types === 'object') {
4154                         for (var type in types) {
4155                                 this._on(obj, type, types[type], fn);
4156                         }
4157                 } else {
4158                         types = L.Util.splitWords(types);
4159
4160                         for (var i = 0, len = types.length; i < len; i++) {
4161                                 this._on(obj, types[i], fn, context);
4162                         }
4163                 }
4164
4165                 return this;
4166         },
4167
4168         // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
4169         // Removes a previously added listener function. If no function is specified,
4170         // it will remove all the listeners of that particular DOM event from the element.
4171         // Note that if you passed a custom context to on, you must pass the same
4172         // context to `off` in order to remove the listener.
4173
4174         // @alternative
4175         // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
4176         // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4177         off: function (obj, types, fn, context) {
4178
4179                 if (typeof types === 'object') {
4180                         for (var type in types) {
4181                                 this._off(obj, type, types[type], fn);
4182                         }
4183                 } else {
4184                         types = L.Util.splitWords(types);
4185
4186                         for (var i = 0, len = types.length; i < len; i++) {
4187                                 this._off(obj, types[i], fn, context);
4188                         }
4189                 }
4190
4191                 return this;
4192         },
4193
4194         _on: function (obj, type, fn, context) {
4195                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
4196
4197                 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
4198
4199                 var handler = function (e) {
4200                         return fn.call(context || obj, e || window.event);
4201                 };
4202
4203                 var originalHandler = handler;
4204
4205                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4206                         this.addPointerListener(obj, type, handler, id);
4207
4208                 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
4209                         this.addDoubleTapListener(obj, handler, id);
4210
4211                 } else if ('addEventListener' in obj) {
4212
4213                         if (type === 'mousewheel') {
4214                                 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4215
4216                         } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
4217                                 handler = function (e) {
4218                                         e = e || window.event;
4219                                         if (L.DomEvent._isExternalTarget(obj, e)) {
4220                                                 originalHandler(e);
4221                                         }
4222                                 };
4223                                 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
4224
4225                         } else {
4226                                 if (type === 'click' && L.Browser.android) {
4227                                         handler = function (e) {
4228                                                 return L.DomEvent._filterClick(e, originalHandler);
4229                                         };
4230                                 }
4231                                 obj.addEventListener(type, handler, false);
4232                         }
4233
4234                 } else if ('attachEvent' in obj) {
4235                         obj.attachEvent('on' + type, handler);
4236                 }
4237
4238                 obj[eventsKey] = obj[eventsKey] || {};
4239                 obj[eventsKey][id] = handler;
4240
4241                 return this;
4242         },
4243
4244         _off: function (obj, type, fn, context) {
4245
4246                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
4247                     handler = obj[eventsKey] && obj[eventsKey][id];
4248
4249                 if (!handler) { return this; }
4250
4251                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4252                         this.removePointerListener(obj, type, id);
4253
4254                 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
4255                         this.removeDoubleTapListener(obj, id);
4256
4257                 } else if ('removeEventListener' in obj) {
4258
4259                         if (type === 'mousewheel') {
4260                                 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4261
4262                         } else {
4263                                 obj.removeEventListener(
4264                                         type === 'mouseenter' ? 'mouseover' :
4265                                         type === 'mouseleave' ? 'mouseout' : type, handler, false);
4266                         }
4267
4268                 } else if ('detachEvent' in obj) {
4269                         obj.detachEvent('on' + type, handler);
4270                 }
4271
4272                 obj[eventsKey][id] = null;
4273
4274                 return this;
4275         },
4276
4277         // @function stopPropagation(ev: DOMEvent): this
4278         // Stop the given event from propagation to parent elements. Used inside the listener functions:
4279         // ```js
4280         // L.DomEvent.on(div, 'click', function (ev) {
4281         //      L.DomEvent.stopPropagation(ev);
4282         // });
4283         // ```
4284         stopPropagation: function (e) {
4285
4286                 if (e.stopPropagation) {
4287                         e.stopPropagation();
4288                 } else if (e.originalEvent) {  // In case of Leaflet event.
4289                         e.originalEvent._stopped = true;
4290                 } else {
4291                         e.cancelBubble = true;
4292                 }
4293                 L.DomEvent._skipped(e);
4294
4295                 return this;
4296         },
4297
4298         // @function disableScrollPropagation(el: HTMLElement): this
4299         // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
4300         disableScrollPropagation: function (el) {
4301                 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
4302         },
4303
4304         // @function disableClickPropagation(el: HTMLElement): this
4305         // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
4306         // `'mousedown'` and `'touchstart'` events (plus browser variants).
4307         disableClickPropagation: function (el) {
4308                 var stop = L.DomEvent.stopPropagation;
4309
4310                 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
4311
4312                 return L.DomEvent.on(el, {
4313                         click: L.DomEvent._fakeStop,
4314                         dblclick: stop
4315                 });
4316         },
4317
4318         // @function preventDefault(ev: DOMEvent): this
4319         // Prevents the default action of the DOM Event `ev` from happening (such as
4320         // following a link in the href of the a element, or doing a POST request
4321         // with page reload when a `<form>` is submitted).
4322         // Use it inside listener functions.
4323         preventDefault: function (e) {
4324
4325                 if (e.preventDefault) {
4326                         e.preventDefault();
4327                 } else {
4328                         e.returnValue = false;
4329                 }
4330                 return this;
4331         },
4332
4333         // @function stop(ev): this
4334         // Does `stopPropagation` and `preventDefault` at the same time.
4335         stop: function (e) {
4336                 return L.DomEvent
4337                         .preventDefault(e)
4338                         .stopPropagation(e);
4339         },
4340
4341         // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
4342         // Gets normalized mouse position from a DOM event relative to the
4343         // `container` or to the whole page if not specified.
4344         getMousePosition: function (e, container) {
4345                 if (!container) {
4346                         return new L.Point(e.clientX, e.clientY);
4347                 }
4348
4349                 var rect = container.getBoundingClientRect();
4350
4351                 return new L.Point(
4352                         e.clientX - rect.left - container.clientLeft,
4353                         e.clientY - rect.top - container.clientTop);
4354         },
4355
4356         // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
4357         // and Firefox scrolls device pixels, not CSS pixels
4358         _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
4359                         L.Browser.gecko ? window.devicePixelRatio :
4360                         1,
4361
4362         // @function getWheelDelta(ev: DOMEvent): Number
4363         // Gets normalized wheel delta from a mousewheel DOM event, in vertical
4364         // pixels scrolled (negative if scrolling down).
4365         // Events from pointing devices without precise scrolling are mapped to
4366         // a best guess of 60 pixels.
4367         getWheelDelta: function (e) {
4368                 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
4369                        (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
4370                        (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
4371                        (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
4372                        (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
4373                        e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
4374                        (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
4375                        e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
4376                        0;
4377         },
4378
4379         _skipEvents: {},
4380
4381         _fakeStop: function (e) {
4382                 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
4383                 L.DomEvent._skipEvents[e.type] = true;
4384         },
4385
4386         _skipped: function (e) {
4387                 var skipped = this._skipEvents[e.type];
4388                 // reset when checking, as it's only used in map container and propagates outside of the map
4389                 this._skipEvents[e.type] = false;
4390                 return skipped;
4391         },
4392
4393         // check if element really left/entered the event target (for mouseenter/mouseleave)
4394         _isExternalTarget: function (el, e) {
4395
4396                 var related = e.relatedTarget;
4397
4398                 if (!related) { return true; }
4399
4400                 try {
4401                         while (related && (related !== el)) {
4402                                 related = related.parentNode;
4403                         }
4404                 } catch (err) {
4405                         return false;
4406                 }
4407                 return (related !== el);
4408         },
4409
4410         // this is a horrible workaround for a bug in Android where a single touch triggers two click events
4411         _filterClick: function (e, handler) {
4412                 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
4413                     elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
4414
4415                 // are they closer together than 500ms yet more than 100ms?
4416                 // Android typically triggers them ~300ms apart while multiple listeners
4417                 // on the same event should be triggered far faster;
4418                 // or check if click is simulated on the element, and if it is, reject any non-simulated events
4419
4420                 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
4421                         L.DomEvent.stop(e);
4422                         return;
4423                 }
4424                 L.DomEvent._lastClick = timeStamp;
4425
4426                 handler(e);
4427         }
4428 };
4429
4430 // @function addListener(…): this
4431 // Alias to [`L.DomEvent.on`](#domevent-on)
4432 L.DomEvent.addListener = L.DomEvent.on;
4433
4434 // @function removeListener(…): this
4435 // Alias to [`L.DomEvent.off`](#domevent-off)
4436 L.DomEvent.removeListener = L.DomEvent.off;
4437
4438
4439
4440 /*
4441  * @class PosAnimation
4442  * @aka L.PosAnimation
4443  * @inherits Evented
4444  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
4445  *
4446  * @example
4447  * ```js
4448  * var fx = new L.PosAnimation();
4449  * fx.run(el, [300, 500], 0.5);
4450  * ```
4451  *
4452  * @constructor L.PosAnimation()
4453  * Creates a `PosAnimation` object.
4454  *
4455  */
4456
4457 L.PosAnimation = L.Evented.extend({
4458
4459         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
4460         // Run an animation of a given element to a new position, optionally setting
4461         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
4462         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
4463         // `0.5` by default).
4464         run: function (el, newPos, duration, easeLinearity) {
4465                 this.stop();
4466
4467                 this._el = el;
4468                 this._inProgress = true;
4469                 this._duration = duration || 0.25;
4470                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
4471
4472                 this._startPos = L.DomUtil.getPosition(el);
4473                 this._offset = newPos.subtract(this._startPos);
4474                 this._startTime = +new Date();
4475
4476                 // @event start: Event
4477                 // Fired when the animation starts
4478                 this.fire('start');
4479
4480                 this._animate();
4481         },
4482
4483         // @method stop()
4484         // Stops the animation (if currently running).
4485         stop: function () {
4486                 if (!this._inProgress) { return; }
4487
4488                 this._step(true);
4489                 this._complete();
4490         },
4491
4492         _animate: function () {
4493                 // animation loop
4494                 this._animId = L.Util.requestAnimFrame(this._animate, this);
4495                 this._step();
4496         },
4497
4498         _step: function (round) {
4499                 var elapsed = (+new Date()) - this._startTime,
4500                     duration = this._duration * 1000;
4501
4502                 if (elapsed < duration) {
4503                         this._runFrame(this._easeOut(elapsed / duration), round);
4504                 } else {
4505                         this._runFrame(1);
4506                         this._complete();
4507                 }
4508         },
4509
4510         _runFrame: function (progress, round) {
4511                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
4512                 if (round) {
4513                         pos._round();
4514                 }
4515                 L.DomUtil.setPosition(this._el, pos);
4516
4517                 // @event step: Event
4518                 // Fired continuously during the animation.
4519                 this.fire('step');
4520         },
4521
4522         _complete: function () {
4523                 L.Util.cancelAnimFrame(this._animId);
4524
4525                 this._inProgress = false;
4526                 // @event end: Event
4527                 // Fired when the animation ends.
4528                 this.fire('end');
4529         },
4530
4531         _easeOut: function (t) {
4532                 return 1 - Math.pow(1 - t, this._easeOutPower);
4533         }
4534 });
4535
4536
4537
4538 /*
4539  * @namespace Projection
4540  * @projection L.Projection.Mercator
4541  *
4542  * 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.
4543  */
4544
4545 L.Projection.Mercator = {
4546         R: 6378137,
4547         R_MINOR: 6356752.314245179,
4548
4549         bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
4550
4551         project: function (latlng) {
4552                 var d = Math.PI / 180,
4553                     r = this.R,
4554                     y = latlng.lat * d,
4555                     tmp = this.R_MINOR / r,
4556                     e = Math.sqrt(1 - tmp * tmp),
4557                     con = e * Math.sin(y);
4558
4559                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
4560                 y = -r * Math.log(Math.max(ts, 1E-10));
4561
4562                 return new L.Point(latlng.lng * d * r, y);
4563         },
4564
4565         unproject: function (point) {
4566                 var d = 180 / Math.PI,
4567                     r = this.R,
4568                     tmp = this.R_MINOR / r,
4569                     e = Math.sqrt(1 - tmp * tmp),
4570                     ts = Math.exp(-point.y / r),
4571                     phi = Math.PI / 2 - 2 * Math.atan(ts);
4572
4573                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
4574                         con = e * Math.sin(phi);
4575                         con = Math.pow((1 - con) / (1 + con), e / 2);
4576                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
4577                         phi += dphi;
4578                 }
4579
4580                 return new L.LatLng(phi * d, point.x * d / r);
4581         }
4582 };
4583
4584
4585
4586 /*
4587  * @namespace CRS
4588  * @crs L.CRS.EPSG3395
4589  *
4590  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
4591  */
4592
4593 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
4594         code: 'EPSG:3395',
4595         projection: L.Projection.Mercator,
4596
4597         transformation: (function () {
4598                 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
4599                 return new L.Transformation(scale, 0.5, -scale, 0.5);
4600         }())
4601 });
4602
4603
4604
4605 /*
4606  * @class GridLayer
4607  * @inherits Layer
4608  * @aka L.GridLayer
4609  *
4610  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
4611  * 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.
4612  *
4613  *
4614  * @section Synchronous usage
4615  * @example
4616  *
4617  * 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.
4618  *
4619  * ```js
4620  * var CanvasLayer = L.GridLayer.extend({
4621  *     createTile: function(coords){
4622  *         // create a <canvas> element for drawing
4623  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4624  *
4625  *         // setup tile width and height according to the options
4626  *         var size = this.getTileSize();
4627  *         tile.width = size.x;
4628  *         tile.height = size.y;
4629  *
4630  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
4631  *         var ctx = tile.getContext('2d');
4632  *
4633  *         // return the tile so it can be rendered on screen
4634  *         return tile;
4635  *     }
4636  * });
4637  * ```
4638  *
4639  * @section Asynchronous usage
4640  * @example
4641  *
4642  * 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.
4643  *
4644  * ```js
4645  * var CanvasLayer = L.GridLayer.extend({
4646  *     createTile: function(coords, done){
4647  *         var error;
4648  *
4649  *         // create a <canvas> element for drawing
4650  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4651  *
4652  *         // setup tile width and height according to the options
4653  *         var size = this.getTileSize();
4654  *         tile.width = size.x;
4655  *         tile.height = size.y;
4656  *
4657  *         // draw something asynchronously and pass the tile to the done() callback
4658  *         setTimeout(function() {
4659  *             done(error, tile);
4660  *         }, 1000);
4661  *
4662  *         return tile;
4663  *     }
4664  * });
4665  * ```
4666  *
4667  * @section
4668  */
4669
4670
4671 L.GridLayer = L.Layer.extend({
4672
4673         // @section
4674         // @aka GridLayer options
4675         options: {
4676                 // @option tileSize: Number|Point = 256
4677                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
4678                 tileSize: 256,
4679
4680                 // @option opacity: Number = 1.0
4681                 // Opacity of the tiles. Can be used in the `createTile()` function.
4682                 opacity: 1,
4683
4684                 // @option updateWhenIdle: Boolean = depends
4685                 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
4686                 updateWhenIdle: L.Browser.mobile,
4687
4688                 // @option updateWhenZooming: Boolean = true
4689                 // 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.
4690                 updateWhenZooming: true,
4691
4692                 // @option updateInterval: Number = 200
4693                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
4694                 updateInterval: 200,
4695
4696                 // @option zIndex: Number = 1
4697                 // The explicit zIndex of the tile layer.
4698                 zIndex: 1,
4699
4700                 // @option bounds: LatLngBounds = undefined
4701                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
4702                 bounds: null,
4703
4704                 // @option minZoom: Number = 0
4705                 // The minimum zoom level that tiles will be loaded at. By default the entire map.
4706                 minZoom: 0,
4707
4708                 // @option maxZoom: Number = undefined
4709                 // The maximum zoom level that tiles will be loaded at.
4710                 maxZoom: undefined,
4711
4712                 // @option noWrap: Boolean = false
4713                 // Whether the layer is wrapped around the antimeridian. If `true`, the
4714                 // GridLayer will only be displayed once at low zoom levels. Has no
4715                 // effect when the [map CRS](#map-crs) doesn't wrap around.
4716                 noWrap: false,
4717
4718                 // @option pane: String = 'tilePane'
4719                 // `Map pane` where the grid layer will be added.
4720                 pane: 'tilePane',
4721
4722                 // @option className: String = ''
4723                 // A custom class name to assign to the tile layer. Empty by default.
4724                 className: '',
4725
4726                 // @option keepBuffer: Number = 2
4727                 // When panning the map, keep this many rows and columns of tiles before unloading them.
4728                 keepBuffer: 2
4729         },
4730
4731         initialize: function (options) {
4732                 L.setOptions(this, options);
4733         },
4734
4735         onAdd: function () {
4736                 this._initContainer();
4737
4738                 this._levels = {};
4739                 this._tiles = {};
4740
4741                 this._resetView();
4742                 this._update();
4743         },
4744
4745         beforeAdd: function (map) {
4746                 map._addZoomLimit(this);
4747         },
4748
4749         onRemove: function (map) {
4750                 this._removeAllTiles();
4751                 L.DomUtil.remove(this._container);
4752                 map._removeZoomLimit(this);
4753                 this._container = null;
4754                 this._tileZoom = null;
4755         },
4756
4757         // @method bringToFront: this
4758         // Brings the tile layer to the top of all tile layers.
4759         bringToFront: function () {
4760                 if (this._map) {
4761                         L.DomUtil.toFront(this._container);
4762                         this._setAutoZIndex(Math.max);
4763                 }
4764                 return this;
4765         },
4766
4767         // @method bringToBack: this
4768         // Brings the tile layer to the bottom of all tile layers.
4769         bringToBack: function () {
4770                 if (this._map) {
4771                         L.DomUtil.toBack(this._container);
4772                         this._setAutoZIndex(Math.min);
4773                 }
4774                 return this;
4775         },
4776
4777         // @method getContainer: HTMLElement
4778         // Returns the HTML element that contains the tiles for this layer.
4779         getContainer: function () {
4780                 return this._container;
4781         },
4782
4783         // @method setOpacity(opacity: Number): this
4784         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
4785         setOpacity: function (opacity) {
4786                 this.options.opacity = opacity;
4787                 this._updateOpacity();
4788                 return this;
4789         },
4790
4791         // @method setZIndex(zIndex: Number): this
4792         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
4793         setZIndex: function (zIndex) {
4794                 this.options.zIndex = zIndex;
4795                 this._updateZIndex();
4796
4797                 return this;
4798         },
4799
4800         // @method isLoading: Boolean
4801         // Returns `true` if any tile in the grid layer has not finished loading.
4802         isLoading: function () {
4803                 return this._loading;
4804         },
4805
4806         // @method redraw: this
4807         // Causes the layer to clear all the tiles and request them again.
4808         redraw: function () {
4809                 if (this._map) {
4810                         this._removeAllTiles();
4811                         this._update();
4812                 }
4813                 return this;
4814         },
4815
4816         getEvents: function () {
4817                 var events = {
4818                         viewprereset: this._invalidateAll,
4819                         viewreset: this._resetView,
4820                         zoom: this._resetView,
4821                         moveend: this._onMoveEnd
4822                 };
4823
4824                 if (!this.options.updateWhenIdle) {
4825                         // update tiles on move, but not more often than once per given interval
4826                         if (!this._onMove) {
4827                                 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4828                         }
4829
4830                         events.move = this._onMove;
4831                 }
4832
4833                 if (this._zoomAnimated) {
4834                         events.zoomanim = this._animateZoom;
4835                 }
4836
4837                 return events;
4838         },
4839
4840         // @section Extension methods
4841         // Layers extending `GridLayer` shall reimplement the following method.
4842         // @method createTile(coords: Object, done?: Function): HTMLElement
4843         // Called only internally, must be overriden by classes extending `GridLayer`.
4844         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4845         // is specified, it must be called when the tile has finished loading and drawing.
4846         createTile: function () {
4847                 return document.createElement('div');
4848         },
4849
4850         // @section
4851         // @method getTileSize: Point
4852         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4853         getTileSize: function () {
4854                 var s = this.options.tileSize;
4855                 return s instanceof L.Point ? s : new L.Point(s, s);
4856         },
4857
4858         _updateZIndex: function () {
4859                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4860                         this._container.style.zIndex = this.options.zIndex;
4861                 }
4862         },
4863
4864         _setAutoZIndex: function (compare) {
4865                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4866
4867                 var layers = this.getPane().children,
4868                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4869
4870                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4871
4872                         zIndex = layers[i].style.zIndex;
4873
4874                         if (layers[i] !== this._container && zIndex) {
4875                                 edgeZIndex = compare(edgeZIndex, +zIndex);
4876                         }
4877                 }
4878
4879                 if (isFinite(edgeZIndex)) {
4880                         this.options.zIndex = edgeZIndex + compare(-1, 1);
4881                         this._updateZIndex();
4882                 }
4883         },
4884
4885         _updateOpacity: function () {
4886                 if (!this._map) { return; }
4887
4888                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4889                 if (L.Browser.ielt9) { return; }
4890
4891                 L.DomUtil.setOpacity(this._container, this.options.opacity);
4892
4893                 var now = +new Date(),
4894                     nextFrame = false,
4895                     willPrune = false;
4896
4897                 for (var key in this._tiles) {
4898                         var tile = this._tiles[key];
4899                         if (!tile.current || !tile.loaded) { continue; }
4900
4901                         var fade = Math.min(1, (now - tile.loaded) / 200);
4902
4903                         L.DomUtil.setOpacity(tile.el, fade);
4904                         if (fade < 1) {
4905                                 nextFrame = true;
4906                         } else {
4907                                 if (tile.active) { willPrune = true; }
4908                                 tile.active = true;
4909                         }
4910                 }
4911
4912                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4913
4914                 if (nextFrame) {
4915                         L.Util.cancelAnimFrame(this._fadeFrame);
4916                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4917                 }
4918         },
4919
4920         _initContainer: function () {
4921                 if (this._container) { return; }
4922
4923                 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4924                 this._updateZIndex();
4925
4926                 if (this.options.opacity < 1) {
4927                         this._updateOpacity();
4928                 }
4929
4930                 this.getPane().appendChild(this._container);
4931         },
4932
4933         _updateLevels: function () {
4934
4935                 var zoom = this._tileZoom,
4936                     maxZoom = this.options.maxZoom;
4937
4938                 if (zoom === undefined) { return undefined; }
4939
4940                 for (var z in this._levels) {
4941                         if (this._levels[z].el.children.length || z === zoom) {
4942                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4943                         } else {
4944                                 L.DomUtil.remove(this._levels[z].el);
4945                                 this._removeTilesAtZoom(z);
4946                                 delete this._levels[z];
4947                         }
4948                 }
4949
4950                 var level = this._levels[zoom],
4951                     map = this._map;
4952
4953                 if (!level) {
4954                         level = this._levels[zoom] = {};
4955
4956                         level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4957                         level.el.style.zIndex = maxZoom;
4958
4959                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
4960                         level.zoom = zoom;
4961
4962                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
4963
4964                         // force the browser to consider the newly added element for transition
4965                         L.Util.falseFn(level.el.offsetWidth);
4966                 }
4967
4968                 this._level = level;
4969
4970                 return level;
4971         },
4972
4973         _pruneTiles: function () {
4974                 if (!this._map) {
4975                         return;
4976                 }
4977
4978                 var key, tile;
4979
4980                 var zoom = this._map.getZoom();
4981                 if (zoom > this.options.maxZoom ||
4982                         zoom < this.options.minZoom) {
4983                         this._removeAllTiles();
4984                         return;
4985                 }
4986
4987                 for (key in this._tiles) {
4988                         tile = this._tiles[key];
4989                         tile.retain = tile.current;
4990                 }
4991
4992                 for (key in this._tiles) {
4993                         tile = this._tiles[key];
4994                         if (tile.current && !tile.active) {
4995                                 var coords = tile.coords;
4996                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
4997                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
4998                                 }
4999                         }
5000                 }
5001
5002                 for (key in this._tiles) {
5003                         if (!this._tiles[key].retain) {
5004                                 this._removeTile(key);
5005                         }
5006                 }
5007         },
5008
5009         _removeTilesAtZoom: function (zoom) {
5010                 for (var key in this._tiles) {
5011                         if (this._tiles[key].coords.z !== zoom) {
5012                                 continue;
5013                         }
5014                         this._removeTile(key);
5015                 }
5016         },
5017
5018         _removeAllTiles: function () {
5019                 for (var key in this._tiles) {
5020                         this._removeTile(key);
5021                 }
5022         },
5023
5024         _invalidateAll: function () {
5025                 for (var z in this._levels) {
5026                         L.DomUtil.remove(this._levels[z].el);
5027                         delete this._levels[z];
5028                 }
5029                 this._removeAllTiles();
5030
5031                 this._tileZoom = null;
5032         },
5033
5034         _retainParent: function (x, y, z, minZoom) {
5035                 var x2 = Math.floor(x / 2),
5036                     y2 = Math.floor(y / 2),
5037                     z2 = z - 1,
5038                     coords2 = new L.Point(+x2, +y2);
5039                 coords2.z = +z2;
5040
5041                 var key = this._tileCoordsToKey(coords2),
5042                     tile = this._tiles[key];
5043
5044                 if (tile && tile.active) {
5045                         tile.retain = true;
5046                         return true;
5047
5048                 } else if (tile && tile.loaded) {
5049                         tile.retain = true;
5050                 }
5051
5052                 if (z2 > minZoom) {
5053                         return this._retainParent(x2, y2, z2, minZoom);
5054                 }
5055
5056                 return false;
5057         },
5058
5059         _retainChildren: function (x, y, z, maxZoom) {
5060
5061                 for (var i = 2 * x; i < 2 * x + 2; i++) {
5062                         for (var j = 2 * y; j < 2 * y + 2; j++) {
5063
5064                                 var coords = new L.Point(i, j);
5065                                 coords.z = z + 1;
5066
5067                                 var key = this._tileCoordsToKey(coords),
5068                                     tile = this._tiles[key];
5069
5070                                 if (tile && tile.active) {
5071                                         tile.retain = true;
5072                                         continue;
5073
5074                                 } else if (tile && tile.loaded) {
5075                                         tile.retain = true;
5076                                 }
5077
5078                                 if (z + 1 < maxZoom) {
5079                                         this._retainChildren(i, j, z + 1, maxZoom);
5080                                 }
5081                         }
5082                 }
5083         },
5084
5085         _resetView: function (e) {
5086                 var animating = e && (e.pinch || e.flyTo);
5087                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
5088         },
5089
5090         _animateZoom: function (e) {
5091                 this._setView(e.center, e.zoom, true, e.noUpdate);
5092         },
5093
5094         _setView: function (center, zoom, noPrune, noUpdate) {
5095                 var tileZoom = Math.round(zoom);
5096                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
5097                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
5098                         tileZoom = undefined;
5099                 }
5100
5101                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
5102
5103                 if (!noUpdate || tileZoomChanged) {
5104
5105                         this._tileZoom = tileZoom;
5106
5107                         if (this._abortLoading) {
5108                                 this._abortLoading();
5109                         }
5110
5111                         this._updateLevels();
5112                         this._resetGrid();
5113
5114                         if (tileZoom !== undefined) {
5115                                 this._update(center);
5116                         }
5117
5118                         if (!noPrune) {
5119                                 this._pruneTiles();
5120                         }
5121
5122                         // Flag to prevent _updateOpacity from pruning tiles during
5123                         // a zoom anim or a pinch gesture
5124                         this._noPrune = !!noPrune;
5125                 }
5126
5127                 this._setZoomTransforms(center, zoom);
5128         },
5129
5130         _setZoomTransforms: function (center, zoom) {
5131                 for (var i in this._levels) {
5132                         this._setZoomTransform(this._levels[i], center, zoom);
5133                 }
5134         },
5135
5136         _setZoomTransform: function (level, center, zoom) {
5137                 var scale = this._map.getZoomScale(zoom, level.zoom),
5138                     translate = level.origin.multiplyBy(scale)
5139                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
5140
5141                 if (L.Browser.any3d) {
5142                         L.DomUtil.setTransform(level.el, translate, scale);
5143                 } else {
5144                         L.DomUtil.setPosition(level.el, translate);
5145                 }
5146         },
5147
5148         _resetGrid: function () {
5149                 var map = this._map,
5150                     crs = map.options.crs,
5151                     tileSize = this._tileSize = this.getTileSize(),
5152                     tileZoom = this._tileZoom;
5153
5154                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
5155                 if (bounds) {
5156                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
5157                 }
5158
5159                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
5160                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
5161                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
5162                 ];
5163                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
5164                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
5165                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
5166                 ];
5167         },
5168
5169         _onMoveEnd: function () {
5170                 if (!this._map || this._map._animatingZoom) { return; }
5171
5172                 this._update();
5173         },
5174
5175         _getTiledPixelBounds: function (center) {
5176                 var map = this._map,
5177                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
5178                     scale = map.getZoomScale(mapZoom, this._tileZoom),
5179                     pixelCenter = map.project(center, this._tileZoom).floor(),
5180                     halfSize = map.getSize().divideBy(scale * 2);
5181
5182                 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
5183         },
5184
5185         // Private method to load tiles in the grid's active zoom level according to map bounds
5186         _update: function (center) {
5187                 var map = this._map;
5188                 if (!map) { return; }
5189                 var zoom = map.getZoom();
5190
5191                 if (center === undefined) { center = map.getCenter(); }
5192                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
5193
5194                 var pixelBounds = this._getTiledPixelBounds(center),
5195                     tileRange = this._pxBoundsToTileRange(pixelBounds),
5196                     tileCenter = tileRange.getCenter(),
5197                     queue = [],
5198                     margin = this.options.keepBuffer,
5199                     noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
5200                                               tileRange.getTopRight().add([margin, -margin]));
5201
5202                 for (var key in this._tiles) {
5203                         var c = this._tiles[key].coords;
5204                         if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
5205                                 this._tiles[key].current = false;
5206                         }
5207                 }
5208
5209                 // _update just loads more tiles. If the tile zoom level differs too much
5210                 // from the map's, let _setView reset levels and prune old tiles.
5211                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
5212
5213                 // create a queue of coordinates to load tiles from
5214                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
5215                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
5216                                 var coords = new L.Point(i, j);
5217                                 coords.z = this._tileZoom;
5218
5219                                 if (!this._isValidTile(coords)) { continue; }
5220
5221                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
5222                                 if (tile) {
5223                                         tile.current = true;
5224                                 } else {
5225                                         queue.push(coords);
5226                                 }
5227                         }
5228                 }
5229
5230                 // sort tile queue to load tiles in order of their distance to center
5231                 queue.sort(function (a, b) {
5232                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
5233                 });
5234
5235                 if (queue.length !== 0) {
5236                         // if it's the first batch of tiles to load
5237                         if (!this._loading) {
5238                                 this._loading = true;
5239                                 // @event loading: Event
5240                                 // Fired when the grid layer starts loading tiles.
5241                                 this.fire('loading');
5242                         }
5243
5244                         // create DOM fragment to append tiles in one batch
5245                         var fragment = document.createDocumentFragment();
5246
5247                         for (i = 0; i < queue.length; i++) {
5248                                 this._addTile(queue[i], fragment);
5249                         }
5250
5251                         this._level.el.appendChild(fragment);
5252                 }
5253         },
5254
5255         _isValidTile: function (coords) {
5256                 var crs = this._map.options.crs;
5257
5258                 if (!crs.infinite) {
5259                         // don't load tile if it's out of bounds and not wrapped
5260                         var bounds = this._globalTileRange;
5261                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
5262                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
5263                 }
5264
5265                 if (!this.options.bounds) { return true; }
5266
5267                 // don't load tile if it doesn't intersect the bounds in options
5268                 var tileBounds = this._tileCoordsToBounds(coords);
5269                 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
5270         },
5271
5272         _keyToBounds: function (key) {
5273                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
5274         },
5275
5276         // converts tile coordinates to its geographical bounds
5277         _tileCoordsToBounds: function (coords) {
5278
5279                 var map = this._map,
5280                     tileSize = this.getTileSize(),
5281
5282                     nwPoint = coords.scaleBy(tileSize),
5283                     sePoint = nwPoint.add(tileSize),
5284
5285                     nw = map.unproject(nwPoint, coords.z),
5286                     se = map.unproject(sePoint, coords.z);
5287
5288                 if (!this.options.noWrap) {
5289                         nw = map.wrapLatLng(nw);
5290                         se = map.wrapLatLng(se);
5291                 }
5292
5293                 return new L.LatLngBounds(nw, se);
5294         },
5295
5296         // converts tile coordinates to key for the tile cache
5297         _tileCoordsToKey: function (coords) {
5298                 return coords.x + ':' + coords.y + ':' + coords.z;
5299         },
5300
5301         // converts tile cache key to coordinates
5302         _keyToTileCoords: function (key) {
5303                 var k = key.split(':'),
5304                     coords = new L.Point(+k[0], +k[1]);
5305                 coords.z = +k[2];
5306                 return coords;
5307         },
5308
5309         _removeTile: function (key) {
5310                 var tile = this._tiles[key];
5311                 if (!tile) { return; }
5312
5313                 L.DomUtil.remove(tile.el);
5314
5315                 delete this._tiles[key];
5316
5317                 // @event tileunload: TileEvent
5318                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
5319                 this.fire('tileunload', {
5320                         tile: tile.el,
5321                         coords: this._keyToTileCoords(key)
5322                 });
5323         },
5324
5325         _initTile: function (tile) {
5326                 L.DomUtil.addClass(tile, 'leaflet-tile');
5327
5328                 var tileSize = this.getTileSize();
5329                 tile.style.width = tileSize.x + 'px';
5330                 tile.style.height = tileSize.y + 'px';
5331
5332                 tile.onselectstart = L.Util.falseFn;
5333                 tile.onmousemove = L.Util.falseFn;
5334
5335                 // update opacity on tiles in IE7-8 because of filter inheritance problems
5336                 if (L.Browser.ielt9 && this.options.opacity < 1) {
5337                         L.DomUtil.setOpacity(tile, this.options.opacity);
5338                 }
5339
5340                 // without this hack, tiles disappear after zoom on Chrome for Android
5341                 // https://github.com/Leaflet/Leaflet/issues/2078
5342                 if (L.Browser.android && !L.Browser.android23) {
5343                         tile.style.WebkitBackfaceVisibility = 'hidden';
5344                 }
5345         },
5346
5347         _addTile: function (coords, container) {
5348                 var tilePos = this._getTilePos(coords),
5349                     key = this._tileCoordsToKey(coords);
5350
5351                 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
5352
5353                 this._initTile(tile);
5354
5355                 // if createTile is defined with a second argument ("done" callback),
5356                 // we know that tile is async and will be ready later; otherwise
5357                 if (this.createTile.length < 2) {
5358                         // mark tile as ready, but delay one frame for opacity animation to happen
5359                         L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
5360                 }
5361
5362                 L.DomUtil.setPosition(tile, tilePos);
5363
5364                 // save tile in cache
5365                 this._tiles[key] = {
5366                         el: tile,
5367                         coords: coords,
5368                         current: true
5369                 };
5370
5371                 container.appendChild(tile);
5372                 // @event tileloadstart: TileEvent
5373                 // Fired when a tile is requested and starts loading.
5374                 this.fire('tileloadstart', {
5375                         tile: tile,
5376                         coords: coords
5377                 });
5378         },
5379
5380         _tileReady: function (coords, err, tile) {
5381                 if (!this._map) { return; }
5382
5383                 if (err) {
5384                         // @event tileerror: TileErrorEvent
5385                         // Fired when there is an error loading a tile.
5386                         this.fire('tileerror', {
5387                                 error: err,
5388                                 tile: tile,
5389                                 coords: coords
5390                         });
5391                 }
5392
5393                 var key = this._tileCoordsToKey(coords);
5394
5395                 tile = this._tiles[key];
5396                 if (!tile) { return; }
5397
5398                 tile.loaded = +new Date();
5399                 if (this._map._fadeAnimated) {
5400                         L.DomUtil.setOpacity(tile.el, 0);
5401                         L.Util.cancelAnimFrame(this._fadeFrame);
5402                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
5403                 } else {
5404                         tile.active = true;
5405                         this._pruneTiles();
5406                 }
5407
5408                 if (!err) {
5409                         L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
5410
5411                         // @event tileload: TileEvent
5412                         // Fired when a tile loads.
5413                         this.fire('tileload', {
5414                                 tile: tile.el,
5415                                 coords: coords
5416                         });
5417                 }
5418
5419                 if (this._noTilesToLoad()) {
5420                         this._loading = false;
5421                         // @event load: Event
5422                         // Fired when the grid layer loaded all visible tiles.
5423                         this.fire('load');
5424
5425                         if (L.Browser.ielt9 || !this._map._fadeAnimated) {
5426                                 L.Util.requestAnimFrame(this._pruneTiles, this);
5427                         } else {
5428                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
5429                                 // to trigger a pruning.
5430                                 setTimeout(L.bind(this._pruneTiles, this), 250);
5431                         }
5432                 }
5433         },
5434
5435         _getTilePos: function (coords) {
5436                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
5437         },
5438
5439         _wrapCoords: function (coords) {
5440                 var newCoords = new L.Point(
5441                         this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
5442                         this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
5443                 newCoords.z = coords.z;
5444                 return newCoords;
5445         },
5446
5447         _pxBoundsToTileRange: function (bounds) {
5448                 var tileSize = this.getTileSize();
5449                 return new L.Bounds(
5450                         bounds.min.unscaleBy(tileSize).floor(),
5451                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
5452         },
5453
5454         _noTilesToLoad: function () {
5455                 for (var key in this._tiles) {
5456                         if (!this._tiles[key].loaded) { return false; }
5457                 }
5458                 return true;
5459         }
5460 });
5461
5462 // @factory L.gridLayer(options?: GridLayer options)
5463 // Creates a new instance of GridLayer with the supplied options.
5464 L.gridLayer = function (options) {
5465         return new L.GridLayer(options);
5466 };
5467
5468
5469
5470 /*
5471  * @class TileLayer
5472  * @inherits GridLayer
5473  * @aka L.TileLayer
5474  * Used to load and display tile layers on the map. Extends `GridLayer`.
5475  *
5476  * @example
5477  *
5478  * ```js
5479  * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
5480  * ```
5481  *
5482  * @section URL template
5483  * @example
5484  *
5485  * A string of the following form:
5486  *
5487  * ```
5488  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
5489  * ```
5490  *
5491  * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add @2x to the URL to load retina tiles.
5492  *
5493  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
5494  *
5495  * ```
5496  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
5497  * ```
5498  */
5499
5500
5501 L.TileLayer = L.GridLayer.extend({
5502
5503         // @section
5504         // @aka TileLayer options
5505         options: {
5506                 // @option minZoom: Number = 0
5507                 // Minimum zoom number.
5508                 minZoom: 0,
5509
5510                 // @option maxZoom: Number = 18
5511                 // Maximum zoom number.
5512                 maxZoom: 18,
5513
5514                 // @option maxNativeZoom: Number = null
5515                 // Maximum zoom number the tile source has available. If it is specified,
5516                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
5517                 // from `maxNativeZoom` level and auto-scaled.
5518                 maxNativeZoom: null,
5519
5520                 // @option minNativeZoom: Number = null
5521                 // Minimum zoom number the tile source has available. If it is specified,
5522                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
5523                 // from `minNativeZoom` level and auto-scaled.
5524                 minNativeZoom: null,
5525
5526                 // @option subdomains: String|String[] = 'abc'
5527                 // 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.
5528                 subdomains: 'abc',
5529
5530                 // @option errorTileUrl: String = ''
5531                 // URL to the tile image to show in place of the tile that failed to load.
5532                 errorTileUrl: '',
5533
5534                 // @option zoomOffset: Number = 0
5535                 // The zoom number used in tile URLs will be offset with this value.
5536                 zoomOffset: 0,
5537
5538                 // @option tms: Boolean = false
5539                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
5540                 tms: false,
5541
5542                 // @option zoomReverse: Boolean = false
5543                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
5544                 zoomReverse: false,
5545
5546                 // @option detectRetina: Boolean = false
5547                 // 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.
5548                 detectRetina: false,
5549
5550                 // @option crossOrigin: Boolean = false
5551                 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
5552                 crossOrigin: false
5553         },
5554
5555         initialize: function (url, options) {
5556
5557                 this._url = url;
5558
5559                 options = L.setOptions(this, options);
5560
5561                 // detecting retina displays, adjusting tileSize and zoom levels
5562                 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
5563
5564                         options.tileSize = Math.floor(options.tileSize / 2);
5565
5566                         if (!options.zoomReverse) {
5567                                 options.zoomOffset++;
5568                                 options.maxZoom--;
5569                         } else {
5570                                 options.zoomOffset--;
5571                                 options.minZoom++;
5572                         }
5573
5574                         options.minZoom = Math.max(0, options.minZoom);
5575                 }
5576
5577                 if (typeof options.subdomains === 'string') {
5578                         options.subdomains = options.subdomains.split('');
5579                 }
5580
5581                 // for https://github.com/Leaflet/Leaflet/issues/137
5582                 if (!L.Browser.android) {
5583                         this.on('tileunload', this._onTileRemove);
5584                 }
5585         },
5586
5587         // @method setUrl(url: String, noRedraw?: Boolean): this
5588         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
5589         setUrl: function (url, noRedraw) {
5590                 this._url = url;
5591
5592                 if (!noRedraw) {
5593                         this.redraw();
5594                 }
5595                 return this;
5596         },
5597
5598         // @method createTile(coords: Object, done?: Function): HTMLElement
5599         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
5600         // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
5601         // callback is called when the tile has been loaded.
5602         createTile: function (coords, done) {
5603                 var tile = document.createElement('img');
5604
5605                 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
5606                 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
5607
5608                 if (this.options.crossOrigin) {
5609                         tile.crossOrigin = '';
5610                 }
5611
5612                 /*
5613                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
5614                  http://www.w3.org/TR/WCAG20-TECHS/H67
5615                 */
5616                 tile.alt = '';
5617
5618                 /*
5619                  Set role="presentation" to force screen readers to ignore this
5620                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
5621                 */
5622                 tile.setAttribute('role', 'presentation');
5623
5624                 tile.src = this.getTileUrl(coords);
5625
5626                 return tile;
5627         },
5628
5629         // @section Extension methods
5630         // @uninheritable
5631         // Layers extending `TileLayer` might reimplement the following method.
5632         // @method getTileUrl(coords: Object): String
5633         // Called only internally, returns the URL for a tile given its coordinates.
5634         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
5635         getTileUrl: function (coords) {
5636                 var data = {
5637                         r: L.Browser.retina ? '@2x' : '',
5638                         s: this._getSubdomain(coords),
5639                         x: coords.x,
5640                         y: coords.y,
5641                         z: this._getZoomForUrl()
5642                 };
5643                 if (this._map && !this._map.options.crs.infinite) {
5644                         var invertedY = this._globalTileRange.max.y - coords.y;
5645                         if (this.options.tms) {
5646                                 data['y'] = invertedY;
5647                         }
5648                         data['-y'] = invertedY;
5649                 }
5650
5651                 return L.Util.template(this._url, L.extend(data, this.options));
5652         },
5653
5654         _tileOnLoad: function (done, tile) {
5655                 // For https://github.com/Leaflet/Leaflet/issues/3332
5656                 if (L.Browser.ielt9) {
5657                         setTimeout(L.bind(done, this, null, tile), 0);
5658                 } else {
5659                         done(null, tile);
5660                 }
5661         },
5662
5663         _tileOnError: function (done, tile, e) {
5664                 var errorUrl = this.options.errorTileUrl;
5665                 if (errorUrl) {
5666                         tile.src = errorUrl;
5667                 }
5668                 done(e, tile);
5669         },
5670
5671         getTileSize: function () {
5672                 var map = this._map,
5673                 tileSize = L.GridLayer.prototype.getTileSize.call(this),
5674                 zoom = this._tileZoom + this.options.zoomOffset,
5675                 minNativeZoom = this.options.minNativeZoom,
5676                 maxNativeZoom = this.options.maxNativeZoom;
5677
5678                 // decrease tile size when scaling below minNativeZoom
5679                 if (minNativeZoom !== null && zoom < minNativeZoom) {
5680                         return tileSize.divideBy(map.getZoomScale(minNativeZoom, zoom)).round();
5681                 }
5682
5683                 // increase tile size when scaling above maxNativeZoom
5684                 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5685                         return tileSize.divideBy(map.getZoomScale(maxNativeZoom, zoom)).round();
5686                 }
5687
5688                 return tileSize;
5689         },
5690
5691         _onTileRemove: function (e) {
5692                 e.tile.onload = null;
5693         },
5694
5695         _getZoomForUrl: function () {
5696                 var zoom = this._tileZoom,
5697                 maxZoom = this.options.maxZoom,
5698                 zoomReverse = this.options.zoomReverse,
5699                 zoomOffset = this.options.zoomOffset,
5700                 minNativeZoom = this.options.minNativeZoom,
5701                 maxNativeZoom = this.options.maxNativeZoom;
5702
5703                 if (zoomReverse) {
5704                         zoom = maxZoom - zoom;
5705                 }
5706
5707                 zoom += zoomOffset;
5708
5709                 if (minNativeZoom !== null && zoom < minNativeZoom) {
5710                         return minNativeZoom;
5711                 }
5712
5713                 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5714                         return maxNativeZoom;
5715                 }
5716
5717                 return zoom;
5718         },
5719
5720         _getSubdomain: function (tilePoint) {
5721                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
5722                 return this.options.subdomains[index];
5723         },
5724
5725         // stops loading all tiles in the background layer
5726         _abortLoading: function () {
5727                 var i, tile;
5728                 for (i in this._tiles) {
5729                         if (this._tiles[i].coords.z !== this._tileZoom) {
5730                                 tile = this._tiles[i].el;
5731
5732                                 tile.onload = L.Util.falseFn;
5733                                 tile.onerror = L.Util.falseFn;
5734
5735                                 if (!tile.complete) {
5736                                         tile.src = L.Util.emptyImageUrl;
5737                                         L.DomUtil.remove(tile);
5738                                 }
5739                         }
5740                 }
5741         }
5742 });
5743
5744
5745 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
5746 // Instantiates a tile layer object given a `URL template` and optionally an options object.
5747
5748 L.tileLayer = function (url, options) {
5749         return new L.TileLayer(url, options);
5750 };
5751
5752
5753
5754 /*
5755  * @class TileLayer.WMS
5756  * @inherits TileLayer
5757  * @aka L.TileLayer.WMS
5758  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
5759  *
5760  * @example
5761  *
5762  * ```js
5763  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
5764  *      layers: 'nexrad-n0r-900913',
5765  *      format: 'image/png',
5766  *      transparent: true,
5767  *      attribution: "Weather data © 2012 IEM Nexrad"
5768  * });
5769  * ```
5770  */
5771
5772 L.TileLayer.WMS = L.TileLayer.extend({
5773
5774         // @section
5775         // @aka TileLayer.WMS options
5776         // If any custom options not documented here are used, they will be sent to the
5777         // WMS server as extra parameters in each request URL. This can be useful for
5778         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
5779         defaultWmsParams: {
5780                 service: 'WMS',
5781                 request: 'GetMap',
5782
5783                 // @option layers: String = ''
5784                 // **(required)** Comma-separated list of WMS layers to show.
5785                 layers: '',
5786
5787                 // @option styles: String = ''
5788                 // Comma-separated list of WMS styles.
5789                 styles: '',
5790
5791                 // @option format: String = 'image/jpeg'
5792                 // WMS image format (use `'image/png'` for layers with transparency).
5793                 format: 'image/jpeg',
5794
5795                 // @option transparent: Boolean = false
5796                 // If `true`, the WMS service will return images with transparency.
5797                 transparent: false,
5798
5799                 // @option version: String = '1.1.1'
5800                 // Version of the WMS service to use
5801                 version: '1.1.1'
5802         },
5803
5804         options: {
5805                 // @option crs: CRS = null
5806                 // Coordinate Reference System to use for the WMS requests, defaults to
5807                 // map CRS. Don't change this if you're not sure what it means.
5808                 crs: null,
5809
5810                 // @option uppercase: Boolean = false
5811                 // If `true`, WMS request parameter keys will be uppercase.
5812                 uppercase: false
5813         },
5814
5815         initialize: function (url, options) {
5816
5817                 this._url = url;
5818
5819                 var wmsParams = L.extend({}, this.defaultWmsParams);
5820
5821                 // all keys that are not TileLayer options go to WMS params
5822                 for (var i in options) {
5823                         if (!(i in this.options)) {
5824                                 wmsParams[i] = options[i];
5825                         }
5826                 }
5827
5828                 options = L.setOptions(this, options);
5829
5830                 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
5831
5832                 this.wmsParams = wmsParams;
5833         },
5834
5835         onAdd: function (map) {
5836
5837                 this._crs = this.options.crs || map.options.crs;
5838                 this._wmsVersion = parseFloat(this.wmsParams.version);
5839
5840                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
5841                 this.wmsParams[projectionKey] = this._crs.code;
5842
5843                 L.TileLayer.prototype.onAdd.call(this, map);
5844         },
5845
5846         getTileUrl: function (coords) {
5847
5848                 var tileBounds = this._tileCoordsToBounds(coords),
5849                     nw = this._crs.project(tileBounds.getNorthWest()),
5850                     se = this._crs.project(tileBounds.getSouthEast()),
5851
5852                     bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
5853                             [se.y, nw.x, nw.y, se.x] :
5854                             [nw.x, se.y, se.x, nw.y]).join(','),
5855
5856                     url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5857
5858                 return url +
5859                         L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5860                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5861         },
5862
5863         // @method setParams(params: Object, noRedraw?: Boolean): this
5864         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5865         setParams: function (params, noRedraw) {
5866
5867                 L.extend(this.wmsParams, params);
5868
5869                 if (!noRedraw) {
5870                         this.redraw();
5871                 }
5872
5873                 return this;
5874         }
5875 });
5876
5877
5878 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5879 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5880 L.tileLayer.wms = function (url, options) {
5881         return new L.TileLayer.WMS(url, options);
5882 };
5883
5884
5885
5886 /*
5887  * @class ImageOverlay
5888  * @aka L.ImageOverlay
5889  * @inherits Interactive layer
5890  *
5891  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5892  *
5893  * @example
5894  *
5895  * ```js
5896  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5897  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5898  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5899  * ```
5900  */
5901
5902 L.ImageOverlay = L.Layer.extend({
5903
5904         // @section
5905         // @aka ImageOverlay options
5906         options: {
5907                 // @option opacity: Number = 1.0
5908                 // The opacity of the image overlay.
5909                 opacity: 1,
5910
5911                 // @option alt: String = ''
5912                 // Text for the `alt` attribute of the image (useful for accessibility).
5913                 alt: '',
5914
5915                 // @option interactive: Boolean = false
5916                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5917                 interactive: false,
5918
5919                 // @option crossOrigin: Boolean = false
5920                 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5921                 crossOrigin: false
5922         },
5923
5924         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5925                 this._url = url;
5926                 this._bounds = L.latLngBounds(bounds);
5927
5928                 L.setOptions(this, options);
5929         },
5930
5931         onAdd: function () {
5932                 if (!this._image) {
5933                         this._initImage();
5934
5935                         if (this.options.opacity < 1) {
5936                                 this._updateOpacity();
5937                         }
5938                 }
5939
5940                 if (this.options.interactive) {
5941                         L.DomUtil.addClass(this._image, 'leaflet-interactive');
5942                         this.addInteractiveTarget(this._image);
5943                 }
5944
5945                 this.getPane().appendChild(this._image);
5946                 this._reset();
5947         },
5948
5949         onRemove: function () {
5950                 L.DomUtil.remove(this._image);
5951                 if (this.options.interactive) {
5952                         this.removeInteractiveTarget(this._image);
5953                 }
5954         },
5955
5956         // @method setOpacity(opacity: Number): this
5957         // Sets the opacity of the overlay.
5958         setOpacity: function (opacity) {
5959                 this.options.opacity = opacity;
5960
5961                 if (this._image) {
5962                         this._updateOpacity();
5963                 }
5964                 return this;
5965         },
5966
5967         setStyle: function (styleOpts) {
5968                 if (styleOpts.opacity) {
5969                         this.setOpacity(styleOpts.opacity);
5970                 }
5971                 return this;
5972         },
5973
5974         // @method bringToFront(): this
5975         // Brings the layer to the top of all overlays.
5976         bringToFront: function () {
5977                 if (this._map) {
5978                         L.DomUtil.toFront(this._image);
5979                 }
5980                 return this;
5981         },
5982
5983         // @method bringToBack(): this
5984         // Brings the layer to the bottom of all overlays.
5985         bringToBack: function () {
5986                 if (this._map) {
5987                         L.DomUtil.toBack(this._image);
5988                 }
5989                 return this;
5990         },
5991
5992         // @method setUrl(url: String): this
5993         // Changes the URL of the image.
5994         setUrl: function (url) {
5995                 this._url = url;
5996
5997                 if (this._image) {
5998                         this._image.src = url;
5999                 }
6000                 return this;
6001         },
6002
6003         setBounds: function (bounds) {
6004                 this._bounds = bounds;
6005
6006                 if (this._map) {
6007                         this._reset();
6008                 }
6009                 return this;
6010         },
6011
6012         getEvents: function () {
6013                 var events = {
6014                         zoom: this._reset,
6015                         viewreset: this._reset
6016                 };
6017
6018                 if (this._zoomAnimated) {
6019                         events.zoomanim = this._animateZoom;
6020                 }
6021
6022                 return events;
6023         },
6024
6025         getBounds: function () {
6026                 return this._bounds;
6027         },
6028
6029         getElement: function () {
6030                 return this._image;
6031         },
6032
6033         _initImage: function () {
6034                 var img = this._image = L.DomUtil.create('img',
6035                                 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
6036
6037                 img.onselectstart = L.Util.falseFn;
6038                 img.onmousemove = L.Util.falseFn;
6039
6040                 img.onload = L.bind(this.fire, this, 'load');
6041
6042                 if (this.options.crossOrigin) {
6043                         img.crossOrigin = '';
6044                 }
6045
6046                 img.src = this._url;
6047                 img.alt = this.options.alt;
6048         },
6049
6050         _animateZoom: function (e) {
6051                 var scale = this._map.getZoomScale(e.zoom),
6052                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
6053
6054                 L.DomUtil.setTransform(this._image, offset, scale);
6055         },
6056
6057         _reset: function () {
6058                 var image = this._image,
6059                     bounds = new L.Bounds(
6060                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
6061                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
6062                     size = bounds.getSize();
6063
6064                 L.DomUtil.setPosition(image, bounds.min);
6065
6066                 image.style.width  = size.x + 'px';
6067                 image.style.height = size.y + 'px';
6068         },
6069
6070         _updateOpacity: function () {
6071                 L.DomUtil.setOpacity(this._image, this.options.opacity);
6072         }
6073 });
6074
6075 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
6076 // Instantiates an image overlay object given the URL of the image and the
6077 // geographical bounds it is tied to.
6078 L.imageOverlay = function (url, bounds, options) {
6079         return new L.ImageOverlay(url, bounds, options);
6080 };
6081
6082
6083
6084 /*
6085  * @class Icon
6086  * @aka L.Icon
6087  * @inherits Layer
6088  *
6089  * Represents an icon to provide when creating a marker.
6090  *
6091  * @example
6092  *
6093  * ```js
6094  * var myIcon = L.icon({
6095  *     iconUrl: 'my-icon.png',
6096  *     iconRetinaUrl: 'my-icon@2x.png',
6097  *     iconSize: [38, 95],
6098  *     iconAnchor: [22, 94],
6099  *     popupAnchor: [-3, -76],
6100  *     shadowUrl: 'my-icon-shadow.png',
6101  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
6102  *     shadowSize: [68, 95],
6103  *     shadowAnchor: [22, 94]
6104  * });
6105  *
6106  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6107  * ```
6108  *
6109  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6110  *
6111  */
6112
6113 L.Icon = L.Class.extend({
6114
6115         /* @section
6116          * @aka Icon options
6117          *
6118          * @option iconUrl: String = null
6119          * **(required)** The URL to the icon image (absolute or relative to your script path).
6120          *
6121          * @option iconRetinaUrl: String = null
6122          * The URL to a retina sized version of the icon image (absolute or relative to your
6123          * script path). Used for Retina screen devices.
6124          *
6125          * @option iconSize: Point = null
6126          * Size of the icon image in pixels.
6127          *
6128          * @option iconAnchor: Point = null
6129          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6130          * will be aligned so that this point is at the marker's geographical location. Centered
6131          * by default if size is specified, also can be set in CSS with negative margins.
6132          *
6133          * @option popupAnchor: Point = null
6134          * The coordinates of the point from which popups will "open", relative to the icon anchor.
6135          *
6136          * @option shadowUrl: String = null
6137          * The URL to the icon shadow image. If not specified, no shadow image will be created.
6138          *
6139          * @option shadowRetinaUrl: String = null
6140          *
6141          * @option shadowSize: Point = null
6142          * Size of the shadow image in pixels.
6143          *
6144          * @option shadowAnchor: Point = null
6145          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6146          * as iconAnchor if not specified).
6147          *
6148          * @option className: String = ''
6149          * A custom class name to assign to both icon and shadow images. Empty by default.
6150          */
6151
6152         initialize: function (options) {
6153                 L.setOptions(this, options);
6154         },
6155
6156         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6157         // Called internally when the icon has to be shown, returns a `<img>` HTML element
6158         // styled according to the options.
6159         createIcon: function (oldIcon) {
6160                 return this._createIcon('icon', oldIcon);
6161         },
6162
6163         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6164         // As `createIcon`, but for the shadow beneath it.
6165         createShadow: function (oldIcon) {
6166                 return this._createIcon('shadow', oldIcon);
6167         },
6168
6169         _createIcon: function (name, oldIcon) {
6170                 var src = this._getIconUrl(name);
6171
6172                 if (!src) {
6173                         if (name === 'icon') {
6174                                 throw new Error('iconUrl not set in Icon options (see the docs).');
6175                         }
6176                         return null;
6177                 }
6178
6179                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6180                 this._setIconStyles(img, name);
6181
6182                 return img;
6183         },
6184
6185         _setIconStyles: function (img, name) {
6186                 var options = this.options;
6187                 var sizeOption = options[name + 'Size'];
6188
6189                 if (typeof sizeOption === 'number') {
6190                         sizeOption = [sizeOption, sizeOption];
6191                 }
6192
6193                 var size = L.point(sizeOption),
6194                     anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6195                             size && size.divideBy(2, true));
6196
6197                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6198
6199                 if (anchor) {
6200                         img.style.marginLeft = (-anchor.x) + 'px';
6201                         img.style.marginTop  = (-anchor.y) + 'px';
6202                 }
6203
6204                 if (size) {
6205                         img.style.width  = size.x + 'px';
6206                         img.style.height = size.y + 'px';
6207                 }
6208         },
6209
6210         _createImg: function (src, el) {
6211                 el = el || document.createElement('img');
6212                 el.src = src;
6213                 return el;
6214         },
6215
6216         _getIconUrl: function (name) {
6217                 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6218         }
6219 });
6220
6221
6222 // @factory L.icon(options: Icon options)
6223 // Creates an icon instance with the given options.
6224 L.icon = function (options) {
6225         return new L.Icon(options);
6226 };
6227
6228
6229
6230 /*
6231  * @miniclass Icon.Default (Icon)
6232  * @aka L.Icon.Default
6233  * @section
6234  *
6235  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6236  * no icon is specified. Points to the blue marker image distributed with Leaflet
6237  * releases.
6238  *
6239  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6240  * (which is a set of `Icon options`).
6241  *
6242  * If you want to _completely_ replace the default icon, override the
6243  * `L.Marker.prototype.options.icon` with your own icon instead.
6244  */
6245
6246 L.Icon.Default = L.Icon.extend({
6247
6248         options: {
6249                 iconUrl:       'marker-icon.png',
6250                 iconRetinaUrl: 'marker-icon-2x.png',
6251                 shadowUrl:     'marker-shadow.png',
6252                 iconSize:    [25, 41],
6253                 iconAnchor:  [12, 41],
6254                 popupAnchor: [1, -34],
6255                 tooltipAnchor: [16, -28],
6256                 shadowSize:  [41, 41]
6257         },
6258
6259         _getIconUrl: function (name) {
6260                 if (!L.Icon.Default.imagePath) {        // Deprecated, backwards-compatibility only
6261                         L.Icon.Default.imagePath = this._detectIconPath();
6262                 }
6263
6264                 // @option imagePath: String
6265                 // `L.Icon.Default` will try to auto-detect the absolute location of the
6266                 // blue icon images. If you are placing these images in a non-standard
6267                 // way, set this option to point to the right absolute path.
6268                 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
6269         },
6270
6271         _detectIconPath: function () {
6272                 var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
6273                 var path = L.DomUtil.getStyle(el, 'background-image') ||
6274                            L.DomUtil.getStyle(el, 'backgroundImage');   // IE8
6275
6276                 document.body.removeChild(el);
6277
6278                 return path.indexOf('url') === 0 ?
6279                         path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
6280         }
6281 });
6282
6283
6284
6285 /*
6286  * @class Marker
6287  * @inherits Interactive layer
6288  * @aka L.Marker
6289  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
6290  *
6291  * @example
6292  *
6293  * ```js
6294  * L.marker([50.5, 30.5]).addTo(map);
6295  * ```
6296  */
6297
6298 L.Marker = L.Layer.extend({
6299
6300         // @section
6301         // @aka Marker options
6302         options: {
6303                 // @option icon: Icon = *
6304                 // Icon class to use for rendering the marker. See [Icon documentation](#L.Icon) for details on how to customize the marker icon. If not specified, a new `L.Icon.Default` is used.
6305                 icon: new L.Icon.Default(),
6306
6307                 // Option inherited from "Interactive layer" abstract class
6308                 interactive: true,
6309
6310                 // @option draggable: Boolean = false
6311                 // Whether the marker is draggable with mouse/touch or not.
6312                 draggable: false,
6313
6314                 // @option keyboard: Boolean = true
6315                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
6316                 keyboard: true,
6317
6318                 // @option title: String = ''
6319                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
6320                 title: '',
6321
6322                 // @option alt: String = ''
6323                 // Text for the `alt` attribute of the icon image (useful for accessibility).
6324                 alt: '',
6325
6326                 // @option zIndexOffset: Number = 0
6327                 // 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).
6328                 zIndexOffset: 0,
6329
6330                 // @option opacity: Number = 1.0
6331                 // The opacity of the marker.
6332                 opacity: 1,
6333
6334                 // @option riseOnHover: Boolean = false
6335                 // If `true`, the marker will get on top of others when you hover the mouse over it.
6336                 riseOnHover: false,
6337
6338                 // @option riseOffset: Number = 250
6339                 // The z-index offset used for the `riseOnHover` feature.
6340                 riseOffset: 250,
6341
6342                 // @option pane: String = 'markerPane'
6343                 // `Map pane` where the markers icon will be added.
6344                 pane: 'markerPane',
6345
6346                 // FIXME: shadowPane is no longer a valid option
6347                 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
6348         },
6349
6350         /* @section
6351          *
6352          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
6353          */
6354
6355         initialize: function (latlng, options) {
6356                 L.setOptions(this, options);
6357                 this._latlng = L.latLng(latlng);
6358         },
6359
6360         onAdd: function (map) {
6361                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
6362
6363                 if (this._zoomAnimated) {
6364                         map.on('zoomanim', this._animateZoom, this);
6365                 }
6366
6367                 this._initIcon();
6368                 this.update();
6369         },
6370
6371         onRemove: function (map) {
6372                 if (this.dragging && this.dragging.enabled()) {
6373                         this.options.draggable = true;
6374                         this.dragging.removeHooks();
6375                 }
6376
6377                 if (this._zoomAnimated) {
6378                         map.off('zoomanim', this._animateZoom, this);
6379                 }
6380
6381                 this._removeIcon();
6382                 this._removeShadow();
6383         },
6384
6385         getEvents: function () {
6386                 return {
6387                         zoom: this.update,
6388                         viewreset: this.update
6389                 };
6390         },
6391
6392         // @method getLatLng: LatLng
6393         // Returns the current geographical position of the marker.
6394         getLatLng: function () {
6395                 return this._latlng;
6396         },
6397
6398         // @method setLatLng(latlng: LatLng): this
6399         // Changes the marker position to the given point.
6400         setLatLng: function (latlng) {
6401                 var oldLatLng = this._latlng;
6402                 this._latlng = L.latLng(latlng);
6403                 this.update();
6404
6405                 // @event move: Event
6406                 // 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`.
6407                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
6408         },
6409
6410         // @method setZIndexOffset(offset: Number): this
6411         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
6412         setZIndexOffset: function (offset) {
6413                 this.options.zIndexOffset = offset;
6414                 return this.update();
6415         },
6416
6417         // @method setIcon(icon: Icon): this
6418         // Changes the marker icon.
6419         setIcon: function (icon) {
6420
6421                 this.options.icon = icon;
6422
6423                 if (this._map) {
6424                         this._initIcon();
6425                         this.update();
6426                 }
6427
6428                 if (this._popup) {
6429                         this.bindPopup(this._popup, this._popup.options);
6430                 }
6431
6432                 return this;
6433         },
6434
6435         getElement: function () {
6436                 return this._icon;
6437         },
6438
6439         update: function () {
6440
6441                 if (this._icon) {
6442                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
6443                         this._setPos(pos);
6444                 }
6445
6446                 return this;
6447         },
6448
6449         _initIcon: function () {
6450                 var options = this.options,
6451                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6452
6453                 var icon = options.icon.createIcon(this._icon),
6454                     addIcon = false;
6455
6456                 // if we're not reusing the icon, remove the old one and init new one
6457                 if (icon !== this._icon) {
6458                         if (this._icon) {
6459                                 this._removeIcon();
6460                         }
6461                         addIcon = true;
6462
6463                         if (options.title) {
6464                                 icon.title = options.title;
6465                         }
6466                         if (options.alt) {
6467                                 icon.alt = options.alt;
6468                         }
6469                 }
6470
6471                 L.DomUtil.addClass(icon, classToAdd);
6472
6473                 if (options.keyboard) {
6474                         icon.tabIndex = '0';
6475                 }
6476
6477                 this._icon = icon;
6478
6479                 if (options.riseOnHover) {
6480                         this.on({
6481                                 mouseover: this._bringToFront,
6482                                 mouseout: this._resetZIndex
6483                         });
6484                 }
6485
6486                 var newShadow = options.icon.createShadow(this._shadow),
6487                     addShadow = false;
6488
6489                 if (newShadow !== this._shadow) {
6490                         this._removeShadow();
6491                         addShadow = true;
6492                 }
6493
6494                 if (newShadow) {
6495                         L.DomUtil.addClass(newShadow, classToAdd);
6496                 }
6497                 this._shadow = newShadow;
6498
6499
6500                 if (options.opacity < 1) {
6501                         this._updateOpacity();
6502                 }
6503
6504
6505                 if (addIcon) {
6506                         this.getPane().appendChild(this._icon);
6507                 }
6508                 this._initInteraction();
6509                 if (newShadow && addShadow) {
6510                         this.getPane('shadowPane').appendChild(this._shadow);
6511                 }
6512         },
6513
6514         _removeIcon: function () {
6515                 if (this.options.riseOnHover) {
6516                         this.off({
6517                                 mouseover: this._bringToFront,
6518                                 mouseout: this._resetZIndex
6519                         });
6520                 }
6521
6522                 L.DomUtil.remove(this._icon);
6523                 this.removeInteractiveTarget(this._icon);
6524
6525                 this._icon = null;
6526         },
6527
6528         _removeShadow: function () {
6529                 if (this._shadow) {
6530                         L.DomUtil.remove(this._shadow);
6531                 }
6532                 this._shadow = null;
6533         },
6534
6535         _setPos: function (pos) {
6536                 L.DomUtil.setPosition(this._icon, pos);
6537
6538                 if (this._shadow) {
6539                         L.DomUtil.setPosition(this._shadow, pos);
6540                 }
6541
6542                 this._zIndex = pos.y + this.options.zIndexOffset;
6543
6544                 this._resetZIndex();
6545         },
6546
6547         _updateZIndex: function (offset) {
6548                 this._icon.style.zIndex = this._zIndex + offset;
6549         },
6550
6551         _animateZoom: function (opt) {
6552                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
6553
6554                 this._setPos(pos);
6555         },
6556
6557         _initInteraction: function () {
6558
6559                 if (!this.options.interactive) { return; }
6560
6561                 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
6562
6563                 this.addInteractiveTarget(this._icon);
6564
6565                 if (L.Handler.MarkerDrag) {
6566                         var draggable = this.options.draggable;
6567                         if (this.dragging) {
6568                                 draggable = this.dragging.enabled();
6569                                 this.dragging.disable();
6570                         }
6571
6572                         this.dragging = new L.Handler.MarkerDrag(this);
6573
6574                         if (draggable) {
6575                                 this.dragging.enable();
6576                         }
6577                 }
6578         },
6579
6580         // @method setOpacity(opacity: Number): this
6581         // Changes the opacity of the marker.
6582         setOpacity: function (opacity) {
6583                 this.options.opacity = opacity;
6584                 if (this._map) {
6585                         this._updateOpacity();
6586                 }
6587
6588                 return this;
6589         },
6590
6591         _updateOpacity: function () {
6592                 var opacity = this.options.opacity;
6593
6594                 L.DomUtil.setOpacity(this._icon, opacity);
6595
6596                 if (this._shadow) {
6597                         L.DomUtil.setOpacity(this._shadow, opacity);
6598                 }
6599         },
6600
6601         _bringToFront: function () {
6602                 this._updateZIndex(this.options.riseOffset);
6603         },
6604
6605         _resetZIndex: function () {
6606                 this._updateZIndex(0);
6607         },
6608
6609         _getPopupAnchor: function () {
6610                 return this.options.icon.options.popupAnchor || [0, 0];
6611         },
6612
6613         _getTooltipAnchor: function () {
6614                 return this.options.icon.options.tooltipAnchor || [0, 0];
6615         }
6616 });
6617
6618
6619 // factory L.marker(latlng: LatLng, options? : Marker options)
6620
6621 // @factory L.marker(latlng: LatLng, options? : Marker options)
6622 // Instantiates a Marker object given a geographical point and optionally an options object.
6623 L.marker = function (latlng, options) {
6624         return new L.Marker(latlng, options);
6625 };
6626
6627
6628
6629 /*
6630  * @class DivIcon
6631  * @aka L.DivIcon
6632  * @inherits Icon
6633  *
6634  * Represents a lightweight icon for markers that uses a simple `<div>`
6635  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
6636  *
6637  * @example
6638  * ```js
6639  * var myIcon = L.divIcon({className: 'my-div-icon'});
6640  * // you can set .my-div-icon styles in CSS
6641  *
6642  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6643  * ```
6644  *
6645  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
6646  */
6647
6648 L.DivIcon = L.Icon.extend({
6649         options: {
6650                 // @section
6651                 // @aka DivIcon options
6652                 iconSize: [12, 12], // also can be set through CSS
6653
6654                 // iconAnchor: (Point),
6655                 // popupAnchor: (Point),
6656
6657                 // @option html: String = ''
6658                 // Custom HTML code to put inside the div element, empty by default.
6659                 html: false,
6660
6661                 // @option bgPos: Point = [0, 0]
6662                 // Optional relative position of the background, in pixels
6663                 bgPos: null,
6664
6665                 className: 'leaflet-div-icon'
6666         },
6667
6668         createIcon: function (oldIcon) {
6669                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
6670                     options = this.options;
6671
6672                 div.innerHTML = options.html !== false ? options.html : '';
6673
6674                 if (options.bgPos) {
6675                         var bgPos = L.point(options.bgPos);
6676                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
6677                 }
6678                 this._setIconStyles(div, 'icon');
6679
6680                 return div;
6681         },
6682
6683         createShadow: function () {
6684                 return null;
6685         }
6686 });
6687
6688 // @factory L.divIcon(options: DivIcon options)
6689 // Creates a `DivIcon` instance with the given options.
6690 L.divIcon = function (options) {
6691         return new L.DivIcon(options);
6692 };
6693
6694
6695
6696 /*
6697  * @class DivOverlay
6698  * @inherits Layer
6699  * @aka L.DivOverlay
6700  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
6701  */
6702
6703 // @namespace DivOverlay
6704 L.DivOverlay = L.Layer.extend({
6705
6706         // @section
6707         // @aka DivOverlay options
6708         options: {
6709                 // @option offset: Point = Point(0, 7)
6710                 // The offset of the popup position. Useful to control the anchor
6711                 // of the popup when opening it on some overlays.
6712                 offset: [0, 7],
6713
6714                 // @option className: String = ''
6715                 // A custom CSS class name to assign to the popup.
6716                 className: '',
6717
6718                 // @option pane: String = 'popupPane'
6719                 // `Map pane` where the popup will be added.
6720                 pane: 'popupPane'
6721         },
6722
6723         initialize: function (options, source) {
6724                 L.setOptions(this, options);
6725
6726                 this._source = source;
6727         },
6728
6729         onAdd: function (map) {
6730                 this._zoomAnimated = map._zoomAnimated;
6731
6732                 if (!this._container) {
6733                         this._initLayout();
6734                 }
6735
6736                 if (map._fadeAnimated) {
6737                         L.DomUtil.setOpacity(this._container, 0);
6738                 }
6739
6740                 clearTimeout(this._removeTimeout);
6741                 this.getPane().appendChild(this._container);
6742                 this.update();
6743
6744                 if (map._fadeAnimated) {
6745                         L.DomUtil.setOpacity(this._container, 1);
6746                 }
6747
6748                 this.bringToFront();
6749         },
6750
6751         onRemove: function (map) {
6752                 if (map._fadeAnimated) {
6753                         L.DomUtil.setOpacity(this._container, 0);
6754                         this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
6755                 } else {
6756                         L.DomUtil.remove(this._container);
6757                 }
6758         },
6759
6760         // @namespace Popup
6761         // @method getLatLng: LatLng
6762         // Returns the geographical point of popup.
6763         getLatLng: function () {
6764                 return this._latlng;
6765         },
6766
6767         // @method setLatLng(latlng: LatLng): this
6768         // Sets the geographical point where the popup will open.
6769         setLatLng: function (latlng) {
6770                 this._latlng = L.latLng(latlng);
6771                 if (this._map) {
6772                         this._updatePosition();
6773                         this._adjustPan();
6774                 }
6775                 return this;
6776         },
6777
6778         // @method getContent: String|HTMLElement
6779         // Returns the content of the popup.
6780         getContent: function () {
6781                 return this._content;
6782         },
6783
6784         // @method setContent(htmlContent: String|HTMLElement|Function): this
6785         // 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.
6786         setContent: function (content) {
6787                 this._content = content;
6788                 this.update();
6789                 return this;
6790         },
6791
6792         // @method getElement: String|HTMLElement
6793         // Alias for [getContent()](#popup-getcontent)
6794         getElement: function () {
6795                 return this._container;
6796         },
6797
6798         // @method update: null
6799         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
6800         update: function () {
6801                 if (!this._map) { return; }
6802
6803                 this._container.style.visibility = 'hidden';
6804
6805                 this._updateContent();
6806                 this._updateLayout();
6807                 this._updatePosition();
6808
6809                 this._container.style.visibility = '';
6810
6811                 this._adjustPan();
6812         },
6813
6814         getEvents: function () {
6815                 var events = {
6816                         zoom: this._updatePosition,
6817                         viewreset: this._updatePosition
6818                 };
6819
6820                 if (this._zoomAnimated) {
6821                         events.zoomanim = this._animateZoom;
6822                 }
6823                 return events;
6824         },
6825
6826         // @method isOpen: Boolean
6827         // Returns `true` when the popup is visible on the map.
6828         isOpen: function () {
6829                 return !!this._map && this._map.hasLayer(this);
6830         },
6831
6832         // @method bringToFront: this
6833         // Brings this popup in front of other popups (in the same map pane).
6834         bringToFront: function () {
6835                 if (this._map) {
6836                         L.DomUtil.toFront(this._container);
6837                 }
6838                 return this;
6839         },
6840
6841         // @method bringToBack: this
6842         // Brings this popup to the back of other popups (in the same map pane).
6843         bringToBack: function () {
6844                 if (this._map) {
6845                         L.DomUtil.toBack(this._container);
6846                 }
6847                 return this;
6848         },
6849
6850         _updateContent: function () {
6851                 if (!this._content) { return; }
6852
6853                 var node = this._contentNode;
6854                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
6855
6856                 if (typeof content === 'string') {
6857                         node.innerHTML = content;
6858                 } else {
6859                         while (node.hasChildNodes()) {
6860                                 node.removeChild(node.firstChild);
6861                         }
6862                         node.appendChild(content);
6863                 }
6864                 this.fire('contentupdate');
6865         },
6866
6867         _updatePosition: function () {
6868                 if (!this._map) { return; }
6869
6870                 var pos = this._map.latLngToLayerPoint(this._latlng),
6871                     offset = L.point(this.options.offset),
6872                     anchor = this._getAnchor();
6873
6874                 if (this._zoomAnimated) {
6875                         L.DomUtil.setPosition(this._container, pos.add(anchor));
6876                 } else {
6877                         offset = offset.add(pos).add(anchor);
6878                 }
6879
6880                 var bottom = this._containerBottom = -offset.y,
6881                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6882
6883                 // bottom position the popup in case the height of the popup changes (images loading etc)
6884                 this._container.style.bottom = bottom + 'px';
6885                 this._container.style.left = left + 'px';
6886         },
6887
6888         _getAnchor: function () {
6889                 return [0, 0];
6890         }
6891
6892 });
6893
6894
6895
6896 /*
6897  * @class Popup
6898  * @inherits DivOverlay
6899  * @aka L.Popup
6900  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6901  * open popups while making sure that only one popup is open at one time
6902  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6903  *
6904  * @example
6905  *
6906  * If you want to just bind a popup to marker click and then open it, it's really easy:
6907  *
6908  * ```js
6909  * marker.bindPopup(popupContent).openPopup();
6910  * ```
6911  * Path overlays like polylines also have a `bindPopup` method.
6912  * Here's a more complicated way to open a popup on a map:
6913  *
6914  * ```js
6915  * var popup = L.popup()
6916  *      .setLatLng(latlng)
6917  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6918  *      .openOn(map);
6919  * ```
6920  */
6921
6922
6923 // @namespace Popup
6924 L.Popup = L.DivOverlay.extend({
6925
6926         // @section
6927         // @aka Popup options
6928         options: {
6929                 // @option maxWidth: Number = 300
6930                 // Max width of the popup, in pixels.
6931                 maxWidth: 300,
6932
6933                 // @option minWidth: Number = 50
6934                 // Min width of the popup, in pixels.
6935                 minWidth: 50,
6936
6937                 // @option maxHeight: Number = null
6938                 // If set, creates a scrollable container of the given height
6939                 // inside a popup if its content exceeds it.
6940                 maxHeight: null,
6941
6942                 // @option autoPan: Boolean = true
6943                 // Set it to `false` if you don't want the map to do panning animation
6944                 // to fit the opened popup.
6945                 autoPan: true,
6946
6947                 // @option autoPanPaddingTopLeft: Point = null
6948                 // The margin between the popup and the top left corner of the map
6949                 // view after autopanning was performed.
6950                 autoPanPaddingTopLeft: null,
6951
6952                 // @option autoPanPaddingBottomRight: Point = null
6953                 // The margin between the popup and the bottom right corner of the map
6954                 // view after autopanning was performed.
6955                 autoPanPaddingBottomRight: null,
6956
6957                 // @option autoPanPadding: Point = Point(5, 5)
6958                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
6959                 autoPanPadding: [5, 5],
6960
6961                 // @option keepInView: Boolean = false
6962                 // Set it to `true` if you want to prevent users from panning the popup
6963                 // off of the screen while it is open.
6964                 keepInView: false,
6965
6966                 // @option closeButton: Boolean = true
6967                 // Controls the presence of a close button in the popup.
6968                 closeButton: true,
6969
6970                 // @option autoClose: Boolean = true
6971                 // Set it to `false` if you want to override the default behavior of
6972                 // the popup closing when user clicks the map (set globally by
6973                 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
6974                 autoClose: true,
6975
6976                 // @option className: String = ''
6977                 // A custom CSS class name to assign to the popup.
6978                 className: ''
6979         },
6980
6981         // @namespace Popup
6982         // @method openOn(map: Map): this
6983         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
6984         openOn: function (map) {
6985                 map.openPopup(this);
6986                 return this;
6987         },
6988
6989         onAdd: function (map) {
6990                 L.DivOverlay.prototype.onAdd.call(this, map);
6991
6992                 // @namespace Map
6993                 // @section Popup events
6994                 // @event popupopen: PopupEvent
6995                 // Fired when a popup is opened in the map
6996                 map.fire('popupopen', {popup: this});
6997
6998                 if (this._source) {
6999                         // @namespace Layer
7000                         // @section Popup events
7001                         // @event popupopen: PopupEvent
7002                         // Fired when a popup bound to this layer is opened
7003                         this._source.fire('popupopen', {popup: this}, true);
7004                         // For non-path layers, we toggle the popup when clicking
7005                         // again the layer, so prevent the map to reopen it.
7006                         if (!(this._source instanceof L.Path)) {
7007                                 this._source.on('preclick', L.DomEvent.stopPropagation);
7008                         }
7009                 }
7010         },
7011
7012         onRemove: function (map) {
7013                 L.DivOverlay.prototype.onRemove.call(this, map);
7014
7015                 // @namespace Map
7016                 // @section Popup events
7017                 // @event popupclose: PopupEvent
7018                 // Fired when a popup in the map is closed
7019                 map.fire('popupclose', {popup: this});
7020
7021                 if (this._source) {
7022                         // @namespace Layer
7023                         // @section Popup events
7024                         // @event popupclose: PopupEvent
7025                         // Fired when a popup bound to this layer is closed
7026                         this._source.fire('popupclose', {popup: this}, true);
7027                         if (!(this._source instanceof L.Path)) {
7028                                 this._source.off('preclick', L.DomEvent.stopPropagation);
7029                         }
7030                 }
7031         },
7032
7033         getEvents: function () {
7034                 var events = L.DivOverlay.prototype.getEvents.call(this);
7035
7036                 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
7037                         events.preclick = this._close;
7038                 }
7039
7040                 if (this.options.keepInView) {
7041                         events.moveend = this._adjustPan;
7042                 }
7043
7044                 return events;
7045         },
7046
7047         _close: function () {
7048                 if (this._map) {
7049                         this._map.closePopup(this);
7050                 }
7051         },
7052
7053         _initLayout: function () {
7054                 var prefix = 'leaflet-popup',
7055                     container = this._container = L.DomUtil.create('div',
7056                         prefix + ' ' + (this.options.className || '') +
7057                         ' leaflet-zoom-animated');
7058
7059                 if (this.options.closeButton) {
7060                         var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
7061                         closeButton.href = '#close';
7062                         closeButton.innerHTML = '&#215;';
7063
7064                         L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
7065                 }
7066
7067                 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
7068                 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
7069
7070                 L.DomEvent
7071                         .disableClickPropagation(wrapper)
7072                         .disableScrollPropagation(this._contentNode)
7073                         .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
7074
7075                 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
7076                 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
7077         },
7078
7079         _updateLayout: function () {
7080                 var container = this._contentNode,
7081                     style = container.style;
7082
7083                 style.width = '';
7084                 style.whiteSpace = 'nowrap';
7085
7086                 var width = container.offsetWidth;
7087                 width = Math.min(width, this.options.maxWidth);
7088                 width = Math.max(width, this.options.minWidth);
7089
7090                 style.width = (width + 1) + 'px';
7091                 style.whiteSpace = '';
7092
7093                 style.height = '';
7094
7095                 var height = container.offsetHeight,
7096                     maxHeight = this.options.maxHeight,
7097                     scrolledClass = 'leaflet-popup-scrolled';
7098
7099                 if (maxHeight && height > maxHeight) {
7100                         style.height = maxHeight + 'px';
7101                         L.DomUtil.addClass(container, scrolledClass);
7102                 } else {
7103                         L.DomUtil.removeClass(container, scrolledClass);
7104                 }
7105
7106                 this._containerWidth = this._container.offsetWidth;
7107         },
7108
7109         _animateZoom: function (e) {
7110                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
7111                     anchor = this._getAnchor();
7112                 L.DomUtil.setPosition(this._container, pos.add(anchor));
7113         },
7114
7115         _adjustPan: function () {
7116                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
7117
7118                 var map = this._map,
7119                     marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
7120                     containerHeight = this._container.offsetHeight + marginBottom,
7121                     containerWidth = this._containerWidth,
7122                     layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
7123
7124                 layerPos._add(L.DomUtil.getPosition(this._container));
7125
7126                 var containerPos = map.layerPointToContainerPoint(layerPos),
7127                     padding = L.point(this.options.autoPanPadding),
7128                     paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
7129                     paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
7130                     size = map.getSize(),
7131                     dx = 0,
7132                     dy = 0;
7133
7134                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
7135                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
7136                 }
7137                 if (containerPos.x - dx - paddingTL.x < 0) { // left
7138                         dx = containerPos.x - paddingTL.x;
7139                 }
7140                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
7141                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
7142                 }
7143                 if (containerPos.y - dy - paddingTL.y < 0) { // top
7144                         dy = containerPos.y - paddingTL.y;
7145                 }
7146
7147                 // @namespace Map
7148                 // @section Popup events
7149                 // @event autopanstart: Event
7150                 // Fired when the map starts autopanning when opening a popup.
7151                 if (dx || dy) {
7152                         map
7153                             .fire('autopanstart')
7154                             .panBy([dx, dy]);
7155                 }
7156         },
7157
7158         _onCloseButtonClick: function (e) {
7159                 this._close();
7160                 L.DomEvent.stop(e);
7161         },
7162
7163         _getAnchor: function () {
7164                 // Where should we anchor the popup on the source layer?
7165                 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
7166         }
7167
7168 });
7169
7170 // @namespace Popup
7171 // @factory L.popup(options?: Popup options, source?: Layer)
7172 // 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.
7173 L.popup = function (options, source) {
7174         return new L.Popup(options, source);
7175 };
7176
7177
7178 /* @namespace Map
7179  * @section Interaction Options
7180  * @option closePopupOnClick: Boolean = true
7181  * Set it to `false` if you don't want popups to close when user clicks the map.
7182  */
7183 L.Map.mergeOptions({
7184         closePopupOnClick: true
7185 });
7186
7187
7188 // @namespace Map
7189 // @section Methods for Layers and Controls
7190 L.Map.include({
7191         // @method openPopup(popup: Popup): this
7192         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
7193         // @alternative
7194         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
7195         // Creates a popup with the specified content and options and opens it in the given point on a map.
7196         openPopup: function (popup, latlng, options) {
7197                 if (!(popup instanceof L.Popup)) {
7198                         popup = new L.Popup(options).setContent(popup);
7199                 }
7200
7201                 if (latlng) {
7202                         popup.setLatLng(latlng);
7203                 }
7204
7205                 if (this.hasLayer(popup)) {
7206                         return this;
7207                 }
7208
7209                 if (this._popup && this._popup.options.autoClose) {
7210                         this.closePopup();
7211                 }
7212
7213                 this._popup = popup;
7214                 return this.addLayer(popup);
7215         },
7216
7217         // @method closePopup(popup?: Popup): this
7218         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
7219         closePopup: function (popup) {
7220                 if (!popup || popup === this._popup) {
7221                         popup = this._popup;
7222                         this._popup = null;
7223                 }
7224                 if (popup) {
7225                         this.removeLayer(popup);
7226                 }
7227                 return this;
7228         }
7229 });
7230
7231 /*
7232  * @namespace Layer
7233  * @section Popup methods example
7234  *
7235  * All layers share a set of methods convenient for binding popups to it.
7236  *
7237  * ```js
7238  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
7239  * layer.openPopup();
7240  * layer.closePopup();
7241  * ```
7242  *
7243  * 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.
7244  */
7245
7246 // @section Popup methods
7247 L.Layer.include({
7248
7249         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
7250         // Binds a popup to the layer with the passed `content` and sets up the
7251         // neccessary event listeners. If a `Function` is passed it will receive
7252         // the layer as the first argument and should return a `String` or `HTMLElement`.
7253         bindPopup: function (content, options) {
7254
7255                 if (content instanceof L.Popup) {
7256                         L.setOptions(content, options);
7257                         this._popup = content;
7258                         content._source = this;
7259                 } else {
7260                         if (!this._popup || options) {
7261                                 this._popup = new L.Popup(options, this);
7262                         }
7263                         this._popup.setContent(content);
7264                 }
7265
7266                 if (!this._popupHandlersAdded) {
7267                         this.on({
7268                                 click: this._openPopup,
7269                                 remove: this.closePopup,
7270                                 move: this._movePopup
7271                         });
7272                         this._popupHandlersAdded = true;
7273                 }
7274
7275                 return this;
7276         },
7277
7278         // @method unbindPopup(): this
7279         // Removes the popup previously bound with `bindPopup`.
7280         unbindPopup: function () {
7281                 if (this._popup) {
7282                         this.off({
7283                                 click: this._openPopup,
7284                                 remove: this.closePopup,
7285                                 move: this._movePopup
7286                         });
7287                         this._popupHandlersAdded = false;
7288                         this._popup = null;
7289                 }
7290                 return this;
7291         },
7292
7293         // @method openPopup(latlng?: LatLng): this
7294         // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
7295         openPopup: function (layer, latlng) {
7296                 if (!(layer instanceof L.Layer)) {
7297                         latlng = layer;
7298                         layer = this;
7299                 }
7300
7301                 if (layer instanceof L.FeatureGroup) {
7302                         for (var id in this._layers) {
7303                                 layer = this._layers[id];
7304                                 break;
7305                         }
7306                 }
7307
7308                 if (!latlng) {
7309                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7310                 }
7311
7312                 if (this._popup && this._map) {
7313                         // set popup source to this layer
7314                         this._popup._source = layer;
7315
7316                         // update the popup (content, layout, ect...)
7317                         this._popup.update();
7318
7319                         // open the popup on the map
7320                         this._map.openPopup(this._popup, latlng);
7321                 }
7322
7323                 return this;
7324         },
7325
7326         // @method closePopup(): this
7327         // Closes the popup bound to this layer if it is open.
7328         closePopup: function () {
7329                 if (this._popup) {
7330                         this._popup._close();
7331                 }
7332                 return this;
7333         },
7334
7335         // @method togglePopup(): this
7336         // Opens or closes the popup bound to this layer depending on its current state.
7337         togglePopup: function (target) {
7338                 if (this._popup) {
7339                         if (this._popup._map) {
7340                                 this.closePopup();
7341                         } else {
7342                                 this.openPopup(target);
7343                         }
7344                 }
7345                 return this;
7346         },
7347
7348         // @method isPopupOpen(): boolean
7349         // Returns `true` if the popup bound to this layer is currently open.
7350         isPopupOpen: function () {
7351                 return this._popup.isOpen();
7352         },
7353
7354         // @method setPopupContent(content: String|HTMLElement|Popup): this
7355         // Sets the content of the popup bound to this layer.
7356         setPopupContent: function (content) {
7357                 if (this._popup) {
7358                         this._popup.setContent(content);
7359                 }
7360                 return this;
7361         },
7362
7363         // @method getPopup(): Popup
7364         // Returns the popup bound to this layer.
7365         getPopup: function () {
7366                 return this._popup;
7367         },
7368
7369         _openPopup: function (e) {
7370                 var layer = e.layer || e.target;
7371
7372                 if (!this._popup) {
7373                         return;
7374                 }
7375
7376                 if (!this._map) {
7377                         return;
7378                 }
7379
7380                 // prevent map click
7381                 L.DomEvent.stop(e);
7382
7383                 // if this inherits from Path its a vector and we can just
7384                 // open the popup at the new location
7385                 if (layer instanceof L.Path) {
7386                         this.openPopup(e.layer || e.target, e.latlng);
7387                         return;
7388                 }
7389
7390                 // otherwise treat it like a marker and figure out
7391                 // if we should toggle it open/closed
7392                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
7393                         this.closePopup();
7394                 } else {
7395                         this.openPopup(layer, e.latlng);
7396                 }
7397         },
7398
7399         _movePopup: function (e) {
7400                 this._popup.setLatLng(e.latlng);
7401         }
7402 });
7403
7404
7405
7406 /*
7407  * @class Tooltip
7408  * @inherits DivOverlay
7409  * @aka L.Tooltip
7410  * Used to display small texts on top of map layers.
7411  *
7412  * @example
7413  *
7414  * ```js
7415  * marker.bindTooltip("my tooltip text").openTooltip();
7416  * ```
7417  * Note about tooltip offset. Leaflet takes two options in consideration
7418  * for computing tooltip offseting:
7419  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
7420  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
7421  *   move it to the bottom. Negatives will move to the left and top.
7422  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
7423  *   should adapt this value if you use a custom icon.
7424  */
7425
7426
7427 // @namespace Tooltip
7428 L.Tooltip = L.DivOverlay.extend({
7429
7430         // @section
7431         // @aka Tooltip options
7432         options: {
7433                 // @option pane: String = 'tooltipPane'
7434                 // `Map pane` where the tooltip will be added.
7435                 pane: 'tooltipPane',
7436
7437                 // @option offset: Point = Point(0, 0)
7438                 // Optional offset of the tooltip position.
7439                 offset: [0, 0],
7440
7441                 // @option direction: String = 'auto'
7442                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
7443                 // `top`, `bottom`, `center`, `auto`.
7444                 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
7445                 // position on the map.
7446                 direction: 'auto',
7447
7448                 // @option permanent: Boolean = false
7449                 // Whether to open the tooltip permanently or only on mouseover.
7450                 permanent: false,
7451
7452                 // @option sticky: Boolean = false
7453                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
7454                 sticky: false,
7455
7456                 // @option interactive: Boolean = false
7457                 // If true, the tooltip will listen to the feature events.
7458                 interactive: false,
7459
7460                 // @option opacity: Number = 0.9
7461                 // Tooltip container opacity.
7462                 opacity: 0.9
7463         },
7464
7465         onAdd: function (map) {
7466                 L.DivOverlay.prototype.onAdd.call(this, map);
7467                 this.setOpacity(this.options.opacity);
7468
7469                 // @namespace Map
7470                 // @section Tooltip events
7471                 // @event tooltipopen: TooltipEvent
7472                 // Fired when a tooltip is opened in the map.
7473                 map.fire('tooltipopen', {tooltip: this});
7474
7475                 if (this._source) {
7476                         // @namespace Layer
7477                         // @section Tooltip events
7478                         // @event tooltipopen: TooltipEvent
7479                         // Fired when a tooltip bound to this layer is opened.
7480                         this._source.fire('tooltipopen', {tooltip: this}, true);
7481                 }
7482         },
7483
7484         onRemove: function (map) {
7485                 L.DivOverlay.prototype.onRemove.call(this, map);
7486
7487                 // @namespace Map
7488                 // @section Tooltip events
7489                 // @event tooltipclose: TooltipEvent
7490                 // Fired when a tooltip in the map is closed.
7491                 map.fire('tooltipclose', {tooltip: this});
7492
7493                 if (this._source) {
7494                         // @namespace Layer
7495                         // @section Tooltip events
7496                         // @event tooltipclose: TooltipEvent
7497                         // Fired when a tooltip bound to this layer is closed.
7498                         this._source.fire('tooltipclose', {tooltip: this}, true);
7499                 }
7500         },
7501
7502         getEvents: function () {
7503                 var events = L.DivOverlay.prototype.getEvents.call(this);
7504
7505                 if (L.Browser.touch && !this.options.permanent) {
7506                         events.preclick = this._close;
7507                 }
7508
7509                 return events;
7510         },
7511
7512         _close: function () {
7513                 if (this._map) {
7514                         this._map.closeTooltip(this);
7515                 }
7516         },
7517
7518         _initLayout: function () {
7519                 var prefix = 'leaflet-tooltip',
7520                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7521
7522                 this._contentNode = this._container = L.DomUtil.create('div', className);
7523         },
7524
7525         _updateLayout: function () {},
7526
7527         _adjustPan: function () {},
7528
7529         _setPosition: function (pos) {
7530                 var map = this._map,
7531                     container = this._container,
7532                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
7533                     tooltipPoint = map.layerPointToContainerPoint(pos),
7534                     direction = this.options.direction,
7535                     tooltipWidth = container.offsetWidth,
7536                     tooltipHeight = container.offsetHeight,
7537                     offset = L.point(this.options.offset),
7538                     anchor = this._getAnchor();
7539
7540                 if (direction === 'top') {
7541                         pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
7542                 } else if (direction === 'bottom') {
7543                         pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true));
7544                 } else if (direction === 'center') {
7545                         pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
7546                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
7547                         direction = 'right';
7548                         pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
7549                 } else {
7550                         direction = 'left';
7551                         pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
7552                 }
7553
7554                 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
7555                 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
7556                 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
7557                 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
7558                 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
7559                 L.DomUtil.setPosition(container, pos);
7560         },
7561
7562         _updatePosition: function () {
7563                 var pos = this._map.latLngToLayerPoint(this._latlng);
7564                 this._setPosition(pos);
7565         },
7566
7567         setOpacity: function (opacity) {
7568                 this.options.opacity = opacity;
7569
7570                 if (this._container) {
7571                         L.DomUtil.setOpacity(this._container, opacity);
7572                 }
7573         },
7574
7575         _animateZoom: function (e) {
7576                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
7577                 this._setPosition(pos);
7578         },
7579
7580         _getAnchor: function () {
7581                 // Where should we anchor the tooltip on the source layer?
7582                 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
7583         }
7584
7585 });
7586
7587 // @namespace Tooltip
7588 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
7589 // 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.
7590 L.tooltip = function (options, source) {
7591         return new L.Tooltip(options, source);
7592 };
7593
7594 // @namespace Map
7595 // @section Methods for Layers and Controls
7596 L.Map.include({
7597
7598         // @method openTooltip(tooltip: Tooltip): this
7599         // Opens the specified tooltip.
7600         // @alternative
7601         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
7602         // Creates a tooltip with the specified content and options and open it.
7603         openTooltip: function (tooltip, latlng, options) {
7604                 if (!(tooltip instanceof L.Tooltip)) {
7605                         tooltip = new L.Tooltip(options).setContent(tooltip);
7606                 }
7607
7608                 if (latlng) {
7609                         tooltip.setLatLng(latlng);
7610                 }
7611
7612                 if (this.hasLayer(tooltip)) {
7613                         return this;
7614                 }
7615
7616                 return this.addLayer(tooltip);
7617         },
7618
7619         // @method closeTooltip(tooltip?: Tooltip): this
7620         // Closes the tooltip given as parameter.
7621         closeTooltip: function (tooltip) {
7622                 if (tooltip) {
7623                         this.removeLayer(tooltip);
7624                 }
7625                 return this;
7626         }
7627
7628 });
7629
7630 /*
7631  * @namespace Layer
7632  * @section Tooltip methods example
7633  *
7634  * All layers share a set of methods convenient for binding tooltips to it.
7635  *
7636  * ```js
7637  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
7638  * layer.openTooltip();
7639  * layer.closeTooltip();
7640  * ```
7641  */
7642
7643 // @section Tooltip methods
7644 L.Layer.include({
7645
7646         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
7647         // Binds a tooltip to the layer with the passed `content` and sets up the
7648         // neccessary event listeners. If a `Function` is passed it will receive
7649         // the layer as the first argument and should return a `String` or `HTMLElement`.
7650         bindTooltip: function (content, options) {
7651
7652                 if (content instanceof L.Tooltip) {
7653                         L.setOptions(content, options);
7654                         this._tooltip = content;
7655                         content._source = this;
7656                 } else {
7657                         if (!this._tooltip || options) {
7658                                 this._tooltip = L.tooltip(options, this);
7659                         }
7660                         this._tooltip.setContent(content);
7661
7662                 }
7663
7664                 this._initTooltipInteractions();
7665
7666                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
7667                         this.openTooltip();
7668                 }
7669
7670                 return this;
7671         },
7672
7673         // @method unbindTooltip(): this
7674         // Removes the tooltip previously bound with `bindTooltip`.
7675         unbindTooltip: function () {
7676                 if (this._tooltip) {
7677                         this._initTooltipInteractions(true);
7678                         this.closeTooltip();
7679                         this._tooltip = null;
7680                 }
7681                 return this;
7682         },
7683
7684         _initTooltipInteractions: function (remove) {
7685                 if (!remove && this._tooltipHandlersAdded) { return; }
7686                 var onOff = remove ? 'off' : 'on',
7687                     events = {
7688                         remove: this.closeTooltip,
7689                         move: this._moveTooltip
7690                     };
7691                 if (!this._tooltip.options.permanent) {
7692                         events.mouseover = this._openTooltip;
7693                         events.mouseout = this.closeTooltip;
7694                         if (this._tooltip.options.sticky) {
7695                                 events.mousemove = this._moveTooltip;
7696                         }
7697                         if (L.Browser.touch) {
7698                                 events.click = this._openTooltip;
7699                         }
7700                 } else {
7701                         events.add = this._openTooltip;
7702                 }
7703                 this[onOff](events);
7704                 this._tooltipHandlersAdded = !remove;
7705         },
7706
7707         // @method openTooltip(latlng?: LatLng): this
7708         // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
7709         openTooltip: function (layer, latlng) {
7710                 if (!(layer instanceof L.Layer)) {
7711                         latlng = layer;
7712                         layer = this;
7713                 }
7714
7715                 if (layer instanceof L.FeatureGroup) {
7716                         for (var id in this._layers) {
7717                                 layer = this._layers[id];
7718                                 break;
7719                         }
7720                 }
7721
7722                 if (!latlng) {
7723                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7724                 }
7725
7726                 if (this._tooltip && this._map) {
7727
7728                         // set tooltip source to this layer
7729                         this._tooltip._source = layer;
7730
7731                         // update the tooltip (content, layout, ect...)
7732                         this._tooltip.update();
7733
7734                         // open the tooltip on the map
7735                         this._map.openTooltip(this._tooltip, latlng);
7736
7737                         // Tooltip container may not be defined if not permanent and never
7738                         // opened.
7739                         if (this._tooltip.options.interactive && this._tooltip._container) {
7740                                 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
7741                                 this.addInteractiveTarget(this._tooltip._container);
7742                         }
7743                 }
7744
7745                 return this;
7746         },
7747
7748         // @method closeTooltip(): this
7749         // Closes the tooltip bound to this layer if it is open.
7750         closeTooltip: function () {
7751                 if (this._tooltip) {
7752                         this._tooltip._close();
7753                         if (this._tooltip.options.interactive && this._tooltip._container) {
7754                                 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
7755                                 this.removeInteractiveTarget(this._tooltip._container);
7756                         }
7757                 }
7758                 return this;
7759         },
7760
7761         // @method toggleTooltip(): this
7762         // Opens or closes the tooltip bound to this layer depending on its current state.
7763         toggleTooltip: function (target) {
7764                 if (this._tooltip) {
7765                         if (this._tooltip._map) {
7766                                 this.closeTooltip();
7767                         } else {
7768                                 this.openTooltip(target);
7769                         }
7770                 }
7771                 return this;
7772         },
7773
7774         // @method isTooltipOpen(): boolean
7775         // Returns `true` if the tooltip bound to this layer is currently open.
7776         isTooltipOpen: function () {
7777                 return this._tooltip.isOpen();
7778         },
7779
7780         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
7781         // Sets the content of the tooltip bound to this layer.
7782         setTooltipContent: function (content) {
7783                 if (this._tooltip) {
7784                         this._tooltip.setContent(content);
7785                 }
7786                 return this;
7787         },
7788
7789         // @method getTooltip(): Tooltip
7790         // Returns the tooltip bound to this layer.
7791         getTooltip: function () {
7792                 return this._tooltip;
7793         },
7794
7795         _openTooltip: function (e) {
7796                 var layer = e.layer || e.target;
7797
7798                 if (!this._tooltip || !this._map) {
7799                         return;
7800                 }
7801                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
7802         },
7803
7804         _moveTooltip: function (e) {
7805                 var latlng = e.latlng, containerPoint, layerPoint;
7806                 if (this._tooltip.options.sticky && e.originalEvent) {
7807                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
7808                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
7809                         latlng = this._map.layerPointToLatLng(layerPoint);
7810                 }
7811                 this._tooltip.setLatLng(latlng);
7812         }
7813 });
7814
7815
7816
7817 /*
7818  * @class LayerGroup
7819  * @aka L.LayerGroup
7820  * @inherits Layer
7821  *
7822  * Used to group several layers and handle them as one. If you add it to the map,
7823  * any layers added or removed from the group will be added/removed on the map as
7824  * well. Extends `Layer`.
7825  *
7826  * @example
7827  *
7828  * ```js
7829  * L.layerGroup([marker1, marker2])
7830  *      .addLayer(polyline)
7831  *      .addTo(map);
7832  * ```
7833  */
7834
7835 L.LayerGroup = L.Layer.extend({
7836
7837         initialize: function (layers) {
7838                 this._layers = {};
7839
7840                 var i, len;
7841
7842                 if (layers) {
7843                         for (i = 0, len = layers.length; i < len; i++) {
7844                                 this.addLayer(layers[i]);
7845                         }
7846                 }
7847         },
7848
7849         // @method addLayer(layer: Layer): this
7850         // Adds the given layer to the group.
7851         addLayer: function (layer) {
7852                 var id = this.getLayerId(layer);
7853
7854                 this._layers[id] = layer;
7855
7856                 if (this._map) {
7857                         this._map.addLayer(layer);
7858                 }
7859
7860                 return this;
7861         },
7862
7863         // @method removeLayer(layer: Layer): this
7864         // Removes the given layer from the group.
7865         // @alternative
7866         // @method removeLayer(id: Number): this
7867         // Removes the layer with the given internal ID from the group.
7868         removeLayer: function (layer) {
7869                 var id = layer in this._layers ? layer : this.getLayerId(layer);
7870
7871                 if (this._map && this._layers[id]) {
7872                         this._map.removeLayer(this._layers[id]);
7873                 }
7874
7875                 delete this._layers[id];
7876
7877                 return this;
7878         },
7879
7880         // @method hasLayer(layer: Layer): Boolean
7881         // Returns `true` if the given layer is currently added to the group.
7882         hasLayer: function (layer) {
7883                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7884         },
7885
7886         // @method clearLayers(): this
7887         // Removes all the layers from the group.
7888         clearLayers: function () {
7889                 for (var i in this._layers) {
7890                         this.removeLayer(this._layers[i]);
7891                 }
7892                 return this;
7893         },
7894
7895         // @method invoke(methodName: String, …): this
7896         // Calls `methodName` on every layer contained in this group, passing any
7897         // additional parameters. Has no effect if the layers contained do not
7898         // implement `methodName`.
7899         invoke: function (methodName) {
7900                 var args = Array.prototype.slice.call(arguments, 1),
7901                     i, layer;
7902
7903                 for (i in this._layers) {
7904                         layer = this._layers[i];
7905
7906                         if (layer[methodName]) {
7907                                 layer[methodName].apply(layer, args);
7908                         }
7909                 }
7910
7911                 return this;
7912         },
7913
7914         onAdd: function (map) {
7915                 for (var i in this._layers) {
7916                         map.addLayer(this._layers[i]);
7917                 }
7918         },
7919
7920         onRemove: function (map) {
7921                 for (var i in this._layers) {
7922                         map.removeLayer(this._layers[i]);
7923                 }
7924         },
7925
7926         // @method eachLayer(fn: Function, context?: Object): this
7927         // Iterates over the layers of the group, optionally specifying context of the iterator function.
7928         // ```js
7929         // group.eachLayer(function (layer) {
7930         //      layer.bindPopup('Hello');
7931         // });
7932         // ```
7933         eachLayer: function (method, context) {
7934                 for (var i in this._layers) {
7935                         method.call(context, this._layers[i]);
7936                 }
7937                 return this;
7938         },
7939
7940         // @method getLayer(id: Number): Layer
7941         // Returns the layer with the given internal ID.
7942         getLayer: function (id) {
7943                 return this._layers[id];
7944         },
7945
7946         // @method getLayers(): Layer[]
7947         // Returns an array of all the layers added to the group.
7948         getLayers: function () {
7949                 var layers = [];
7950
7951                 for (var i in this._layers) {
7952                         layers.push(this._layers[i]);
7953                 }
7954                 return layers;
7955         },
7956
7957         // @method setZIndex(zIndex: Number): this
7958         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7959         setZIndex: function (zIndex) {
7960                 return this.invoke('setZIndex', zIndex);
7961         },
7962
7963         // @method getLayerId(layer: Layer): Number
7964         // Returns the internal ID for a layer
7965         getLayerId: function (layer) {
7966                 return L.stamp(layer);
7967         }
7968 });
7969
7970
7971 // @factory L.layerGroup(layers: Layer[])
7972 // Create a layer group, optionally given an initial set of layers.
7973 L.layerGroup = function (layers) {
7974         return new L.LayerGroup(layers);
7975 };
7976
7977
7978
7979 /*
7980  * @class FeatureGroup
7981  * @aka L.FeatureGroup
7982  * @inherits LayerGroup
7983  *
7984  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7985  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7986  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
7987  * handler, it will handle events from any of the layers. This includes mouse events
7988  * and custom events.
7989  *  * Has `layeradd` and `layerremove` events
7990  *
7991  * @example
7992  *
7993  * ```js
7994  * L.featureGroup([marker1, marker2, polyline])
7995  *      .bindPopup('Hello world!')
7996  *      .on('click', function() { alert('Clicked on a member of the group!'); })
7997  *      .addTo(map);
7998  * ```
7999  */
8000
8001 L.FeatureGroup = L.LayerGroup.extend({
8002
8003         addLayer: function (layer) {
8004                 if (this.hasLayer(layer)) {
8005                         return this;
8006                 }
8007
8008                 layer.addEventParent(this);
8009
8010                 L.LayerGroup.prototype.addLayer.call(this, layer);
8011
8012                 // @event layeradd: LayerEvent
8013                 // Fired when a layer is added to this `FeatureGroup`
8014                 return this.fire('layeradd', {layer: layer});
8015         },
8016
8017         removeLayer: function (layer) {
8018                 if (!this.hasLayer(layer)) {
8019                         return this;
8020                 }
8021                 if (layer in this._layers) {
8022                         layer = this._layers[layer];
8023                 }
8024
8025                 layer.removeEventParent(this);
8026
8027                 L.LayerGroup.prototype.removeLayer.call(this, layer);
8028
8029                 // @event layerremove: LayerEvent
8030                 // Fired when a layer is removed from this `FeatureGroup`
8031                 return this.fire('layerremove', {layer: layer});
8032         },
8033
8034         // @method setStyle(style: Path options): this
8035         // Sets the given path options to each layer of the group that has a `setStyle` method.
8036         setStyle: function (style) {
8037                 return this.invoke('setStyle', style);
8038         },
8039
8040         // @method bringToFront(): this
8041         // Brings the layer group to the top of all other layers
8042         bringToFront: function () {
8043                 return this.invoke('bringToFront');
8044         },
8045
8046         // @method bringToBack(): this
8047         // Brings the layer group to the top of all other layers
8048         bringToBack: function () {
8049                 return this.invoke('bringToBack');
8050         },
8051
8052         // @method getBounds(): LatLngBounds
8053         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
8054         getBounds: function () {
8055                 var bounds = new L.LatLngBounds();
8056
8057                 for (var id in this._layers) {
8058                         var layer = this._layers[id];
8059                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
8060                 }
8061                 return bounds;
8062         }
8063 });
8064
8065 // @factory L.featureGroup(layers: Layer[])
8066 // Create a feature group, optionally given an initial set of layers.
8067 L.featureGroup = function (layers) {
8068         return new L.FeatureGroup(layers);
8069 };
8070
8071
8072
8073 /*
8074  * @class Renderer
8075  * @inherits Layer
8076  * @aka L.Renderer
8077  *
8078  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
8079  * DOM container of the renderer, its bounds, and its zoom animation.
8080  *
8081  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
8082  * itself can be added or removed to the map. All paths use a renderer, which can
8083  * be implicit (the map will decide the type of renderer and use it automatically)
8084  * or explicit (using the [`renderer`](#path-renderer) option of the path).
8085  *
8086  * Do not use this class directly, use `SVG` and `Canvas` instead.
8087  *
8088  * @event update: Event
8089  * Fired when the renderer updates its bounds, center and zoom, for example when
8090  * its map has moved
8091  */
8092
8093 L.Renderer = L.Layer.extend({
8094
8095         // @section
8096         // @aka Renderer options
8097         options: {
8098                 // @option padding: Number = 0.1
8099                 // How much to extend the clip area around the map view (relative to its size)
8100                 // e.g. 0.1 would be 10% of map view in each direction
8101                 padding: 0.1
8102         },
8103
8104         initialize: function (options) {
8105                 L.setOptions(this, options);
8106                 L.stamp(this);
8107                 this._layers = this._layers || {};
8108         },
8109
8110         onAdd: function () {
8111                 if (!this._container) {
8112                         this._initContainer(); // defined by renderer implementations
8113
8114                         if (this._zoomAnimated) {
8115                                 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
8116                         }
8117                 }
8118
8119                 this.getPane().appendChild(this._container);
8120                 this._update();
8121                 this.on('update', this._updatePaths, this);
8122         },
8123
8124         onRemove: function () {
8125                 L.DomUtil.remove(this._container);
8126                 this.off('update', this._updatePaths, this);
8127         },
8128
8129         getEvents: function () {
8130                 var events = {
8131                         viewreset: this._reset,
8132                         zoom: this._onZoom,
8133                         moveend: this._update,
8134                         zoomend: this._onZoomEnd
8135                 };
8136                 if (this._zoomAnimated) {
8137                         events.zoomanim = this._onAnimZoom;
8138                 }
8139                 return events;
8140         },
8141
8142         _onAnimZoom: function (ev) {
8143                 this._updateTransform(ev.center, ev.zoom);
8144         },
8145
8146         _onZoom: function () {
8147                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
8148         },
8149
8150         _updateTransform: function (center, zoom) {
8151                 var scale = this._map.getZoomScale(zoom, this._zoom),
8152                     position = L.DomUtil.getPosition(this._container),
8153                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
8154                     currentCenterPoint = this._map.project(this._center, zoom),
8155                     destCenterPoint = this._map.project(center, zoom),
8156                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
8157
8158                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
8159
8160                 if (L.Browser.any3d) {
8161                         L.DomUtil.setTransform(this._container, topLeftOffset, scale);
8162                 } else {
8163                         L.DomUtil.setPosition(this._container, topLeftOffset);
8164                 }
8165         },
8166
8167         _reset: function () {
8168                 this._update();
8169                 this._updateTransform(this._center, this._zoom);
8170
8171                 for (var id in this._layers) {
8172                         this._layers[id]._reset();
8173                 }
8174         },
8175
8176         _onZoomEnd: function () {
8177                 for (var id in this._layers) {
8178                         this._layers[id]._project();
8179                 }
8180         },
8181
8182         _updatePaths: function () {
8183                 for (var id in this._layers) {
8184                         this._layers[id]._update();
8185                 }
8186         },
8187
8188         _update: function () {
8189                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
8190                 // Subclasses are responsible of firing the 'update' event.
8191                 var p = this.options.padding,
8192                     size = this._map.getSize(),
8193                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
8194
8195                 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
8196
8197                 this._center = this._map.getCenter();
8198                 this._zoom = this._map.getZoom();
8199         }
8200 });
8201
8202
8203 L.Map.include({
8204         // @namespace Map; @method getRenderer(layer: Path): Renderer
8205         // Returns the instance of `Renderer` that should be used to render the given
8206         // `Path`. It will ensure that the `renderer` options of the map and paths
8207         // are respected, and that the renderers do exist on the map.
8208         getRenderer: function (layer) {
8209                 // @namespace Path; @option renderer: Renderer
8210                 // Use this specific instance of `Renderer` for this path. Takes
8211                 // precedence over the map's [default renderer](#map-renderer).
8212                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
8213
8214                 if (!renderer) {
8215                         // @namespace Map; @option preferCanvas: Boolean = false
8216                         // Whether `Path`s should be rendered on a `Canvas` renderer.
8217                         // By default, all `Path`s are rendered in a `SVG` renderer.
8218                         renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
8219                 }
8220
8221                 if (!this.hasLayer(renderer)) {
8222                         this.addLayer(renderer);
8223                 }
8224                 return renderer;
8225         },
8226
8227         _getPaneRenderer: function (name) {
8228                 if (name === 'overlayPane' || name === undefined) {
8229                         return false;
8230                 }
8231
8232                 var renderer = this._paneRenderers[name];
8233                 if (renderer === undefined) {
8234                         renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
8235                         this._paneRenderers[name] = renderer;
8236                 }
8237                 return renderer;
8238         }
8239 });
8240
8241
8242
8243 /*
8244  * @class Path
8245  * @aka L.Path
8246  * @inherits Interactive layer
8247  *
8248  * An abstract class that contains options and constants shared between vector
8249  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8250  */
8251
8252 L.Path = L.Layer.extend({
8253
8254         // @section
8255         // @aka Path options
8256         options: {
8257                 // @option stroke: Boolean = true
8258                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8259                 stroke: true,
8260
8261                 // @option color: String = '#3388ff'
8262                 // Stroke color
8263                 color: '#3388ff',
8264
8265                 // @option weight: Number = 3
8266                 // Stroke width in pixels
8267                 weight: 3,
8268
8269                 // @option opacity: Number = 1.0
8270                 // Stroke opacity
8271                 opacity: 1,
8272
8273                 // @option lineCap: String= 'round'
8274                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8275                 lineCap: 'round',
8276
8277                 // @option lineJoin: String = 'round'
8278                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8279                 lineJoin: 'round',
8280
8281                 // @option dashArray: String = null
8282                 // 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).
8283                 dashArray: null,
8284
8285                 // @option dashOffset: String = null
8286                 // 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).
8287                 dashOffset: null,
8288
8289                 // @option fill: Boolean = depends
8290                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8291                 fill: false,
8292
8293                 // @option fillColor: String = *
8294                 // Fill color. Defaults to the value of the [`color`](#path-color) option
8295                 fillColor: null,
8296
8297                 // @option fillOpacity: Number = 0.2
8298                 // Fill opacity.
8299                 fillOpacity: 0.2,
8300
8301                 // @option fillRule: String = 'evenodd'
8302                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8303                 fillRule: 'evenodd',
8304
8305                 // className: '',
8306
8307                 // Option inherited from "Interactive layer" abstract class
8308                 interactive: true
8309         },
8310
8311         beforeAdd: function (map) {
8312                 // Renderer is set here because we need to call renderer.getEvents
8313                 // before this.getEvents.
8314                 this._renderer = map.getRenderer(this);
8315         },
8316
8317         onAdd: function () {
8318                 this._renderer._initPath(this);
8319                 this._reset();
8320                 this._renderer._addPath(this);
8321         },
8322
8323         onRemove: function () {
8324                 this._renderer._removePath(this);
8325         },
8326
8327         // @method redraw(): this
8328         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8329         redraw: function () {
8330                 if (this._map) {
8331                         this._renderer._updatePath(this);
8332                 }
8333                 return this;
8334         },
8335
8336         // @method setStyle(style: Path options): this
8337         // Changes the appearance of a Path based on the options in the `Path options` object.
8338         setStyle: function (style) {
8339                 L.setOptions(this, style);
8340                 if (this._renderer) {
8341                         this._renderer._updateStyle(this);
8342                 }
8343                 return this;
8344         },
8345
8346         // @method bringToFront(): this
8347         // Brings the layer to the top of all path layers.
8348         bringToFront: function () {
8349                 if (this._renderer) {
8350                         this._renderer._bringToFront(this);
8351                 }
8352                 return this;
8353         },
8354
8355         // @method bringToBack(): this
8356         // Brings the layer to the bottom of all path layers.
8357         bringToBack: function () {
8358                 if (this._renderer) {
8359                         this._renderer._bringToBack(this);
8360                 }
8361                 return this;
8362         },
8363
8364         getElement: function () {
8365                 return this._path;
8366         },
8367
8368         _reset: function () {
8369                 // defined in children classes
8370                 this._project();
8371                 this._update();
8372         },
8373
8374         _clickTolerance: function () {
8375                 // used when doing hit detection for Canvas layers
8376                 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
8377         }
8378 });
8379
8380
8381
8382 /*
8383  * @namespace LineUtil
8384  *
8385  * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
8386  */
8387
8388 L.LineUtil = {
8389
8390         // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
8391         // Improves rendering performance dramatically by lessening the number of points to draw.
8392
8393         // @function simplify(points: Point[], tolerance: Number): Point[]
8394         // Dramatically reduces the number of points in a polyline while retaining
8395         // its shape and returns a new array of simplified points, using the
8396         // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
8397         // Used for a huge performance boost when processing/displaying Leaflet polylines for
8398         // each zoom level and also reducing visual noise. tolerance affects the amount of
8399         // simplification (lesser value means higher quality but slower and with more points).
8400         // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
8401         simplify: function (points, tolerance) {
8402                 if (!tolerance || !points.length) {
8403                         return points.slice();
8404                 }
8405
8406                 var sqTolerance = tolerance * tolerance;
8407
8408                 // stage 1: vertex reduction
8409                 points = this._reducePoints(points, sqTolerance);
8410
8411                 // stage 2: Douglas-Peucker simplification
8412                 points = this._simplifyDP(points, sqTolerance);
8413
8414                 return points;
8415         },
8416
8417         // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
8418         // Returns the distance between point `p` and segment `p1` to `p2`.
8419         pointToSegmentDistance:  function (p, p1, p2) {
8420                 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
8421         },
8422
8423         // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
8424         // Returns the closest point from a point `p` on a segment `p1` to `p2`.
8425         closestPointOnSegment: function (p, p1, p2) {
8426                 return this._sqClosestPointOnSegment(p, p1, p2);
8427         },
8428
8429         // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
8430         _simplifyDP: function (points, sqTolerance) {
8431
8432                 var len = points.length,
8433                     ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
8434                     markers = new ArrayConstructor(len);
8435
8436                 markers[0] = markers[len - 1] = 1;
8437
8438                 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
8439
8440                 var i,
8441                     newPoints = [];
8442
8443                 for (i = 0; i < len; i++) {
8444                         if (markers[i]) {
8445                                 newPoints.push(points[i]);
8446                         }
8447                 }
8448
8449                 return newPoints;
8450         },
8451
8452         _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
8453
8454                 var maxSqDist = 0,
8455                     index, i, sqDist;
8456
8457                 for (i = first + 1; i <= last - 1; i++) {
8458                         sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
8459
8460                         if (sqDist > maxSqDist) {
8461                                 index = i;
8462                                 maxSqDist = sqDist;
8463                         }
8464                 }
8465
8466                 if (maxSqDist > sqTolerance) {
8467                         markers[index] = 1;
8468
8469                         this._simplifyDPStep(points, markers, sqTolerance, first, index);
8470                         this._simplifyDPStep(points, markers, sqTolerance, index, last);
8471                 }
8472         },
8473
8474         // reduce points that are too close to each other to a single point
8475         _reducePoints: function (points, sqTolerance) {
8476                 var reducedPoints = [points[0]];
8477
8478                 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
8479                         if (this._sqDist(points[i], points[prev]) > sqTolerance) {
8480                                 reducedPoints.push(points[i]);
8481                                 prev = i;
8482                         }
8483                 }
8484                 if (prev < len - 1) {
8485                         reducedPoints.push(points[len - 1]);
8486                 }
8487                 return reducedPoints;
8488         },
8489
8490
8491         // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
8492         // Clips the segment a to b by rectangular bounds with the
8493         // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
8494         // (modifying the segment points directly!). Used by Leaflet to only show polyline
8495         // points that are on the screen or near, increasing performance.
8496         clipSegment: function (a, b, bounds, useLastCode, round) {
8497                 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
8498                     codeB = this._getBitCode(b, bounds),
8499
8500                     codeOut, p, newCode;
8501
8502                 // save 2nd code to avoid calculating it on the next segment
8503                 this._lastCode = codeB;
8504
8505                 while (true) {
8506                         // if a,b is inside the clip window (trivial accept)
8507                         if (!(codeA | codeB)) {
8508                                 return [a, b];
8509                         }
8510
8511                         // if a,b is outside the clip window (trivial reject)
8512                         if (codeA & codeB) {
8513                                 return false;
8514                         }
8515
8516                         // other cases
8517                         codeOut = codeA || codeB;
8518                         p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
8519                         newCode = this._getBitCode(p, bounds);
8520
8521                         if (codeOut === codeA) {
8522                                 a = p;
8523                                 codeA = newCode;
8524                         } else {
8525                                 b = p;
8526                                 codeB = newCode;
8527                         }
8528                 }
8529         },
8530
8531         _getEdgeIntersection: function (a, b, code, bounds, round) {
8532                 var dx = b.x - a.x,
8533                     dy = b.y - a.y,
8534                     min = bounds.min,
8535                     max = bounds.max,
8536                     x, y;
8537
8538                 if (code & 8) { // top
8539                         x = a.x + dx * (max.y - a.y) / dy;
8540                         y = max.y;
8541
8542                 } else if (code & 4) { // bottom
8543                         x = a.x + dx * (min.y - a.y) / dy;
8544                         y = min.y;
8545
8546                 } else if (code & 2) { // right
8547                         x = max.x;
8548                         y = a.y + dy * (max.x - a.x) / dx;
8549
8550                 } else if (code & 1) { // left
8551                         x = min.x;
8552                         y = a.y + dy * (min.x - a.x) / dx;
8553                 }
8554
8555                 return new L.Point(x, y, round);
8556         },
8557
8558         _getBitCode: function (p, bounds) {
8559                 var code = 0;
8560
8561                 if (p.x < bounds.min.x) { // left
8562                         code |= 1;
8563                 } else if (p.x > bounds.max.x) { // right
8564                         code |= 2;
8565                 }
8566
8567                 if (p.y < bounds.min.y) { // bottom
8568                         code |= 4;
8569                 } else if (p.y > bounds.max.y) { // top
8570                         code |= 8;
8571                 }
8572
8573                 return code;
8574         },
8575
8576         // square distance (to avoid unnecessary Math.sqrt calls)
8577         _sqDist: function (p1, p2) {
8578                 var dx = p2.x - p1.x,
8579                     dy = p2.y - p1.y;
8580                 return dx * dx + dy * dy;
8581         },
8582
8583         // return closest point on segment or distance to that point
8584         _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
8585                 var x = p1.x,
8586                     y = p1.y,
8587                     dx = p2.x - x,
8588                     dy = p2.y - y,
8589                     dot = dx * dx + dy * dy,
8590                     t;
8591
8592                 if (dot > 0) {
8593                         t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
8594
8595                         if (t > 1) {
8596                                 x = p2.x;
8597                                 y = p2.y;
8598                         } else if (t > 0) {
8599                                 x += dx * t;
8600                                 y += dy * t;
8601                         }
8602                 }
8603
8604                 dx = p.x - x;
8605                 dy = p.y - y;
8606
8607                 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
8608         }
8609 };
8610
8611
8612
8613 /*
8614  * @class Polyline
8615  * @aka L.Polyline
8616  * @inherits Path
8617  *
8618  * A class for drawing polyline overlays on a map. Extends `Path`.
8619  *
8620  * @example
8621  *
8622  * ```js
8623  * // create a red polyline from an array of LatLng points
8624  * var latlngs = [
8625  *      [-122.68, 45.51],
8626  *      [-122.43, 37.77],
8627  *      [-118.2, 34.04]
8628  * ];
8629  *
8630  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8631  *
8632  * // zoom the map to the polyline
8633  * map.fitBounds(polyline.getBounds());
8634  * ```
8635  *
8636  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8637  *
8638  * ```js
8639  * // create a red polyline from an array of arrays of LatLng points
8640  * var latlngs = [
8641  *      [[-122.68, 45.51],
8642  *       [-122.43, 37.77],
8643  *       [-118.2, 34.04]],
8644  *      [[-73.91, 40.78],
8645  *       [-87.62, 41.83],
8646  *       [-96.72, 32.76]]
8647  * ];
8648  * ```
8649  */
8650
8651 L.Polyline = L.Path.extend({
8652
8653         // @section
8654         // @aka Polyline options
8655         options: {
8656                 // @option smoothFactor: Number = 1.0
8657                 // How much to simplify the polyline on each zoom level. More means
8658                 // better performance and smoother look, and less means more accurate representation.
8659                 smoothFactor: 1.0,
8660
8661                 // @option noClip: Boolean = false
8662                 // Disable polyline clipping.
8663                 noClip: false
8664         },
8665
8666         initialize: function (latlngs, options) {
8667                 L.setOptions(this, options);
8668                 this._setLatLngs(latlngs);
8669         },
8670
8671         // @method getLatLngs(): LatLng[]
8672         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8673         getLatLngs: function () {
8674                 return this._latlngs;
8675         },
8676
8677         // @method setLatLngs(latlngs: LatLng[]): this
8678         // Replaces all the points in the polyline with the given array of geographical points.
8679         setLatLngs: function (latlngs) {
8680                 this._setLatLngs(latlngs);
8681                 return this.redraw();
8682         },
8683
8684         // @method isEmpty(): Boolean
8685         // Returns `true` if the Polyline has no LatLngs.
8686         isEmpty: function () {
8687                 return !this._latlngs.length;
8688         },
8689
8690         closestLayerPoint: function (p) {
8691                 var minDistance = Infinity,
8692                     minPoint = null,
8693                     closest = L.LineUtil._sqClosestPointOnSegment,
8694                     p1, p2;
8695
8696                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8697                         var points = this._parts[j];
8698
8699                         for (var i = 1, len = points.length; i < len; i++) {
8700                                 p1 = points[i - 1];
8701                                 p2 = points[i];
8702
8703                                 var sqDist = closest(p, p1, p2, true);
8704
8705                                 if (sqDist < minDistance) {
8706                                         minDistance = sqDist;
8707                                         minPoint = closest(p, p1, p2);
8708                                 }
8709                         }
8710                 }
8711                 if (minPoint) {
8712                         minPoint.distance = Math.sqrt(minDistance);
8713                 }
8714                 return minPoint;
8715         },
8716
8717         // @method getCenter(): LatLng
8718         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8719         getCenter: function () {
8720                 // throws error when not yet added to map as this center calculation requires projected coordinates
8721                 if (!this._map) {
8722                         throw new Error('Must add layer to map before using getCenter()');
8723                 }
8724
8725                 var i, halfDist, segDist, dist, p1, p2, ratio,
8726                     points = this._rings[0],
8727                     len = points.length;
8728
8729                 if (!len) { return null; }
8730
8731                 // polyline centroid algorithm; only uses the first ring if there are multiple
8732
8733                 for (i = 0, halfDist = 0; i < len - 1; i++) {
8734                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
8735                 }
8736
8737                 // The line is so small in the current view that all points are on the same pixel.
8738                 if (halfDist === 0) {
8739                         return this._map.layerPointToLatLng(points[0]);
8740                 }
8741
8742                 for (i = 0, dist = 0; i < len - 1; i++) {
8743                         p1 = points[i];
8744                         p2 = points[i + 1];
8745                         segDist = p1.distanceTo(p2);
8746                         dist += segDist;
8747
8748                         if (dist > halfDist) {
8749                                 ratio = (dist - halfDist) / segDist;
8750                                 return this._map.layerPointToLatLng([
8751                                         p2.x - ratio * (p2.x - p1.x),
8752                                         p2.y - ratio * (p2.y - p1.y)
8753                                 ]);
8754                         }
8755                 }
8756         },
8757
8758         // @method getBounds(): LatLngBounds
8759         // Returns the `LatLngBounds` of the path.
8760         getBounds: function () {
8761                 return this._bounds;
8762         },
8763
8764         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8765         // Adds a given point to the polyline. By default, adds to the first ring of
8766         // the polyline in case of a multi-polyline, but can be overridden by passing
8767         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8768         addLatLng: function (latlng, latlngs) {
8769                 latlngs = latlngs || this._defaultShape();
8770                 latlng = L.latLng(latlng);
8771                 latlngs.push(latlng);
8772                 this._bounds.extend(latlng);
8773                 return this.redraw();
8774         },
8775
8776         _setLatLngs: function (latlngs) {
8777                 this._bounds = new L.LatLngBounds();
8778                 this._latlngs = this._convertLatLngs(latlngs);
8779         },
8780
8781         _defaultShape: function () {
8782                 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
8783         },
8784
8785         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8786         _convertLatLngs: function (latlngs) {
8787                 var result = [],
8788                     flat = L.Polyline._flat(latlngs);
8789
8790                 for (var i = 0, len = latlngs.length; i < len; i++) {
8791                         if (flat) {
8792                                 result[i] = L.latLng(latlngs[i]);
8793                                 this._bounds.extend(result[i]);
8794                         } else {
8795                                 result[i] = this._convertLatLngs(latlngs[i]);
8796                         }
8797                 }
8798
8799                 return result;
8800         },
8801
8802         _project: function () {
8803                 var pxBounds = new L.Bounds();
8804                 this._rings = [];
8805                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8806
8807                 var w = this._clickTolerance(),
8808                     p = new L.Point(w, w);
8809
8810                 if (this._bounds.isValid() && pxBounds.isValid()) {
8811                         pxBounds.min._subtract(p);
8812                         pxBounds.max._add(p);
8813                         this._pxBounds = pxBounds;
8814                 }
8815         },
8816
8817         // recursively turns latlngs into a set of rings with projected coordinates
8818         _projectLatlngs: function (latlngs, result, projectedBounds) {
8819                 var flat = latlngs[0] instanceof L.LatLng,
8820                     len = latlngs.length,
8821                     i, ring;
8822
8823                 if (flat) {
8824                         ring = [];
8825                         for (i = 0; i < len; i++) {
8826                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8827                                 projectedBounds.extend(ring[i]);
8828                         }
8829                         result.push(ring);
8830                 } else {
8831                         for (i = 0; i < len; i++) {
8832                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8833                         }
8834                 }
8835         },
8836
8837         // clip polyline by renderer bounds so that we have less to render for performance
8838         _clipPoints: function () {
8839                 var bounds = this._renderer._bounds;
8840
8841                 this._parts = [];
8842                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8843                         return;
8844                 }
8845
8846                 if (this.options.noClip) {
8847                         this._parts = this._rings;
8848                         return;
8849                 }
8850
8851                 var parts = this._parts,
8852                     i, j, k, len, len2, segment, points;
8853
8854                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8855                         points = this._rings[i];
8856
8857                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8858                                 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8859
8860                                 if (!segment) { continue; }
8861
8862                                 parts[k] = parts[k] || [];
8863                                 parts[k].push(segment[0]);
8864
8865                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8866                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8867                                         parts[k].push(segment[1]);
8868                                         k++;
8869                                 }
8870                         }
8871                 }
8872         },
8873
8874         // simplify each clipped part of the polyline for performance
8875         _simplifyPoints: function () {
8876                 var parts = this._parts,
8877                     tolerance = this.options.smoothFactor;
8878
8879                 for (var i = 0, len = parts.length; i < len; i++) {
8880                         parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8881                 }
8882         },
8883
8884         _update: function () {
8885                 if (!this._map) { return; }
8886
8887                 this._clipPoints();
8888                 this._simplifyPoints();
8889                 this._updatePath();
8890         },
8891
8892         _updatePath: function () {
8893                 this._renderer._updatePoly(this);
8894         }
8895 });
8896
8897 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8898 // Instantiates a polyline object given an array of geographical points and
8899 // optionally an options object. You can create a `Polyline` object with
8900 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8901 // of geographic points.
8902 L.polyline = function (latlngs, options) {
8903         return new L.Polyline(latlngs, options);
8904 };
8905
8906 L.Polyline._flat = function (latlngs) {
8907         // true if it's a flat array of latlngs; false if nested
8908         return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8909 };
8910
8911
8912
8913 /*
8914  * @namespace PolyUtil
8915  * Various utility functions for polygon geometries.
8916  */
8917
8918 L.PolyUtil = {};
8919
8920 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8921  * 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)).
8922  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8923  * performance. Note that polygon points needs different algorithm for clipping
8924  * than polyline, so there's a seperate method for it.
8925  */
8926 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8927         var clippedPoints,
8928             edges = [1, 4, 2, 8],
8929             i, j, k,
8930             a, b,
8931             len, edge, p,
8932             lu = L.LineUtil;
8933
8934         for (i = 0, len = points.length; i < len; i++) {
8935                 points[i]._code = lu._getBitCode(points[i], bounds);
8936         }
8937
8938         // for each edge (left, bottom, right, top)
8939         for (k = 0; k < 4; k++) {
8940                 edge = edges[k];
8941                 clippedPoints = [];
8942
8943                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8944                         a = points[i];
8945                         b = points[j];
8946
8947                         // if a is inside the clip window
8948                         if (!(a._code & edge)) {
8949                                 // if b is outside the clip window (a->b goes out of screen)
8950                                 if (b._code & edge) {
8951                                         p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8952                                         p._code = lu._getBitCode(p, bounds);
8953                                         clippedPoints.push(p);
8954                                 }
8955                                 clippedPoints.push(a);
8956
8957                         // else if b is inside the clip window (a->b enters the screen)
8958                         } else if (!(b._code & edge)) {
8959                                 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8960                                 p._code = lu._getBitCode(p, bounds);
8961                                 clippedPoints.push(p);
8962                         }
8963                 }
8964                 points = clippedPoints;
8965         }
8966
8967         return points;
8968 };
8969
8970
8971
8972 /*
8973  * @class Polygon
8974  * @aka L.Polygon
8975  * @inherits Polyline
8976  *
8977  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8978  *
8979  * 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.
8980  *
8981  *
8982  * @example
8983  *
8984  * ```js
8985  * // create a red polygon from an array of LatLng points
8986  * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
8987  *
8988  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8989  *
8990  * // zoom the map to the polygon
8991  * map.fitBounds(polygon.getBounds());
8992  * ```
8993  *
8994  * 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:
8995  *
8996  * ```js
8997  * var latlngs = [
8998  *   [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8999  *   [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
9000  * ];
9001  * ```
9002  *
9003  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
9004  *
9005  * ```js
9006  * var latlngs = [
9007  *   [ // first polygon
9008  *     [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
9009  *     [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
9010  *   ],
9011  *   [ // second polygon
9012  *     [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
9013  *   ]
9014  * ];
9015  * ```
9016  */
9017
9018 L.Polygon = L.Polyline.extend({
9019
9020         options: {
9021                 fill: true
9022         },
9023
9024         isEmpty: function () {
9025                 return !this._latlngs.length || !this._latlngs[0].length;
9026         },
9027
9028         getCenter: function () {
9029                 // throws error when not yet added to map as this center calculation requires projected coordinates
9030                 if (!this._map) {
9031                         throw new Error('Must add layer to map before using getCenter()');
9032                 }
9033
9034                 var i, j, p1, p2, f, area, x, y, center,
9035                     points = this._rings[0],
9036                     len = points.length;
9037
9038                 if (!len) { return null; }
9039
9040                 // polygon centroid algorithm; only uses the first ring if there are multiple
9041
9042                 area = x = y = 0;
9043
9044                 for (i = 0, j = len - 1; i < len; j = i++) {
9045                         p1 = points[i];
9046                         p2 = points[j];
9047
9048                         f = p1.y * p2.x - p2.y * p1.x;
9049                         x += (p1.x + p2.x) * f;
9050                         y += (p1.y + p2.y) * f;
9051                         area += f * 3;
9052                 }
9053
9054                 if (area === 0) {
9055                         // Polygon is so small that all points are on same pixel.
9056                         center = points[0];
9057                 } else {
9058                         center = [x / area, y / area];
9059                 }
9060                 return this._map.layerPointToLatLng(center);
9061         },
9062
9063         _convertLatLngs: function (latlngs) {
9064                 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
9065                     len = result.length;
9066
9067                 // remove last point if it equals first one
9068                 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
9069                         result.pop();
9070                 }
9071                 return result;
9072         },
9073
9074         _setLatLngs: function (latlngs) {
9075                 L.Polyline.prototype._setLatLngs.call(this, latlngs);
9076                 if (L.Polyline._flat(this._latlngs)) {
9077                         this._latlngs = [this._latlngs];
9078                 }
9079         },
9080
9081         _defaultShape: function () {
9082                 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
9083         },
9084
9085         _clipPoints: function () {
9086                 // polygons need a different clipping algorithm so we redefine that
9087
9088                 var bounds = this._renderer._bounds,
9089                     w = this.options.weight,
9090                     p = new L.Point(w, w);
9091
9092                 // increase clip padding by stroke width to avoid stroke on clip edges
9093                 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
9094
9095                 this._parts = [];
9096                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
9097                         return;
9098                 }
9099
9100                 if (this.options.noClip) {
9101                         this._parts = this._rings;
9102                         return;
9103                 }
9104
9105                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
9106                         clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
9107                         if (clipped.length) {
9108                                 this._parts.push(clipped);
9109                         }
9110                 }
9111         },
9112
9113         _updatePath: function () {
9114                 this._renderer._updatePoly(this, true);
9115         }
9116 });
9117
9118
9119 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
9120 L.polygon = function (latlngs, options) {
9121         return new L.Polygon(latlngs, options);
9122 };
9123
9124
9125
9126 /*
9127  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
9128  */
9129
9130 /*
9131  * @class Rectangle
9132  * @aka L.Retangle
9133  * @inherits Polygon
9134  *
9135  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
9136  *
9137  * @example
9138  *
9139  * ```js
9140  * // define rectangle geographical bounds
9141  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
9142  *
9143  * // create an orange rectangle
9144  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
9145  *
9146  * // zoom the map to the rectangle bounds
9147  * map.fitBounds(bounds);
9148  * ```
9149  *
9150  */
9151
9152
9153 L.Rectangle = L.Polygon.extend({
9154         initialize: function (latLngBounds, options) {
9155                 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
9156         },
9157
9158         // @method setBounds(latLngBounds: LatLngBounds): this
9159         // Redraws the rectangle with the passed bounds.
9160         setBounds: function (latLngBounds) {
9161                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
9162         },
9163
9164         _boundsToLatLngs: function (latLngBounds) {
9165                 latLngBounds = L.latLngBounds(latLngBounds);
9166                 return [
9167                         latLngBounds.getSouthWest(),
9168                         latLngBounds.getNorthWest(),
9169                         latLngBounds.getNorthEast(),
9170                         latLngBounds.getSouthEast()
9171                 ];
9172         }
9173 });
9174
9175
9176 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
9177 L.rectangle = function (latLngBounds, options) {
9178         return new L.Rectangle(latLngBounds, options);
9179 };
9180
9181
9182
9183 /*
9184  * @class CircleMarker
9185  * @aka L.CircleMarker
9186  * @inherits Path
9187  *
9188  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
9189  */
9190
9191 L.CircleMarker = L.Path.extend({
9192
9193         // @section
9194         // @aka CircleMarker options
9195         options: {
9196                 fill: true,
9197
9198                 // @option radius: Number = 10
9199                 // Radius of the circle marker, in pixels
9200                 radius: 10
9201         },
9202
9203         initialize: function (latlng, options) {
9204                 L.setOptions(this, options);
9205                 this._latlng = L.latLng(latlng);
9206                 this._radius = this.options.radius;
9207         },
9208
9209         // @method setLatLng(latLng: LatLng): this
9210         // Sets the position of a circle marker to a new location.
9211         setLatLng: function (latlng) {
9212                 this._latlng = L.latLng(latlng);
9213                 this.redraw();
9214                 return this.fire('move', {latlng: this._latlng});
9215         },
9216
9217         // @method getLatLng(): LatLng
9218         // Returns the current geographical position of the circle marker
9219         getLatLng: function () {
9220                 return this._latlng;
9221         },
9222
9223         // @method setRadius(radius: Number): this
9224         // Sets the radius of a circle marker. Units are in pixels.
9225         setRadius: function (radius) {
9226                 this.options.radius = this._radius = radius;
9227                 return this.redraw();
9228         },
9229
9230         // @method getRadius(): Number
9231         // Returns the current radius of the circle
9232         getRadius: function () {
9233                 return this._radius;
9234         },
9235
9236         setStyle : function (options) {
9237                 var radius = options && options.radius || this._radius;
9238                 L.Path.prototype.setStyle.call(this, options);
9239                 this.setRadius(radius);
9240                 return this;
9241         },
9242
9243         _project: function () {
9244                 this._point = this._map.latLngToLayerPoint(this._latlng);
9245                 this._updateBounds();
9246         },
9247
9248         _updateBounds: function () {
9249                 var r = this._radius,
9250                     r2 = this._radiusY || r,
9251                     w = this._clickTolerance(),
9252                     p = [r + w, r2 + w];
9253                 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
9254         },
9255
9256         _update: function () {
9257                 if (this._map) {
9258                         this._updatePath();
9259                 }
9260         },
9261
9262         _updatePath: function () {
9263                 this._renderer._updateCircle(this);
9264         },
9265
9266         _empty: function () {
9267                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
9268         }
9269 });
9270
9271
9272 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
9273 // Instantiates a circle marker object given a geographical point, and an optional options object.
9274 L.circleMarker = function (latlng, options) {
9275         return new L.CircleMarker(latlng, options);
9276 };
9277
9278
9279
9280 /*
9281  * @class Circle
9282  * @aka L.Circle
9283  * @inherits CircleMarker
9284  *
9285  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
9286  *
9287  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
9288  *
9289  * @example
9290  *
9291  * ```js
9292  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
9293  * ```
9294  */
9295
9296 L.Circle = L.CircleMarker.extend({
9297
9298         initialize: function (latlng, options, legacyOptions) {
9299                 if (typeof options === 'number') {
9300                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
9301                         options = L.extend({}, legacyOptions, {radius: options});
9302                 }
9303                 L.setOptions(this, options);
9304                 this._latlng = L.latLng(latlng);
9305
9306                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
9307
9308                 // @section
9309                 // @aka Circle options
9310                 // @option radius: Number; Radius of the circle, in meters.
9311                 this._mRadius = this.options.radius;
9312         },
9313
9314         // @method setRadius(radius: Number): this
9315         // Sets the radius of a circle. Units are in meters.
9316         setRadius: function (radius) {
9317                 this._mRadius = radius;
9318                 return this.redraw();
9319         },
9320
9321         // @method getRadius(): Number
9322         // Returns the current radius of a circle. Units are in meters.
9323         getRadius: function () {
9324                 return this._mRadius;
9325         },
9326
9327         // @method getBounds(): LatLngBounds
9328         // Returns the `LatLngBounds` of the path.
9329         getBounds: function () {
9330                 var half = [this._radius, this._radiusY || this._radius];
9331
9332                 return new L.LatLngBounds(
9333                         this._map.layerPointToLatLng(this._point.subtract(half)),
9334                         this._map.layerPointToLatLng(this._point.add(half)));
9335         },
9336
9337         setStyle: L.Path.prototype.setStyle,
9338
9339         _project: function () {
9340
9341                 var lng = this._latlng.lng,
9342                     lat = this._latlng.lat,
9343                     map = this._map,
9344                     crs = map.options.crs;
9345
9346                 if (crs.distance === L.CRS.Earth.distance) {
9347                         var d = Math.PI / 180,
9348                             latR = (this._mRadius / L.CRS.Earth.R) / d,
9349                             top = map.project([lat + latR, lng]),
9350                             bottom = map.project([lat - latR, lng]),
9351                             p = top.add(bottom).divideBy(2),
9352                             lat2 = map.unproject(p).lat,
9353                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
9354                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
9355
9356                         if (isNaN(lngR) || lngR === 0) {
9357                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
9358                         }
9359
9360                         this._point = p.subtract(map.getPixelOrigin());
9361                         this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
9362                         this._radiusY = Math.max(Math.round(p.y - top.y), 1);
9363
9364                 } else {
9365                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
9366
9367                         this._point = map.latLngToLayerPoint(this._latlng);
9368                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
9369                 }
9370
9371                 this._updateBounds();
9372         }
9373 });
9374
9375 // @factory L.circle(latlng: LatLng, options?: Circle options)
9376 // Instantiates a circle object given a geographical point, and an options object
9377 // which contains the circle radius.
9378 // @alternative
9379 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
9380 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
9381 // Do not use in new applications or plugins.
9382 L.circle = function (latlng, options, legacyOptions) {
9383         return new L.Circle(latlng, options, legacyOptions);
9384 };
9385
9386
9387
9388 /*
9389  * @class SVG
9390  * @inherits Renderer
9391  * @aka L.SVG
9392  *
9393  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
9394  * Inherits `Renderer`.
9395  *
9396  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
9397  * available in all web browsers, notably Android 2.x and 3.x.
9398  *
9399  * Although SVG is not available on IE7 and IE8, these browsers support
9400  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
9401  * (a now deprecated technology), and the SVG renderer will fall back to VML in
9402  * this case.
9403  *
9404  * @example
9405  *
9406  * Use SVG by default for all paths in the map:
9407  *
9408  * ```js
9409  * var map = L.map('map', {
9410  *      renderer: L.svg()
9411  * });
9412  * ```
9413  *
9414  * Use a SVG renderer with extra padding for specific vector geometries:
9415  *
9416  * ```js
9417  * var map = L.map('map');
9418  * var myRenderer = L.svg({ padding: 0.5 });
9419  * var line = L.polyline( coordinates, { renderer: myRenderer } );
9420  * var circle = L.circle( center, { renderer: myRenderer } );
9421  * ```
9422  */
9423
9424 L.SVG = L.Renderer.extend({
9425
9426         getEvents: function () {
9427                 var events = L.Renderer.prototype.getEvents.call(this);
9428                 events.zoomstart = this._onZoomStart;
9429                 return events;
9430         },
9431
9432         _initContainer: function () {
9433                 this._container = L.SVG.create('svg');
9434
9435                 // makes it possible to click through svg root; we'll reset it back in individual paths
9436                 this._container.setAttribute('pointer-events', 'none');
9437
9438                 this._rootGroup = L.SVG.create('g');
9439                 this._container.appendChild(this._rootGroup);
9440         },
9441
9442         _onZoomStart: function () {
9443                 // Drag-then-pinch interactions might mess up the center and zoom.
9444                 // In this case, the easiest way to prevent this is re-do the renderer
9445                 //   bounds and padding when the zooming starts.
9446                 this._update();
9447         },
9448
9449         _update: function () {
9450                 if (this._map._animatingZoom && this._bounds) { return; }
9451
9452                 L.Renderer.prototype._update.call(this);
9453
9454                 var b = this._bounds,
9455                     size = b.getSize(),
9456                     container = this._container;
9457
9458                 // set size of svg-container if changed
9459                 if (!this._svgSize || !this._svgSize.equals(size)) {
9460                         this._svgSize = size;
9461                         container.setAttribute('width', size.x);
9462                         container.setAttribute('height', size.y);
9463                 }
9464
9465                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
9466                 L.DomUtil.setPosition(container, b.min);
9467                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
9468
9469                 this.fire('update');
9470         },
9471
9472         // methods below are called by vector layers implementations
9473
9474         _initPath: function (layer) {
9475                 var path = layer._path = L.SVG.create('path');
9476
9477                 // @namespace Path
9478                 // @option className: String = null
9479                 // Custom class name set on an element. Only for SVG renderer.
9480                 if (layer.options.className) {
9481                         L.DomUtil.addClass(path, layer.options.className);
9482                 }
9483
9484                 if (layer.options.interactive) {
9485                         L.DomUtil.addClass(path, 'leaflet-interactive');
9486                 }
9487
9488                 this._updateStyle(layer);
9489                 this._layers[L.stamp(layer)] = layer;
9490         },
9491
9492         _addPath: function (layer) {
9493                 this._rootGroup.appendChild(layer._path);
9494                 layer.addInteractiveTarget(layer._path);
9495         },
9496
9497         _removePath: function (layer) {
9498                 L.DomUtil.remove(layer._path);
9499                 layer.removeInteractiveTarget(layer._path);
9500                 delete this._layers[L.stamp(layer)];
9501         },
9502
9503         _updatePath: function (layer) {
9504                 layer._project();
9505                 layer._update();
9506         },
9507
9508         _updateStyle: function (layer) {
9509                 var path = layer._path,
9510                     options = layer.options;
9511
9512                 if (!path) { return; }
9513
9514                 if (options.stroke) {
9515                         path.setAttribute('stroke', options.color);
9516                         path.setAttribute('stroke-opacity', options.opacity);
9517                         path.setAttribute('stroke-width', options.weight);
9518                         path.setAttribute('stroke-linecap', options.lineCap);
9519                         path.setAttribute('stroke-linejoin', options.lineJoin);
9520
9521                         if (options.dashArray) {
9522                                 path.setAttribute('stroke-dasharray', options.dashArray);
9523                         } else {
9524                                 path.removeAttribute('stroke-dasharray');
9525                         }
9526
9527                         if (options.dashOffset) {
9528                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
9529                         } else {
9530                                 path.removeAttribute('stroke-dashoffset');
9531                         }
9532                 } else {
9533                         path.setAttribute('stroke', 'none');
9534                 }
9535
9536                 if (options.fill) {
9537                         path.setAttribute('fill', options.fillColor || options.color);
9538                         path.setAttribute('fill-opacity', options.fillOpacity);
9539                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
9540                 } else {
9541                         path.setAttribute('fill', 'none');
9542                 }
9543         },
9544
9545         _updatePoly: function (layer, closed) {
9546                 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
9547         },
9548
9549         _updateCircle: function (layer) {
9550                 var p = layer._point,
9551                     r = layer._radius,
9552                     r2 = layer._radiusY || r,
9553                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
9554
9555                 // drawing a circle with two half-arcs
9556                 var d = layer._empty() ? 'M0 0' :
9557                                 'M' + (p.x - r) + ',' + p.y +
9558                                 arc + (r * 2) + ',0 ' +
9559                                 arc + (-r * 2) + ',0 ';
9560
9561                 this._setPath(layer, d);
9562         },
9563
9564         _setPath: function (layer, path) {
9565                 layer._path.setAttribute('d', path);
9566         },
9567
9568         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
9569         _bringToFront: function (layer) {
9570                 L.DomUtil.toFront(layer._path);
9571         },
9572
9573         _bringToBack: function (layer) {
9574                 L.DomUtil.toBack(layer._path);
9575         }
9576 });
9577
9578
9579 // @namespace SVG; @section
9580 // There are several static functions which can be called without instantiating L.SVG:
9581 L.extend(L.SVG, {
9582         // @function create(name: String): SVGElement
9583         // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
9584         // corresponding to the class name passed. For example, using 'line' will return
9585         // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
9586         create: function (name) {
9587                 return document.createElementNS('http://www.w3.org/2000/svg', name);
9588         },
9589
9590         // @function pointsToPath(rings: Point[], closed: Boolean): String
9591         // Generates a SVG path string for multiple rings, with each ring turning
9592         // into "M..L..L.." instructions
9593         pointsToPath: function (rings, closed) {
9594                 var str = '',
9595                     i, j, len, len2, points, p;
9596
9597                 for (i = 0, len = rings.length; i < len; i++) {
9598                         points = rings[i];
9599
9600                         for (j = 0, len2 = points.length; j < len2; j++) {
9601                                 p = points[j];
9602                                 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
9603                         }
9604
9605                         // closes the ring for polygons; "x" is VML syntax
9606                         str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
9607                 }
9608
9609                 // SVG complains about empty path strings
9610                 return str || 'M0 0';
9611         }
9612 });
9613
9614 // @namespace Browser; @property svg: Boolean
9615 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
9616 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
9617
9618
9619 // @namespace SVG
9620 // @factory L.svg(options?: Renderer options)
9621 // Creates a SVG renderer with the given options.
9622 L.svg = function (options) {
9623         return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
9624 };
9625
9626
9627
9628 /*
9629  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
9630  */
9631
9632 /*
9633  * @class SVG
9634  *
9635  * 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.
9636  *
9637  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
9638  * with old versions of Internet Explorer.
9639  */
9640
9641 // @namespace Browser; @property vml: Boolean
9642 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
9643 L.Browser.vml = !L.Browser.svg && (function () {
9644         try {
9645                 var div = document.createElement('div');
9646                 div.innerHTML = '<v:shape adj="1"/>';
9647
9648                 var shape = div.firstChild;
9649                 shape.style.behavior = 'url(#default#VML)';
9650
9651                 return shape && (typeof shape.adj === 'object');
9652
9653         } catch (e) {
9654                 return false;
9655         }
9656 }());
9657
9658 // redefine some SVG methods to handle VML syntax which is similar but with some differences
9659 L.SVG.include(!L.Browser.vml ? {} : {
9660
9661         _initContainer: function () {
9662                 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
9663         },
9664
9665         _update: function () {
9666                 if (this._map._animatingZoom) { return; }
9667                 L.Renderer.prototype._update.call(this);
9668                 this.fire('update');
9669         },
9670
9671         _initPath: function (layer) {
9672                 var container = layer._container = L.SVG.create('shape');
9673
9674                 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
9675
9676                 container.coordsize = '1 1';
9677
9678                 layer._path = L.SVG.create('path');
9679                 container.appendChild(layer._path);
9680
9681                 this._updateStyle(layer);
9682         },
9683
9684         _addPath: function (layer) {
9685                 var container = layer._container;
9686                 this._container.appendChild(container);
9687
9688                 if (layer.options.interactive) {
9689                         layer.addInteractiveTarget(container);
9690                 }
9691         },
9692
9693         _removePath: function (layer) {
9694                 var container = layer._container;
9695                 L.DomUtil.remove(container);
9696                 layer.removeInteractiveTarget(container);
9697         },
9698
9699         _updateStyle: function (layer) {
9700                 var stroke = layer._stroke,
9701                     fill = layer._fill,
9702                     options = layer.options,
9703                     container = layer._container;
9704
9705                 container.stroked = !!options.stroke;
9706                 container.filled = !!options.fill;
9707
9708                 if (options.stroke) {
9709                         if (!stroke) {
9710                                 stroke = layer._stroke = L.SVG.create('stroke');
9711                         }
9712                         container.appendChild(stroke);
9713                         stroke.weight = options.weight + 'px';
9714                         stroke.color = options.color;
9715                         stroke.opacity = options.opacity;
9716
9717                         if (options.dashArray) {
9718                                 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
9719                                     options.dashArray.join(' ') :
9720                                     options.dashArray.replace(/( *, *)/g, ' ');
9721                         } else {
9722                                 stroke.dashStyle = '';
9723                         }
9724                         stroke.endcap = options.lineCap.replace('butt', 'flat');
9725                         stroke.joinstyle = options.lineJoin;
9726
9727                 } else if (stroke) {
9728                         container.removeChild(stroke);
9729                         layer._stroke = null;
9730                 }
9731
9732                 if (options.fill) {
9733                         if (!fill) {
9734                                 fill = layer._fill = L.SVG.create('fill');
9735                         }
9736                         container.appendChild(fill);
9737                         fill.color = options.fillColor || options.color;
9738                         fill.opacity = options.fillOpacity;
9739
9740                 } else if (fill) {
9741                         container.removeChild(fill);
9742                         layer._fill = null;
9743                 }
9744         },
9745
9746         _updateCircle: function (layer) {
9747                 var p = layer._point.round(),
9748                     r = Math.round(layer._radius),
9749                     r2 = Math.round(layer._radiusY || r);
9750
9751                 this._setPath(layer, layer._empty() ? 'M0 0' :
9752                                 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
9753         },
9754
9755         _setPath: function (layer, path) {
9756                 layer._path.v = path;
9757         },
9758
9759         _bringToFront: function (layer) {
9760                 L.DomUtil.toFront(layer._container);
9761         },
9762
9763         _bringToBack: function (layer) {
9764                 L.DomUtil.toBack(layer._container);
9765         }
9766 });
9767
9768 if (L.Browser.vml) {
9769         L.SVG.create = (function () {
9770                 try {
9771                         document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
9772                         return function (name) {
9773                                 return document.createElement('<lvml:' + name + ' class="lvml">');
9774                         };
9775                 } catch (e) {
9776                         return function (name) {
9777                                 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
9778                         };
9779                 }
9780         })();
9781 }
9782
9783
9784
9785 /*
9786  * @class Canvas
9787  * @inherits Renderer
9788  * @aka L.Canvas
9789  *
9790  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9791  * Inherits `Renderer`.
9792  *
9793  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
9794  * available in all web browsers, notably IE8, and overlapping geometries might
9795  * not display properly in some edge cases.
9796  *
9797  * @example
9798  *
9799  * Use Canvas by default for all paths in the map:
9800  *
9801  * ```js
9802  * var map = L.map('map', {
9803  *      renderer: L.canvas()
9804  * });
9805  * ```
9806  *
9807  * Use a Canvas renderer with extra padding for specific vector geometries:
9808  *
9809  * ```js
9810  * var map = L.map('map');
9811  * var myRenderer = L.canvas({ padding: 0.5 });
9812  * var line = L.polyline( coordinates, { renderer: myRenderer } );
9813  * var circle = L.circle( center, { renderer: myRenderer } );
9814  * ```
9815  */
9816
9817 L.Canvas = L.Renderer.extend({
9818
9819         onAdd: function () {
9820                 L.Renderer.prototype.onAdd.call(this);
9821
9822                 // Redraw vectors since canvas is cleared upon removal,
9823                 // in case of removing the renderer itself from the map.
9824                 this._draw();
9825         },
9826
9827         _initContainer: function () {
9828                 var container = this._container = document.createElement('canvas');
9829
9830                 L.DomEvent
9831                         .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
9832                         .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
9833                         .on(container, 'mouseout', this._handleMouseOut, this);
9834
9835                 this._ctx = container.getContext('2d');
9836         },
9837
9838         _updatePaths: function () {
9839                 var layer;
9840                 this._redrawBounds = null;
9841                 for (var id in this._layers) {
9842                         layer = this._layers[id];
9843                         layer._update();
9844                 }
9845                 this._redraw();
9846         },
9847
9848         _update: function () {
9849                 if (this._map._animatingZoom && this._bounds) { return; }
9850
9851                 this._drawnLayers = {};
9852
9853                 L.Renderer.prototype._update.call(this);
9854
9855                 var b = this._bounds,
9856                     container = this._container,
9857                     size = b.getSize(),
9858                     m = L.Browser.retina ? 2 : 1;
9859
9860                 L.DomUtil.setPosition(container, b.min);
9861
9862                 // set canvas size (also clearing it); use double size on retina
9863                 container.width = m * size.x;
9864                 container.height = m * size.y;
9865                 container.style.width = size.x + 'px';
9866                 container.style.height = size.y + 'px';
9867
9868                 if (L.Browser.retina) {
9869                         this._ctx.scale(2, 2);
9870                 }
9871
9872                 // translate so we use the same path coordinates after canvas element moves
9873                 this._ctx.translate(-b.min.x, -b.min.y);
9874
9875                 // Tell paths to redraw themselves
9876                 this.fire('update');
9877         },
9878
9879         _initPath: function (layer) {
9880                 this._updateDashArray(layer);
9881                 this._layers[L.stamp(layer)] = layer;
9882
9883                 var order = layer._order = {
9884                         layer: layer,
9885                         prev: this._drawLast,
9886                         next: null
9887                 };
9888                 if (this._drawLast) { this._drawLast.next = order; }
9889                 this._drawLast = order;
9890                 this._drawFirst = this._drawFirst || this._drawLast;
9891         },
9892
9893         _addPath: function (layer) {
9894                 this._requestRedraw(layer);
9895         },
9896
9897         _removePath: function (layer) {
9898                 var order = layer._order;
9899                 var next = order.next;
9900                 var prev = order.prev;
9901
9902                 if (next) {
9903                         next.prev = prev;
9904                 } else {
9905                         this._drawLast = prev;
9906                 }
9907                 if (prev) {
9908                         prev.next = next;
9909                 } else {
9910                         this._drawFirst = next;
9911                 }
9912
9913                 delete layer._order;
9914
9915                 delete this._layers[L.stamp(layer)];
9916
9917                 this._requestRedraw(layer);
9918         },
9919
9920         _updatePath: function (layer) {
9921                 // Redraw the union of the layer's old pixel
9922                 // bounds and the new pixel bounds.
9923                 this._extendRedrawBounds(layer);
9924                 layer._project();
9925                 layer._update();
9926                 // The redraw will extend the redraw bounds
9927                 // with the new pixel bounds.
9928                 this._requestRedraw(layer);
9929         },
9930
9931         _updateStyle: function (layer) {
9932                 this._updateDashArray(layer);
9933                 this._requestRedraw(layer);
9934         },
9935
9936         _updateDashArray: function (layer) {
9937                 if (layer.options.dashArray) {
9938                         var parts = layer.options.dashArray.split(','),
9939                             dashArray = [],
9940                             i;
9941                         for (i = 0; i < parts.length; i++) {
9942                                 dashArray.push(Number(parts[i]));
9943                         }
9944                         layer.options._dashArray = dashArray;
9945                 }
9946         },
9947
9948         _requestRedraw: function (layer) {
9949                 if (!this._map) { return; }
9950
9951                 this._extendRedrawBounds(layer);
9952                 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
9953         },
9954
9955         _extendRedrawBounds: function (layer) {
9956                 var padding = (layer.options.weight || 0) + 1;
9957                 this._redrawBounds = this._redrawBounds || new L.Bounds();
9958                 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
9959                 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
9960         },
9961
9962         _redraw: function () {
9963                 this._redrawRequest = null;
9964
9965                 this._clear(); // clear layers in redraw bounds
9966                 this._draw(); // draw layers
9967
9968                 this._redrawBounds = null;
9969         },
9970
9971         _clear: function () {
9972                 var bounds = this._redrawBounds;
9973                 if (bounds) {
9974                         var size = bounds.getSize();
9975                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
9976                 } else {
9977                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
9978                 }
9979         },
9980
9981         _draw: function () {
9982                 var layer, bounds = this._redrawBounds;
9983                 this._ctx.save();
9984                 if (bounds) {
9985                         var size = bounds.getSize();
9986                         this._ctx.beginPath();
9987                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
9988                         this._ctx.clip();
9989                 }
9990
9991                 this._drawing = true;
9992
9993                 for (var order = this._drawFirst; order; order = order.next) {
9994                         layer = order.layer;
9995                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
9996                                 layer._updatePath();
9997                         }
9998                 }
9999
10000                 this._drawing = false;
10001
10002                 this._ctx.restore();  // Restore state before clipping.
10003         },
10004
10005         _updatePoly: function (layer, closed) {
10006                 if (!this._drawing) { return; }
10007
10008                 var i, j, len2, p,
10009                     parts = layer._parts,
10010                     len = parts.length,
10011                     ctx = this._ctx;
10012
10013                 if (!len) { return; }
10014
10015                 this._drawnLayers[layer._leaflet_id] = layer;
10016
10017                 ctx.beginPath();
10018
10019                 if (ctx.setLineDash) {
10020                         ctx.setLineDash(layer.options && layer.options._dashArray || []);
10021                 }
10022
10023                 for (i = 0; i < len; i++) {
10024                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
10025                                 p = parts[i][j];
10026                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
10027                         }
10028                         if (closed) {
10029                                 ctx.closePath();
10030                         }
10031                 }
10032
10033                 this._fillStroke(ctx, layer);
10034
10035                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
10036         },
10037
10038         _updateCircle: function (layer) {
10039
10040                 if (!this._drawing || layer._empty()) { return; }
10041
10042                 var p = layer._point,
10043                     ctx = this._ctx,
10044                     r = layer._radius,
10045                     s = (layer._radiusY || r) / r;
10046
10047                 this._drawnLayers[layer._leaflet_id] = layer;
10048
10049                 if (s !== 1) {
10050                         ctx.save();
10051                         ctx.scale(1, s);
10052                 }
10053
10054                 ctx.beginPath();
10055                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
10056
10057                 if (s !== 1) {
10058                         ctx.restore();
10059                 }
10060
10061                 this._fillStroke(ctx, layer);
10062         },
10063
10064         _fillStroke: function (ctx, layer) {
10065                 var options = layer.options;
10066
10067                 if (options.fill) {
10068                         ctx.globalAlpha = options.fillOpacity;
10069                         ctx.fillStyle = options.fillColor || options.color;
10070                         ctx.fill(options.fillRule || 'evenodd');
10071                 }
10072
10073                 if (options.stroke && options.weight !== 0) {
10074                         ctx.globalAlpha = options.opacity;
10075                         ctx.lineWidth = options.weight;
10076                         ctx.strokeStyle = options.color;
10077                         ctx.lineCap = options.lineCap;
10078                         ctx.lineJoin = options.lineJoin;
10079                         ctx.stroke();
10080                 }
10081         },
10082
10083         // Canvas obviously doesn't have mouse events for individual drawn objects,
10084         // so we emulate that by calculating what's under the mouse on mousemove/click manually
10085
10086         _onClick: function (e) {
10087                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
10088
10089                 for (var order = this._drawFirst; order; order = order.next) {
10090                         layer = order.layer;
10091                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
10092                                 clickedLayer = layer;
10093                         }
10094                 }
10095                 if (clickedLayer)  {
10096                         L.DomEvent._fakeStop(e);
10097                         this._fireEvent([clickedLayer], e);
10098                 }
10099         },
10100
10101         _onMouseMove: function (e) {
10102                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
10103
10104                 var point = this._map.mouseEventToLayerPoint(e);
10105                 this._handleMouseHover(e, point);
10106         },
10107
10108
10109         _handleMouseOut: function (e) {
10110                 var layer = this._hoveredLayer;
10111                 if (layer) {
10112                         // if we're leaving the layer, fire mouseout
10113                         L.DomUtil.removeClass(this._container, 'leaflet-interactive');
10114                         this._fireEvent([layer], e, 'mouseout');
10115                         this._hoveredLayer = null;
10116                 }
10117         },
10118
10119         _handleMouseHover: function (e, point) {
10120                 var layer, candidateHoveredLayer;
10121
10122                 for (var order = this._drawFirst; order; order = order.next) {
10123                         layer = order.layer;
10124                         if (layer.options.interactive && layer._containsPoint(point)) {
10125                                 candidateHoveredLayer = layer;
10126                         }
10127                 }
10128
10129                 if (candidateHoveredLayer !== this._hoveredLayer) {
10130                         this._handleMouseOut(e);
10131
10132                         if (candidateHoveredLayer) {
10133                                 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
10134                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
10135                                 this._hoveredLayer = candidateHoveredLayer;
10136                         }
10137                 }
10138
10139                 if (this._hoveredLayer) {
10140                         this._fireEvent([this._hoveredLayer], e);
10141                 }
10142         },
10143
10144         _fireEvent: function (layers, e, type) {
10145                 this._map._fireDOMEvent(e, type || e.type, layers);
10146         },
10147
10148         _bringToFront: function (layer) {
10149                 var order = layer._order;
10150                 var next = order.next;
10151                 var prev = order.prev;
10152
10153                 if (next) {
10154                         next.prev = prev;
10155                 } else {
10156                         // Already last
10157                         return;
10158                 }
10159                 if (prev) {
10160                         prev.next = next;
10161                 } else if (next) {
10162                         // Update first entry unless this is the
10163                         // signle entry
10164                         this._drawFirst = next;
10165                 }
10166
10167                 order.prev = this._drawLast;
10168                 this._drawLast.next = order;
10169
10170                 order.next = null;
10171                 this._drawLast = order;
10172
10173                 this._requestRedraw(layer);
10174         },
10175
10176         _bringToBack: function (layer) {
10177                 var order = layer._order;
10178                 var next = order.next;
10179                 var prev = order.prev;
10180
10181                 if (prev) {
10182                         prev.next = next;
10183                 } else {
10184                         // Already first
10185                         return;
10186                 }
10187                 if (next) {
10188                         next.prev = prev;
10189                 } else if (prev) {
10190                         // Update last entry unless this is the
10191                         // signle entry
10192                         this._drawLast = prev;
10193                 }
10194
10195                 order.prev = null;
10196
10197                 order.next = this._drawFirst;
10198                 this._drawFirst.prev = order;
10199                 this._drawFirst = order;
10200
10201                 this._requestRedraw(layer);
10202         }
10203 });
10204
10205 // @namespace Browser; @property canvas: Boolean
10206 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
10207 L.Browser.canvas = (function () {
10208         return !!document.createElement('canvas').getContext;
10209 }());
10210
10211 // @namespace Canvas
10212 // @factory L.canvas(options?: Renderer options)
10213 // Creates a Canvas renderer with the given options.
10214 L.canvas = function (options) {
10215         return L.Browser.canvas ? new L.Canvas(options) : null;
10216 };
10217
10218 L.Polyline.prototype._containsPoint = function (p, closed) {
10219         var i, j, k, len, len2, part,
10220             w = this._clickTolerance();
10221
10222         if (!this._pxBounds.contains(p)) { return false; }
10223
10224         // hit detection for polylines
10225         for (i = 0, len = this._parts.length; i < len; i++) {
10226                 part = this._parts[i];
10227
10228                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10229                         if (!closed && (j === 0)) { continue; }
10230
10231                         if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
10232                                 return true;
10233                         }
10234                 }
10235         }
10236         return false;
10237 };
10238
10239 L.Polygon.prototype._containsPoint = function (p) {
10240         var inside = false,
10241             part, p1, p2, i, j, k, len, len2;
10242
10243         if (!this._pxBounds.contains(p)) { return false; }
10244
10245         // ray casting algorithm for detecting if point is in polygon
10246         for (i = 0, len = this._parts.length; i < len; i++) {
10247                 part = this._parts[i];
10248
10249                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10250                         p1 = part[j];
10251                         p2 = part[k];
10252
10253                         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)) {
10254                                 inside = !inside;
10255                         }
10256                 }
10257         }
10258
10259         // also check if it's on polygon stroke
10260         return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
10261 };
10262
10263 L.CircleMarker.prototype._containsPoint = function (p) {
10264         return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
10265 };
10266
10267
10268
10269 /*
10270  * @class GeoJSON
10271  * @aka L.GeoJSON
10272  * @inherits FeatureGroup
10273  *
10274  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
10275  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
10276  *
10277  * @example
10278  *
10279  * ```js
10280  * L.geoJSON(data, {
10281  *      style: function (feature) {
10282  *              return {color: feature.properties.color};
10283  *      }
10284  * }).bindPopup(function (layer) {
10285  *      return layer.feature.properties.description;
10286  * }).addTo(map);
10287  * ```
10288  */
10289
10290 L.GeoJSON = L.FeatureGroup.extend({
10291
10292         /* @section
10293          * @aka GeoJSON options
10294          *
10295          * @option pointToLayer: Function = *
10296          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
10297          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
10298          * The default is to spawn a default `Marker`:
10299          * ```js
10300          * function(geoJsonPoint, latlng) {
10301          *      return L.marker(latlng);
10302          * }
10303          * ```
10304          *
10305          * @option style: Function = *
10306          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
10307          * called internally when data is added.
10308          * The default value is to not override any defaults:
10309          * ```js
10310          * function (geoJsonFeature) {
10311          *      return {}
10312          * }
10313          * ```
10314          *
10315          * @option onEachFeature: Function = *
10316          * A `Function` that will be called once for each created `Feature`, after it has
10317          * been created and styled. Useful for attaching events and popups to features.
10318          * The default is to do nothing with the newly created layers:
10319          * ```js
10320          * function (feature, layer) {}
10321          * ```
10322          *
10323          * @option filter: Function = *
10324          * A `Function` that will be used to decide whether to include a feature or not.
10325          * The default is to include all features:
10326          * ```js
10327          * function (geoJsonFeature) {
10328          *      return true;
10329          * }
10330          * ```
10331          * Note: dynamically changing the `filter` option will have effect only on newly
10332          * added data. It will _not_ re-evaluate already included features.
10333          *
10334          * @option coordsToLatLng: Function = *
10335          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
10336          * The default is the `coordsToLatLng` static method.
10337          */
10338
10339         initialize: function (geojson, options) {
10340                 L.setOptions(this, options);
10341
10342                 this._layers = {};
10343
10344                 if (geojson) {
10345                         this.addData(geojson);
10346                 }
10347         },
10348
10349         // @method addData( <GeoJSON> data ): this
10350         // Adds a GeoJSON object to the layer.
10351         addData: function (geojson) {
10352                 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
10353                     i, len, feature;
10354
10355                 if (features) {
10356                         for (i = 0, len = features.length; i < len; i++) {
10357                                 // only add this if geometry or geometries are set and not null
10358                                 feature = features[i];
10359                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
10360                                         this.addData(feature);
10361                                 }
10362                         }
10363                         return this;
10364                 }
10365
10366                 var options = this.options;
10367
10368                 if (options.filter && !options.filter(geojson)) { return this; }
10369
10370                 var layer = L.GeoJSON.geometryToLayer(geojson, options);
10371                 if (!layer) {
10372                         return this;
10373                 }
10374                 layer.feature = L.GeoJSON.asFeature(geojson);
10375
10376                 layer.defaultOptions = layer.options;
10377                 this.resetStyle(layer);
10378
10379                 if (options.onEachFeature) {
10380                         options.onEachFeature(geojson, layer);
10381                 }
10382
10383                 return this.addLayer(layer);
10384         },
10385
10386         // @method resetStyle( <Path> layer ): this
10387         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
10388         resetStyle: function (layer) {
10389                 // reset any custom styles
10390                 layer.options = L.Util.extend({}, layer.defaultOptions);
10391                 this._setLayerStyle(layer, this.options.style);
10392                 return this;
10393         },
10394
10395         // @method setStyle( <Function> style ): this
10396         // Changes styles of GeoJSON vector layers with the given style function.
10397         setStyle: function (style) {
10398                 return this.eachLayer(function (layer) {
10399                         this._setLayerStyle(layer, style);
10400                 }, this);
10401         },
10402
10403         _setLayerStyle: function (layer, style) {
10404                 if (typeof style === 'function') {
10405                         style = style(layer.feature);
10406                 }
10407                 if (layer.setStyle) {
10408                         layer.setStyle(style);
10409                 }
10410         }
10411 });
10412
10413 // @section
10414 // There are several static functions which can be called without instantiating L.GeoJSON:
10415 L.extend(L.GeoJSON, {
10416         // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
10417         // Creates a `Layer` from a given GeoJSON feature. Can use a custom
10418         // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
10419         // functions if provided as options.
10420         geometryToLayer: function (geojson, options) {
10421
10422                 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
10423                     coords = geometry ? geometry.coordinates : null,
10424                     layers = [],
10425                     pointToLayer = options && options.pointToLayer,
10426                     coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
10427                     latlng, latlngs, i, len;
10428
10429                 if (!coords && !geometry) {
10430                         return null;
10431                 }
10432
10433                 switch (geometry.type) {
10434                 case 'Point':
10435                         latlng = coordsToLatLng(coords);
10436                         return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
10437
10438                 case 'MultiPoint':
10439                         for (i = 0, len = coords.length; i < len; i++) {
10440                                 latlng = coordsToLatLng(coords[i]);
10441                                 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
10442                         }
10443                         return new L.FeatureGroup(layers);
10444
10445                 case 'LineString':
10446                 case 'MultiLineString':
10447                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
10448                         return new L.Polyline(latlngs, options);
10449
10450                 case 'Polygon':
10451                 case 'MultiPolygon':
10452                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
10453                         return new L.Polygon(latlngs, options);
10454
10455                 case 'GeometryCollection':
10456                         for (i = 0, len = geometry.geometries.length; i < len; i++) {
10457                                 var layer = this.geometryToLayer({
10458                                         geometry: geometry.geometries[i],
10459                                         type: 'Feature',
10460                                         properties: geojson.properties
10461                                 }, options);
10462
10463                                 if (layer) {
10464                                         layers.push(layer);
10465                                 }
10466                         }
10467                         return new L.FeatureGroup(layers);
10468
10469                 default:
10470                         throw new Error('Invalid GeoJSON object.');
10471                 }
10472         },
10473
10474         // @function coordsToLatLng(coords: Array): LatLng
10475         // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
10476         // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
10477         coordsToLatLng: function (coords) {
10478                 return new L.LatLng(coords[1], coords[0], coords[2]);
10479         },
10480
10481         // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
10482         // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
10483         // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
10484         // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
10485         coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
10486                 var latlngs = [];
10487
10488                 for (var i = 0, len = coords.length, latlng; i < len; i++) {
10489                         latlng = levelsDeep ?
10490                                 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
10491                                 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
10492
10493                         latlngs.push(latlng);
10494                 }
10495
10496                 return latlngs;
10497         },
10498
10499         // @function latLngToCoords(latlng: LatLng): Array
10500         // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
10501         latLngToCoords: function (latlng) {
10502                 return latlng.alt !== undefined ?
10503                                 [latlng.lng, latlng.lat, latlng.alt] :
10504                                 [latlng.lng, latlng.lat];
10505         },
10506
10507         // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
10508         // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
10509         // `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.
10510         latLngsToCoords: function (latlngs, levelsDeep, closed) {
10511                 var coords = [];
10512
10513                 for (var i = 0, len = latlngs.length; i < len; i++) {
10514                         coords.push(levelsDeep ?
10515                                 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
10516                                 L.GeoJSON.latLngToCoords(latlngs[i]));
10517                 }
10518
10519                 if (!levelsDeep && closed) {
10520                         coords.push(coords[0]);
10521                 }
10522
10523                 return coords;
10524         },
10525
10526         getFeature: function (layer, newGeometry) {
10527                 return layer.feature ?
10528                                 L.extend({}, layer.feature, {geometry: newGeometry}) :
10529                                 L.GeoJSON.asFeature(newGeometry);
10530         },
10531
10532         // @function asFeature(geojson: Object): Object
10533         // Normalize GeoJSON geometries/features into GeoJSON features.
10534         asFeature: function (geojson) {
10535                 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
10536                         return geojson;
10537                 }
10538
10539                 return {
10540                         type: 'Feature',
10541                         properties: {},
10542                         geometry: geojson
10543                 };
10544         }
10545 });
10546
10547 var PointToGeoJSON = {
10548         toGeoJSON: function () {
10549                 return L.GeoJSON.getFeature(this, {
10550                         type: 'Point',
10551                         coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
10552                 });
10553         }
10554 };
10555
10556 // @namespace Marker
10557 // @method toGeoJSON(): Object
10558 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
10559 L.Marker.include(PointToGeoJSON);
10560
10561 // @namespace CircleMarker
10562 // @method toGeoJSON(): Object
10563 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
10564 L.Circle.include(PointToGeoJSON);
10565 L.CircleMarker.include(PointToGeoJSON);
10566
10567
10568 // @namespace Polyline
10569 // @method toGeoJSON(): Object
10570 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
10571 L.Polyline.prototype.toGeoJSON = function () {
10572         var multi = !L.Polyline._flat(this._latlngs);
10573
10574         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
10575
10576         return L.GeoJSON.getFeature(this, {
10577                 type: (multi ? 'Multi' : '') + 'LineString',
10578                 coordinates: coords
10579         });
10580 };
10581
10582 // @namespace Polygon
10583 // @method toGeoJSON(): Object
10584 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
10585 L.Polygon.prototype.toGeoJSON = function () {
10586         var holes = !L.Polyline._flat(this._latlngs),
10587             multi = holes && !L.Polyline._flat(this._latlngs[0]);
10588
10589         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
10590
10591         if (!holes) {
10592                 coords = [coords];
10593         }
10594
10595         return L.GeoJSON.getFeature(this, {
10596                 type: (multi ? 'Multi' : '') + 'Polygon',
10597                 coordinates: coords
10598         });
10599 };
10600
10601
10602 // @namespace LayerGroup
10603 L.LayerGroup.include({
10604         toMultiPoint: function () {
10605                 var coords = [];
10606
10607                 this.eachLayer(function (layer) {
10608                         coords.push(layer.toGeoJSON().geometry.coordinates);
10609                 });
10610
10611                 return L.GeoJSON.getFeature(this, {
10612                         type: 'MultiPoint',
10613                         coordinates: coords
10614                 });
10615         },
10616
10617         // @method toGeoJSON(): Object
10618         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
10619         toGeoJSON: function () {
10620
10621                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
10622
10623                 if (type === 'MultiPoint') {
10624                         return this.toMultiPoint();
10625                 }
10626
10627                 var isGeometryCollection = type === 'GeometryCollection',
10628                     jsons = [];
10629
10630                 this.eachLayer(function (layer) {
10631                         if (layer.toGeoJSON) {
10632                                 var json = layer.toGeoJSON();
10633                                 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
10634                         }
10635                 });
10636
10637                 if (isGeometryCollection) {
10638                         return L.GeoJSON.getFeature(this, {
10639                                 geometries: jsons,
10640                                 type: 'GeometryCollection'
10641                         });
10642                 }
10643
10644                 return {
10645                         type: 'FeatureCollection',
10646                         features: jsons
10647                 };
10648         }
10649 });
10650
10651 // @namespace GeoJSON
10652 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
10653 // Creates a GeoJSON layer. Optionally accepts an object in
10654 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
10655 // (you can alternatively add it later with `addData` method) and an `options` object.
10656 L.geoJSON = function (geojson, options) {
10657         return new L.GeoJSON(geojson, options);
10658 };
10659 // Backward compatibility.
10660 L.geoJson = L.geoJSON;
10661
10662
10663
10664 /*
10665  * @class Draggable
10666  * @aka L.Draggable
10667  * @inherits Evented
10668  *
10669  * A class for making DOM elements draggable (including touch support).
10670  * Used internally for map and marker dragging. Only works for elements
10671  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10672  *
10673  * @example
10674  * ```js
10675  * var draggable = new L.Draggable(elementToDrag);
10676  * draggable.enable();
10677  * ```
10678  */
10679
10680 L.Draggable = L.Evented.extend({
10681
10682         options: {
10683                 // @option clickTolerance: Number = 3
10684                 // The max number of pixels a user can shift the mouse pointer during a click
10685                 // for it to be considered a valid click (as opposed to a mouse drag).
10686                 clickTolerance: 3
10687         },
10688
10689         statics: {
10690                 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10691                 END: {
10692                         mousedown: 'mouseup',
10693                         touchstart: 'touchend',
10694                         pointerdown: 'touchend',
10695                         MSPointerDown: 'touchend'
10696                 },
10697                 MOVE: {
10698                         mousedown: 'mousemove',
10699                         touchstart: 'touchmove',
10700                         pointerdown: 'touchmove',
10701                         MSPointerDown: 'touchmove'
10702                 }
10703         },
10704
10705         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10706         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10707         initialize: function (element, dragStartTarget, preventOutline) {
10708                 this._element = element;
10709                 this._dragStartTarget = dragStartTarget || element;
10710                 this._preventOutline = preventOutline;
10711         },
10712
10713         // @method enable()
10714         // Enables the dragging ability
10715         enable: function () {
10716                 if (this._enabled) { return; }
10717
10718                 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10719
10720                 this._enabled = true;
10721         },
10722
10723         // @method disable()
10724         // Disables the dragging ability
10725         disable: function () {
10726                 if (!this._enabled) { return; }
10727
10728                 // If we're currently dragging this draggable,
10729                 // disabling it counts as first ending the drag.
10730                 if (L.Draggable._dragging === this) {
10731                         this.finishDrag();
10732                 }
10733
10734                 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10735
10736                 this._enabled = false;
10737                 this._moved = false;
10738         },
10739
10740         _onDown: function (e) {
10741                 // Ignore simulated events, since we handle both touch and
10742                 // mouse explicitly; otherwise we risk getting duplicates of
10743                 // touch events, see #4315.
10744                 // Also ignore the event if disabled; this happens in IE11
10745                 // under some circumstances, see #3666.
10746                 if (e._simulated || !this._enabled) { return; }
10747
10748                 this._moved = false;
10749
10750                 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10751
10752                 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
10753                 L.Draggable._dragging = this;  // Prevent dragging multiple objects at once.
10754
10755                 if (this._preventOutline) {
10756                         L.DomUtil.preventOutline(this._element);
10757                 }
10758
10759                 L.DomUtil.disableImageDrag();
10760                 L.DomUtil.disableTextSelection();
10761
10762                 if (this._moving) { return; }
10763
10764                 // @event down: Event
10765                 // Fired when a drag is about to start.
10766                 this.fire('down');
10767
10768                 var first = e.touches ? e.touches[0] : e;
10769
10770                 this._startPoint = new L.Point(first.clientX, first.clientY);
10771
10772                 L.DomEvent
10773                         .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10774                         .on(document, L.Draggable.END[e.type], this._onUp, this);
10775         },
10776
10777         _onMove: function (e) {
10778                 // Ignore simulated events, since we handle both touch and
10779                 // mouse explicitly; otherwise we risk getting duplicates of
10780                 // touch events, see #4315.
10781                 // Also ignore the event if disabled; this happens in IE11
10782                 // under some circumstances, see #3666.
10783                 if (e._simulated || !this._enabled) { return; }
10784
10785                 if (e.touches && e.touches.length > 1) {
10786                         this._moved = true;
10787                         return;
10788                 }
10789
10790                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10791                     newPoint = new L.Point(first.clientX, first.clientY),
10792                     offset = newPoint.subtract(this._startPoint);
10793
10794                 if (!offset.x && !offset.y) { return; }
10795                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10796
10797                 L.DomEvent.preventDefault(e);
10798
10799                 if (!this._moved) {
10800                         // @event dragstart: Event
10801                         // Fired when a drag starts
10802                         this.fire('dragstart');
10803
10804                         this._moved = true;
10805                         this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10806
10807                         L.DomUtil.addClass(document.body, 'leaflet-dragging');
10808
10809                         this._lastTarget = e.target || e.srcElement;
10810                         // IE and Edge do not give the <use> element, so fetch it
10811                         // if necessary
10812                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10813                                 this._lastTarget = this._lastTarget.correspondingUseElement;
10814                         }
10815                         L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10816                 }
10817
10818                 this._newPos = this._startPos.add(offset);
10819                 this._moving = true;
10820
10821                 L.Util.cancelAnimFrame(this._animRequest);
10822                 this._lastEvent = e;
10823                 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10824         },
10825
10826         _updatePosition: function () {
10827                 var e = {originalEvent: this._lastEvent};
10828
10829                 // @event predrag: Event
10830                 // Fired continuously during dragging *before* each corresponding
10831                 // update of the element's position.
10832                 this.fire('predrag', e);
10833                 L.DomUtil.setPosition(this._element, this._newPos);
10834
10835                 // @event drag: Event
10836                 // Fired continuously during dragging.
10837                 this.fire('drag', e);
10838         },
10839
10840         _onUp: function (e) {
10841                 // Ignore simulated events, since we handle both touch and
10842                 // mouse explicitly; otherwise we risk getting duplicates of
10843                 // touch events, see #4315.
10844                 // Also ignore the event if disabled; this happens in IE11
10845                 // under some circumstances, see #3666.
10846                 if (e._simulated || !this._enabled) { return; }
10847                 this.finishDrag();
10848         },
10849
10850         finishDrag: function () {
10851                 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10852
10853                 if (this._lastTarget) {
10854                         L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10855                         this._lastTarget = null;
10856                 }
10857
10858                 for (var i in L.Draggable.MOVE) {
10859                         L.DomEvent
10860                                 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10861                                 .off(document, L.Draggable.END[i], this._onUp, this);
10862                 }
10863
10864                 L.DomUtil.enableImageDrag();
10865                 L.DomUtil.enableTextSelection();
10866
10867                 if (this._moved && this._moving) {
10868                         // ensure drag is not fired after dragend
10869                         L.Util.cancelAnimFrame(this._animRequest);
10870
10871                         // @event dragend: DragEndEvent
10872                         // Fired when the drag ends.
10873                         this.fire('dragend', {
10874                                 distance: this._newPos.distanceTo(this._startPos)
10875                         });
10876                 }
10877
10878                 this._moving = false;
10879                 L.Draggable._dragging = false;
10880         }
10881
10882 });
10883
10884
10885
10886 /*
10887         L.Handler is a base class for handler classes that are used internally to inject
10888         interaction features like dragging to classes like Map and Marker.
10889 */
10890
10891 // @class Handler
10892 // @aka L.Handler
10893 // Abstract class for map interaction handlers
10894
10895 L.Handler = L.Class.extend({
10896         initialize: function (map) {
10897                 this._map = map;
10898         },
10899
10900         // @method enable(): this
10901         // Enables the handler
10902         enable: function () {
10903                 if (this._enabled) { return this; }
10904
10905                 this._enabled = true;
10906                 this.addHooks();
10907                 return this;
10908         },
10909
10910         // @method disable(): this
10911         // Disables the handler
10912         disable: function () {
10913                 if (!this._enabled) { return this; }
10914
10915                 this._enabled = false;
10916                 this.removeHooks();
10917                 return this;
10918         },
10919
10920         // @method enabled(): Boolean
10921         // Returns `true` if the handler is enabled
10922         enabled: function () {
10923                 return !!this._enabled;
10924         }
10925
10926         // @section Extension methods
10927         // Classes inheriting from `Handler` must implement the two following methods:
10928         // @method addHooks()
10929         // Called when the handler is enabled, should add event hooks.
10930         // @method removeHooks()
10931         // Called when the handler is disabled, should remove the event hooks added previously.
10932 });
10933
10934
10935
10936 /*
10937  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
10938  */
10939
10940 // @namespace Map
10941 // @section Interaction Options
10942 L.Map.mergeOptions({
10943         // @option dragging: Boolean = true
10944         // Whether the map be draggable with mouse/touch or not.
10945         dragging: true,
10946
10947         // @section Panning Inertia Options
10948         // @option inertia: Boolean = *
10949         // If enabled, panning of the map will have an inertia effect where
10950         // the map builds momentum while dragging and continues moving in
10951         // the same direction for some time. Feels especially nice on touch
10952         // devices. Enabled by default unless running on old Android devices.
10953         inertia: !L.Browser.android23,
10954
10955         // @option inertiaDeceleration: Number = 3000
10956         // The rate with which the inertial movement slows down, in pixels/second².
10957         inertiaDeceleration: 3400, // px/s^2
10958
10959         // @option inertiaMaxSpeed: Number = Infinity
10960         // Max speed of the inertial movement, in pixels/second.
10961         inertiaMaxSpeed: Infinity, // px/s
10962
10963         // @option easeLinearity: Number = 0.2
10964         easeLinearity: 0.2,
10965
10966         // TODO refactor, move to CRS
10967         // @option worldCopyJump: Boolean = false
10968         // With this option enabled, the map tracks when you pan to another "copy"
10969         // of the world and seamlessly jumps to the original one so that all overlays
10970         // like markers and vector layers are still visible.
10971         worldCopyJump: false,
10972
10973         // @option maxBoundsViscosity: Number = 0.0
10974         // If `maxBounds` is set, this option will control how solid the bounds
10975         // are when dragging the map around. The default value of `0.0` allows the
10976         // user to drag outside the bounds at normal speed, higher values will
10977         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
10978         // solid, preventing the user from dragging outside the bounds.
10979         maxBoundsViscosity: 0.0
10980 });
10981
10982 L.Map.Drag = L.Handler.extend({
10983         addHooks: function () {
10984                 if (!this._draggable) {
10985                         var map = this._map;
10986
10987                         this._draggable = new L.Draggable(map._mapPane, map._container);
10988
10989                         this._draggable.on({
10990                                 down: this._onDown,
10991                                 dragstart: this._onDragStart,
10992                                 drag: this._onDrag,
10993                                 dragend: this._onDragEnd
10994                         }, this);
10995
10996                         this._draggable.on('predrag', this._onPreDragLimit, this);
10997                         if (map.options.worldCopyJump) {
10998                                 this._draggable.on('predrag', this._onPreDragWrap, this);
10999                                 map.on('zoomend', this._onZoomEnd, this);
11000
11001                                 map.whenReady(this._onZoomEnd, this);
11002                         }
11003                 }
11004                 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
11005                 this._draggable.enable();
11006                 this._positions = [];
11007                 this._times = [];
11008         },
11009
11010         removeHooks: function () {
11011                 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
11012                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
11013                 this._draggable.disable();
11014         },
11015
11016         moved: function () {
11017                 return this._draggable && this._draggable._moved;
11018         },
11019
11020         moving: function () {
11021                 return this._draggable && this._draggable._moving;
11022         },
11023
11024         _onDown: function () {
11025                 this._map._stop();
11026         },
11027
11028         _onDragStart: function () {
11029                 var map = this._map;
11030
11031                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
11032                         var bounds = L.latLngBounds(this._map.options.maxBounds);
11033
11034                         this._offsetLimit = L.bounds(
11035                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
11036                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
11037                                         .add(this._map.getSize()));
11038
11039                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
11040                 } else {
11041                         this._offsetLimit = null;
11042                 }
11043
11044                 map
11045                     .fire('movestart')
11046                     .fire('dragstart');
11047
11048                 if (map.options.inertia) {
11049                         this._positions = [];
11050                         this._times = [];
11051                 }
11052         },
11053
11054         _onDrag: function (e) {
11055                 if (this._map.options.inertia) {
11056                         var time = this._lastTime = +new Date(),
11057                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
11058
11059                         this._positions.push(pos);
11060                         this._times.push(time);
11061
11062                         if (time - this._times[0] > 50) {
11063                                 this._positions.shift();
11064                                 this._times.shift();
11065                         }
11066                 }
11067
11068                 this._map
11069                     .fire('move', e)
11070                     .fire('drag', e);
11071         },
11072
11073         _onZoomEnd: function () {
11074                 var pxCenter = this._map.getSize().divideBy(2),
11075                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
11076
11077                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
11078                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
11079         },
11080
11081         _viscousLimit: function (value, threshold) {
11082                 return value - (value - threshold) * this._viscosity;
11083         },
11084
11085         _onPreDragLimit: function () {
11086                 if (!this._viscosity || !this._offsetLimit) { return; }
11087
11088                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
11089
11090                 var limit = this._offsetLimit;
11091                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
11092                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
11093                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
11094                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
11095
11096                 this._draggable._newPos = this._draggable._startPos.add(offset);
11097         },
11098
11099         _onPreDragWrap: function () {
11100                 // TODO refactor to be able to adjust map pane position after zoom
11101                 var worldWidth = this._worldWidth,
11102                     halfWidth = Math.round(worldWidth / 2),
11103                     dx = this._initialWorldOffset,
11104                     x = this._draggable._newPos.x,
11105                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
11106                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
11107                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
11108
11109                 this._draggable._absPos = this._draggable._newPos.clone();
11110                 this._draggable._newPos.x = newX;
11111         },
11112
11113         _onDragEnd: function (e) {
11114                 var map = this._map,
11115                     options = map.options,
11116
11117                     noInertia = !options.inertia || this._times.length < 2;
11118
11119                 map.fire('dragend', e);
11120
11121                 if (noInertia) {
11122                         map.fire('moveend');
11123
11124                 } else {
11125
11126                         var direction = this._lastPos.subtract(this._positions[0]),
11127                             duration = (this._lastTime - this._times[0]) / 1000,
11128                             ease = options.easeLinearity,
11129
11130                             speedVector = direction.multiplyBy(ease / duration),
11131                             speed = speedVector.distanceTo([0, 0]),
11132
11133                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
11134                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
11135
11136                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
11137                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
11138
11139                         if (!offset.x && !offset.y) {
11140                                 map.fire('moveend');
11141
11142                         } else {
11143                                 offset = map._limitOffset(offset, map.options.maxBounds);
11144
11145                                 L.Util.requestAnimFrame(function () {
11146                                         map.panBy(offset, {
11147                                                 duration: decelerationDuration,
11148                                                 easeLinearity: ease,
11149                                                 noMoveStart: true,
11150                                                 animate: true
11151                                         });
11152                                 });
11153                         }
11154                 }
11155         }
11156 });
11157
11158 // @section Handlers
11159 // @property dragging: Handler
11160 // Map dragging handler (by both mouse and touch).
11161 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
11162
11163
11164
11165 /*
11166  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
11167  */
11168
11169 // @namespace Map
11170 // @section Interaction Options
11171
11172 L.Map.mergeOptions({
11173         // @option doubleClickZoom: Boolean|String = true
11174         // Whether the map can be zoomed in by double clicking on it and
11175         // zoomed out by double clicking while holding shift. If passed
11176         // `'center'`, double-click zoom will zoom to the center of the
11177         //  view regardless of where the mouse was.
11178         doubleClickZoom: true
11179 });
11180
11181 L.Map.DoubleClickZoom = L.Handler.extend({
11182         addHooks: function () {
11183                 this._map.on('dblclick', this._onDoubleClick, this);
11184         },
11185
11186         removeHooks: function () {
11187                 this._map.off('dblclick', this._onDoubleClick, this);
11188         },
11189
11190         _onDoubleClick: function (e) {
11191                 var map = this._map,
11192                     oldZoom = map.getZoom(),
11193                     delta = map.options.zoomDelta,
11194                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
11195
11196                 if (map.options.doubleClickZoom === 'center') {
11197                         map.setZoom(zoom);
11198                 } else {
11199                         map.setZoomAround(e.containerPoint, zoom);
11200                 }
11201         }
11202 });
11203
11204 // @section Handlers
11205 //
11206 // Map properties include interaction handlers that allow you to control
11207 // interaction behavior in runtime, enabling or disabling certain features such
11208 // as dragging or touch zoom (see `Handler` methods). For example:
11209 //
11210 // ```js
11211 // map.doubleClickZoom.disable();
11212 // ```
11213 //
11214 // @property doubleClickZoom: Handler
11215 // Double click zoom handler.
11216 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
11217
11218
11219
11220 /*
11221  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
11222  */
11223
11224 // @namespace Map
11225 // @section Interaction Options
11226 L.Map.mergeOptions({
11227         // @section Mousewheel options
11228         // @option scrollWheelZoom: Boolean|String = true
11229         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
11230         // it will zoom to the center of the view regardless of where the mouse was.
11231         scrollWheelZoom: true,
11232
11233         // @option wheelDebounceTime: Number = 40
11234         // Limits the rate at which a wheel can fire (in milliseconds). By default
11235         // user can't zoom via wheel more often than once per 40 ms.
11236         wheelDebounceTime: 40,
11237
11238         // @option wheelPxPerZoomLevel: Number = 60
11239         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
11240         // mean a change of one full zoom level. Smaller values will make wheel-zooming
11241         // faster (and vice versa).
11242         wheelPxPerZoomLevel: 60
11243 });
11244
11245 L.Map.ScrollWheelZoom = L.Handler.extend({
11246         addHooks: function () {
11247                 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
11248
11249                 this._delta = 0;
11250         },
11251
11252         removeHooks: function () {
11253                 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
11254         },
11255
11256         _onWheelScroll: function (e) {
11257                 var delta = L.DomEvent.getWheelDelta(e);
11258
11259                 var debounce = this._map.options.wheelDebounceTime;
11260
11261                 this._delta += delta;
11262                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
11263
11264                 if (!this._startTime) {
11265                         this._startTime = +new Date();
11266                 }
11267
11268                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
11269
11270                 clearTimeout(this._timer);
11271                 this._timer = setTimeout(L.bind(this._performZoom, this), left);
11272
11273                 L.DomEvent.stop(e);
11274         },
11275
11276         _performZoom: function () {
11277                 var map = this._map,
11278                     zoom = map.getZoom(),
11279                     snap = this._map.options.zoomSnap || 0;
11280
11281                 map._stop(); // stop panning and fly animations if any
11282
11283                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
11284                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
11285                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
11286                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
11287                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
11288
11289                 this._delta = 0;
11290                 this._startTime = null;
11291
11292                 if (!delta) { return; }
11293
11294                 if (map.options.scrollWheelZoom === 'center') {
11295                         map.setZoom(zoom + delta);
11296                 } else {
11297                         map.setZoomAround(this._lastMousePos, zoom + delta);
11298                 }
11299         }
11300 });
11301
11302 // @section Handlers
11303 // @property scrollWheelZoom: Handler
11304 // Scroll wheel zoom handler.
11305 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
11306
11307
11308
11309 /*
11310  * Extends the event handling code with double tap support for mobile browsers.
11311  */
11312
11313 L.extend(L.DomEvent, {
11314
11315         _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
11316         _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
11317
11318         // inspired by Zepto touch code by Thomas Fuchs
11319         addDoubleTapListener: function (obj, handler, id) {
11320                 var last, touch,
11321                     doubleTap = false,
11322                     delay = 250;
11323
11324                 function onTouchStart(e) {
11325                         var count;
11326
11327                         if (L.Browser.pointer) {
11328                                 count = L.DomEvent._pointersCount;
11329                         } else {
11330                                 count = e.touches.length;
11331                         }
11332
11333                         if (count > 1) { return; }
11334
11335                         var now = Date.now(),
11336                             delta = now - (last || now);
11337
11338                         touch = e.touches ? e.touches[0] : e;
11339                         doubleTap = (delta > 0 && delta <= delay);
11340                         last = now;
11341                 }
11342
11343                 function onTouchEnd() {
11344                         if (doubleTap && !touch.cancelBubble) {
11345                                 if (L.Browser.pointer) {
11346                                         // work around .type being readonly with MSPointer* events
11347                                         var newTouch = {},
11348                                             prop, i;
11349
11350                                         for (i in touch) {
11351                                                 prop = touch[i];
11352                                                 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
11353                                         }
11354                                         touch = newTouch;
11355                                 }
11356                                 touch.type = 'dblclick';
11357                                 handler(touch);
11358                                 last = null;
11359                         }
11360                 }
11361
11362                 var pre = '_leaflet_',
11363                     touchstart = this._touchstart,
11364                     touchend = this._touchend;
11365
11366                 obj[pre + touchstart + id] = onTouchStart;
11367                 obj[pre + touchend + id] = onTouchEnd;
11368                 obj[pre + 'dblclick' + id] = handler;
11369
11370                 obj.addEventListener(touchstart, onTouchStart, false);
11371                 obj.addEventListener(touchend, onTouchEnd, false);
11372
11373                 // On some platforms (notably, chrome on win10 + touchscreen + mouse),
11374                 // the browser doesn't fire touchend/pointerup events but does fire
11375                 // native dblclicks. See #4127.
11376                 if (!L.Browser.edge) {
11377                         obj.addEventListener('dblclick', handler, false);
11378                 }
11379
11380                 return this;
11381         },
11382
11383         removeDoubleTapListener: function (obj, id) {
11384                 var pre = '_leaflet_',
11385                     touchstart = obj[pre + this._touchstart + id],
11386                     touchend = obj[pre + this._touchend + id],
11387                     dblclick = obj[pre + 'dblclick' + id];
11388
11389                 obj.removeEventListener(this._touchstart, touchstart, false);
11390                 obj.removeEventListener(this._touchend, touchend, false);
11391                 if (!L.Browser.edge) {
11392                         obj.removeEventListener('dblclick', dblclick, false);
11393                 }
11394
11395                 return this;
11396         }
11397 });
11398
11399
11400
11401 /*
11402  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
11403  */
11404
11405 L.extend(L.DomEvent, {
11406
11407         POINTER_DOWN:   L.Browser.msPointer ? 'MSPointerDown'   : 'pointerdown',
11408         POINTER_MOVE:   L.Browser.msPointer ? 'MSPointerMove'   : 'pointermove',
11409         POINTER_UP:     L.Browser.msPointer ? 'MSPointerUp'     : 'pointerup',
11410         POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
11411         TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
11412
11413         _pointers: {},
11414         _pointersCount: 0,
11415
11416         // Provides a touch events wrapper for (ms)pointer events.
11417         // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
11418
11419         addPointerListener: function (obj, type, handler, id) {
11420
11421                 if (type === 'touchstart') {
11422                         this._addPointerStart(obj, handler, id);
11423
11424                 } else if (type === 'touchmove') {
11425                         this._addPointerMove(obj, handler, id);
11426
11427                 } else if (type === 'touchend') {
11428                         this._addPointerEnd(obj, handler, id);
11429                 }
11430
11431                 return this;
11432         },
11433
11434         removePointerListener: function (obj, type, id) {
11435                 var handler = obj['_leaflet_' + type + id];
11436
11437                 if (type === 'touchstart') {
11438                         obj.removeEventListener(this.POINTER_DOWN, handler, false);
11439
11440                 } else if (type === 'touchmove') {
11441                         obj.removeEventListener(this.POINTER_MOVE, handler, false);
11442
11443                 } else if (type === 'touchend') {
11444                         obj.removeEventListener(this.POINTER_UP, handler, false);
11445                         obj.removeEventListener(this.POINTER_CANCEL, handler, false);
11446                 }
11447
11448                 return this;
11449         },
11450
11451         _addPointerStart: function (obj, handler, id) {
11452                 var onDown = L.bind(function (e) {
11453                         if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
11454                                 // In IE11, some touch events needs to fire for form controls, or
11455                                 // the controls will stop working. We keep a whitelist of tag names that
11456                                 // need these events. For other target tags, we prevent default on the event.
11457                                 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
11458                                         L.DomEvent.preventDefault(e);
11459                                 } else {
11460                                         return;
11461                                 }
11462                         }
11463
11464                         this._handlePointer(e, handler);
11465                 }, this);
11466
11467                 obj['_leaflet_touchstart' + id] = onDown;
11468                 obj.addEventListener(this.POINTER_DOWN, onDown, false);
11469
11470                 // need to keep track of what pointers and how many are active to provide e.touches emulation
11471                 if (!this._pointerDocListener) {
11472                         var pointerUp = L.bind(this._globalPointerUp, this);
11473
11474                         // we listen documentElement as any drags that end by moving the touch off the screen get fired there
11475                         document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
11476                         document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
11477                         document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
11478                         document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
11479
11480                         this._pointerDocListener = true;
11481                 }
11482         },
11483
11484         _globalPointerDown: function (e) {
11485                 this._pointers[e.pointerId] = e;
11486                 this._pointersCount++;
11487         },
11488
11489         _globalPointerMove: function (e) {
11490                 if (this._pointers[e.pointerId]) {
11491                         this._pointers[e.pointerId] = e;
11492                 }
11493         },
11494
11495         _globalPointerUp: function (e) {
11496                 delete this._pointers[e.pointerId];
11497                 this._pointersCount--;
11498         },
11499
11500         _handlePointer: function (e, handler) {
11501                 e.touches = [];
11502                 for (var i in this._pointers) {
11503                         e.touches.push(this._pointers[i]);
11504                 }
11505                 e.changedTouches = [e];
11506
11507                 handler(e);
11508         },
11509
11510         _addPointerMove: function (obj, handler, id) {
11511                 var onMove = L.bind(function (e) {
11512                         // don't fire touch moves when mouse isn't down
11513                         if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
11514
11515                         this._handlePointer(e, handler);
11516                 }, this);
11517
11518                 obj['_leaflet_touchmove' + id] = onMove;
11519                 obj.addEventListener(this.POINTER_MOVE, onMove, false);
11520         },
11521
11522         _addPointerEnd: function (obj, handler, id) {
11523                 var onUp = L.bind(function (e) {
11524                         this._handlePointer(e, handler);
11525                 }, this);
11526
11527                 obj['_leaflet_touchend' + id] = onUp;
11528                 obj.addEventListener(this.POINTER_UP, onUp, false);
11529                 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
11530         }
11531 });
11532
11533
11534
11535 /*
11536  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
11537  */
11538
11539 // @namespace Map
11540 // @section Interaction Options
11541 L.Map.mergeOptions({
11542         // @section Touch interaction options
11543         // @option touchZoom: Boolean|String = *
11544         // Whether the map can be zoomed by touch-dragging with two fingers. If
11545         // passed `'center'`, it will zoom to the center of the view regardless of
11546         // where the touch events (fingers) were. Enabled for touch-capable web
11547         // browsers except for old Androids.
11548         touchZoom: L.Browser.touch && !L.Browser.android23,
11549
11550         // @option bounceAtZoomLimits: Boolean = true
11551         // Set it to false if you don't want the map to zoom beyond min/max zoom
11552         // and then bounce back when pinch-zooming.
11553         bounceAtZoomLimits: true
11554 });
11555
11556 L.Map.TouchZoom = L.Handler.extend({
11557         addHooks: function () {
11558                 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
11559                 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
11560         },
11561
11562         removeHooks: function () {
11563                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
11564                 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
11565         },
11566
11567         _onTouchStart: function (e) {
11568                 var map = this._map;
11569                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
11570
11571                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
11572                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
11573
11574                 this._centerPoint = map.getSize()._divideBy(2);
11575                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
11576                 if (map.options.touchZoom !== 'center') {
11577                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
11578                 }
11579
11580                 this._startDist = p1.distanceTo(p2);
11581                 this._startZoom = map.getZoom();
11582
11583                 this._moved = false;
11584                 this._zooming = true;
11585
11586                 map._stop();
11587
11588                 L.DomEvent
11589                     .on(document, 'touchmove', this._onTouchMove, this)
11590                     .on(document, 'touchend', this._onTouchEnd, this);
11591
11592                 L.DomEvent.preventDefault(e);
11593         },
11594
11595         _onTouchMove: function (e) {
11596                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
11597
11598                 var map = this._map,
11599                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
11600                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
11601                     scale = p1.distanceTo(p2) / this._startDist;
11602
11603
11604                 this._zoom = map.getScaleZoom(scale, this._startZoom);
11605
11606                 if (!map.options.bounceAtZoomLimits && (
11607                         (this._zoom < map.getMinZoom() && scale < 1) ||
11608                         (this._zoom > map.getMaxZoom() && scale > 1))) {
11609                         this._zoom = map._limitZoom(this._zoom);
11610                 }
11611
11612                 if (map.options.touchZoom === 'center') {
11613                         this._center = this._startLatLng;
11614                         if (scale === 1) { return; }
11615                 } else {
11616                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
11617                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
11618                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
11619                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
11620                 }
11621
11622                 if (!this._moved) {
11623                         map._moveStart(true);
11624                         this._moved = true;
11625                 }
11626
11627                 L.Util.cancelAnimFrame(this._animRequest);
11628
11629                 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
11630                 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
11631
11632                 L.DomEvent.preventDefault(e);
11633         },
11634
11635         _onTouchEnd: function () {
11636                 if (!this._moved || !this._zooming) {
11637                         this._zooming = false;
11638                         return;
11639                 }
11640
11641                 this._zooming = false;
11642                 L.Util.cancelAnimFrame(this._animRequest);
11643
11644                 L.DomEvent
11645                     .off(document, 'touchmove', this._onTouchMove)
11646                     .off(document, 'touchend', this._onTouchEnd);
11647
11648                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
11649                 if (this._map.options.zoomAnimation) {
11650                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
11651                 } else {
11652                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11653                 }
11654         }
11655 });
11656
11657 // @section Handlers
11658 // @property touchZoom: Handler
11659 // Touch zoom handler.
11660 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11661
11662
11663
11664 /*
11665  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11666  */
11667
11668 // @namespace Map
11669 // @section Interaction Options
11670 L.Map.mergeOptions({
11671         // @section Touch interaction options
11672         // @option tap: Boolean = true
11673         // Enables mobile hacks for supporting instant taps (fixing 200ms click
11674         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11675         tap: true,
11676
11677         // @option tapTolerance: Number = 15
11678         // The max number of pixels a user can shift his finger during touch
11679         // for it to be considered a valid tap.
11680         tapTolerance: 15
11681 });
11682
11683 L.Map.Tap = L.Handler.extend({
11684         addHooks: function () {
11685                 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11686         },
11687
11688         removeHooks: function () {
11689                 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11690         },
11691
11692         _onDown: function (e) {
11693                 if (!e.touches) { return; }
11694
11695                 L.DomEvent.preventDefault(e);
11696
11697                 this._fireClick = true;
11698
11699                 // don't simulate click or track longpress if more than 1 touch
11700                 if (e.touches.length > 1) {
11701                         this._fireClick = false;
11702                         clearTimeout(this._holdTimeout);
11703                         return;
11704                 }
11705
11706                 var first = e.touches[0],
11707                     el = first.target;
11708
11709                 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11710
11711                 // if touching a link, highlight it
11712                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11713                         L.DomUtil.addClass(el, 'leaflet-active');
11714                 }
11715
11716                 // simulate long hold but setting a timeout
11717                 this._holdTimeout = setTimeout(L.bind(function () {
11718                         if (this._isTapValid()) {
11719                                 this._fireClick = false;
11720                                 this._onUp();
11721                                 this._simulateEvent('contextmenu', first);
11722                         }
11723                 }, this), 1000);
11724
11725                 this._simulateEvent('mousedown', first);
11726
11727                 L.DomEvent.on(document, {
11728                         touchmove: this._onMove,
11729                         touchend: this._onUp
11730                 }, this);
11731         },
11732
11733         _onUp: function (e) {
11734                 clearTimeout(this._holdTimeout);
11735
11736                 L.DomEvent.off(document, {
11737                         touchmove: this._onMove,
11738                         touchend: this._onUp
11739                 }, this);
11740
11741                 if (this._fireClick && e && e.changedTouches) {
11742
11743                         var first = e.changedTouches[0],
11744                             el = first.target;
11745
11746                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11747                                 L.DomUtil.removeClass(el, 'leaflet-active');
11748                         }
11749
11750                         this._simulateEvent('mouseup', first);
11751
11752                         // simulate click if the touch didn't move too much
11753                         if (this._isTapValid()) {
11754                                 this._simulateEvent('click', first);
11755                         }
11756                 }
11757         },
11758
11759         _isTapValid: function () {
11760                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11761         },
11762
11763         _onMove: function (e) {
11764                 var first = e.touches[0];
11765                 this._newPos = new L.Point(first.clientX, first.clientY);
11766                 this._simulateEvent('mousemove', first);
11767         },
11768
11769         _simulateEvent: function (type, e) {
11770                 var simulatedEvent = document.createEvent('MouseEvents');
11771
11772                 simulatedEvent._simulated = true;
11773                 e.target._simulatedClick = true;
11774
11775                 simulatedEvent.initMouseEvent(
11776                         type, true, true, window, 1,
11777                         e.screenX, e.screenY,
11778                         e.clientX, e.clientY,
11779                         false, false, false, false, 0, null);
11780
11781                 e.target.dispatchEvent(simulatedEvent);
11782         }
11783 });
11784
11785 // @section Handlers
11786 // @property tap: Handler
11787 // Mobile touch hacks (quick tap and touch hold) handler.
11788 if (L.Browser.touch && !L.Browser.pointer) {
11789         L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11790 }
11791
11792
11793
11794 /*
11795  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11796  * (zoom to a selected bounding box), enabled by default.
11797  */
11798
11799 // @namespace Map
11800 // @section Interaction Options
11801 L.Map.mergeOptions({
11802         // @option boxZoom: Boolean = true
11803         // Whether the map can be zoomed to a rectangular area specified by
11804         // dragging the mouse while pressing the shift key.
11805         boxZoom: true
11806 });
11807
11808 L.Map.BoxZoom = L.Handler.extend({
11809         initialize: function (map) {
11810                 this._map = map;
11811                 this._container = map._container;
11812                 this._pane = map._panes.overlayPane;
11813         },
11814
11815         addHooks: function () {
11816                 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11817         },
11818
11819         removeHooks: function () {
11820                 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11821         },
11822
11823         moved: function () {
11824                 return this._moved;
11825         },
11826
11827         _resetState: function () {
11828                 this._moved = false;
11829         },
11830
11831         _onMouseDown: function (e) {
11832                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11833
11834                 this._resetState();
11835
11836                 L.DomUtil.disableTextSelection();
11837                 L.DomUtil.disableImageDrag();
11838
11839                 this._startPoint = this._map.mouseEventToContainerPoint(e);
11840
11841                 L.DomEvent.on(document, {
11842                         contextmenu: L.DomEvent.stop,
11843                         mousemove: this._onMouseMove,
11844                         mouseup: this._onMouseUp,
11845                         keydown: this._onKeyDown
11846                 }, this);
11847         },
11848
11849         _onMouseMove: function (e) {
11850                 if (!this._moved) {
11851                         this._moved = true;
11852
11853                         this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11854                         L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11855
11856                         this._map.fire('boxzoomstart');
11857                 }
11858
11859                 this._point = this._map.mouseEventToContainerPoint(e);
11860
11861                 var bounds = new L.Bounds(this._point, this._startPoint),
11862                     size = bounds.getSize();
11863
11864                 L.DomUtil.setPosition(this._box, bounds.min);
11865
11866                 this._box.style.width  = size.x + 'px';
11867                 this._box.style.height = size.y + 'px';
11868         },
11869
11870         _finish: function () {
11871                 if (this._moved) {
11872                         L.DomUtil.remove(this._box);
11873                         L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11874                 }
11875
11876                 L.DomUtil.enableTextSelection();
11877                 L.DomUtil.enableImageDrag();
11878
11879                 L.DomEvent.off(document, {
11880                         contextmenu: L.DomEvent.stop,
11881                         mousemove: this._onMouseMove,
11882                         mouseup: this._onMouseUp,
11883                         keydown: this._onKeyDown
11884                 }, this);
11885         },
11886
11887         _onMouseUp: function (e) {
11888                 if ((e.which !== 1) && (e.button !== 1)) { return; }
11889
11890                 this._finish();
11891
11892                 if (!this._moved) { return; }
11893                 // Postpone to next JS tick so internal click event handling
11894                 // still see it as "moved".
11895                 setTimeout(L.bind(this._resetState, this), 0);
11896
11897                 var bounds = new L.LatLngBounds(
11898                         this._map.containerPointToLatLng(this._startPoint),
11899                         this._map.containerPointToLatLng(this._point));
11900
11901                 this._map
11902                         .fitBounds(bounds)
11903                         .fire('boxzoomend', {boxZoomBounds: bounds});
11904         },
11905
11906         _onKeyDown: function (e) {
11907                 if (e.keyCode === 27) {
11908                         this._finish();
11909                 }
11910         }
11911 });
11912
11913 // @section Handlers
11914 // @property boxZoom: Handler
11915 // Box (shift-drag with mouse) zoom handler.
11916 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11917
11918
11919
11920 /*
11921  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11922  */
11923
11924 // @namespace Map
11925 // @section Keyboard Navigation Options
11926 L.Map.mergeOptions({
11927         // @option keyboard: Boolean = true
11928         // Makes the map focusable and allows users to navigate the map with keyboard
11929         // arrows and `+`/`-` keys.
11930         keyboard: true,
11931
11932         // @option keyboardPanDelta: Number = 80
11933         // Amount of pixels to pan when pressing an arrow key.
11934         keyboardPanDelta: 80
11935 });
11936
11937 L.Map.Keyboard = L.Handler.extend({
11938
11939         keyCodes: {
11940                 left:    [37],
11941                 right:   [39],
11942                 down:    [40],
11943                 up:      [38],
11944                 zoomIn:  [187, 107, 61, 171],
11945                 zoomOut: [189, 109, 54, 173]
11946         },
11947
11948         initialize: function (map) {
11949                 this._map = map;
11950
11951                 this._setPanDelta(map.options.keyboardPanDelta);
11952                 this._setZoomDelta(map.options.zoomDelta);
11953         },
11954
11955         addHooks: function () {
11956                 var container = this._map._container;
11957
11958                 // make the container focusable by tabbing
11959                 if (container.tabIndex <= 0) {
11960                         container.tabIndex = '0';
11961                 }
11962
11963                 L.DomEvent.on(container, {
11964                         focus: this._onFocus,
11965                         blur: this._onBlur,
11966                         mousedown: this._onMouseDown
11967                 }, this);
11968
11969                 this._map.on({
11970                         focus: this._addHooks,
11971                         blur: this._removeHooks
11972                 }, this);
11973         },
11974
11975         removeHooks: function () {
11976                 this._removeHooks();
11977
11978                 L.DomEvent.off(this._map._container, {
11979                         focus: this._onFocus,
11980                         blur: this._onBlur,
11981                         mousedown: this._onMouseDown
11982                 }, this);
11983
11984                 this._map.off({
11985                         focus: this._addHooks,
11986                         blur: this._removeHooks
11987                 }, this);
11988         },
11989
11990         _onMouseDown: function () {
11991                 if (this._focused) { return; }
11992
11993                 var body = document.body,
11994                     docEl = document.documentElement,
11995                     top = body.scrollTop || docEl.scrollTop,
11996                     left = body.scrollLeft || docEl.scrollLeft;
11997
11998                 this._map._container.focus();
11999
12000                 window.scrollTo(left, top);
12001         },
12002
12003         _onFocus: function () {
12004                 this._focused = true;
12005                 this._map.fire('focus');
12006         },
12007
12008         _onBlur: function () {
12009                 this._focused = false;
12010                 this._map.fire('blur');
12011         },
12012
12013         _setPanDelta: function (panDelta) {
12014                 var keys = this._panKeys = {},
12015                     codes = this.keyCodes,
12016                     i, len;
12017
12018                 for (i = 0, len = codes.left.length; i < len; i++) {
12019                         keys[codes.left[i]] = [-1 * panDelta, 0];
12020                 }
12021                 for (i = 0, len = codes.right.length; i < len; i++) {
12022                         keys[codes.right[i]] = [panDelta, 0];
12023                 }
12024                 for (i = 0, len = codes.down.length; i < len; i++) {
12025                         keys[codes.down[i]] = [0, panDelta];
12026                 }
12027                 for (i = 0, len = codes.up.length; i < len; i++) {
12028                         keys[codes.up[i]] = [0, -1 * panDelta];
12029                 }
12030         },
12031
12032         _setZoomDelta: function (zoomDelta) {
12033                 var keys = this._zoomKeys = {},
12034                     codes = this.keyCodes,
12035                     i, len;
12036
12037                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
12038                         keys[codes.zoomIn[i]] = zoomDelta;
12039                 }
12040                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
12041                         keys[codes.zoomOut[i]] = -zoomDelta;
12042                 }
12043         },
12044
12045         _addHooks: function () {
12046                 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
12047         },
12048
12049         _removeHooks: function () {
12050                 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
12051         },
12052
12053         _onKeyDown: function (e) {
12054                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
12055
12056                 var key = e.keyCode,
12057                     map = this._map,
12058                     offset;
12059
12060                 if (key in this._panKeys) {
12061
12062                         if (map._panAnim && map._panAnim._inProgress) { return; }
12063
12064                         offset = this._panKeys[key];
12065                         if (e.shiftKey) {
12066                                 offset = L.point(offset).multiplyBy(3);
12067                         }
12068
12069                         map.panBy(offset);
12070
12071                         if (map.options.maxBounds) {
12072                                 map.panInsideBounds(map.options.maxBounds);
12073                         }
12074
12075                 } else if (key in this._zoomKeys) {
12076                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
12077
12078                 } else if (key === 27) {
12079                         map.closePopup();
12080
12081                 } else {
12082                         return;
12083                 }
12084
12085                 L.DomEvent.stop(e);
12086         }
12087 });
12088
12089 // @section Handlers
12090 // @section Handlers
12091 // @property keyboard: Handler
12092 // Keyboard navigation handler.
12093 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
12094
12095
12096
12097 /*
12098  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
12099  */
12100
12101
12102 /* @namespace Marker
12103  * @section Interaction handlers
12104  *
12105  * 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:
12106  *
12107  * ```js
12108  * marker.dragging.disable();
12109  * ```
12110  *
12111  * @property dragging: Handler
12112  * Marker dragging handler (by both mouse and touch).
12113  */
12114
12115 L.Handler.MarkerDrag = L.Handler.extend({
12116         initialize: function (marker) {
12117                 this._marker = marker;
12118         },
12119
12120         addHooks: function () {
12121                 var icon = this._marker._icon;
12122
12123                 if (!this._draggable) {
12124                         this._draggable = new L.Draggable(icon, icon, true);
12125                 }
12126
12127                 this._draggable.on({
12128                         dragstart: this._onDragStart,
12129                         drag: this._onDrag,
12130                         dragend: this._onDragEnd
12131                 }, this).enable();
12132
12133                 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
12134         },
12135
12136         removeHooks: function () {
12137                 this._draggable.off({
12138                         dragstart: this._onDragStart,
12139                         drag: this._onDrag,
12140                         dragend: this._onDragEnd
12141                 }, this).disable();
12142
12143                 if (this._marker._icon) {
12144                         L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
12145                 }
12146         },
12147
12148         moved: function () {
12149                 return this._draggable && this._draggable._moved;
12150         },
12151
12152         _onDragStart: function () {
12153                 // @section Dragging events
12154                 // @event dragstart: Event
12155                 // Fired when the user starts dragging the marker.
12156
12157                 // @event movestart: Event
12158                 // Fired when the marker starts moving (because of dragging).
12159
12160                 this._oldLatLng = this._marker.getLatLng();
12161                 this._marker
12162                     .closePopup()
12163                     .fire('movestart')
12164                     .fire('dragstart');
12165         },
12166
12167         _onDrag: function (e) {
12168                 var marker = this._marker,
12169                     shadow = marker._shadow,
12170                     iconPos = L.DomUtil.getPosition(marker._icon),
12171                     latlng = marker._map.layerPointToLatLng(iconPos);
12172
12173                 // update shadow position
12174                 if (shadow) {
12175                         L.DomUtil.setPosition(shadow, iconPos);
12176                 }
12177
12178                 marker._latlng = latlng;
12179                 e.latlng = latlng;
12180                 e.oldLatLng = this._oldLatLng;
12181
12182                 // @event drag: Event
12183                 // Fired repeatedly while the user drags the marker.
12184                 marker
12185                     .fire('move', e)
12186                     .fire('drag', e);
12187         },
12188
12189         _onDragEnd: function (e) {
12190                 // @event dragend: DragEndEvent
12191                 // Fired when the user stops dragging the marker.
12192
12193                 // @event moveend: Event
12194                 // Fired when the marker stops moving (because of dragging).
12195                 delete this._oldLatLng;
12196                 this._marker
12197                     .fire('moveend')
12198                     .fire('dragend', e);
12199         }
12200 });
12201
12202
12203
12204 /*
12205  * @class Control
12206  * @aka L.Control
12207  * @inherits Class
12208  *
12209  * L.Control is a base class for implementing map controls. Handles positioning.
12210  * All other controls extend from this class.
12211  */
12212
12213 L.Control = L.Class.extend({
12214         // @section
12215         // @aka Control options
12216         options: {
12217                 // @option position: String = 'topright'
12218                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
12219                 // `'topright'`, `'bottomleft'` or `'bottomright'`
12220                 position: 'topright'
12221         },
12222
12223         initialize: function (options) {
12224                 L.setOptions(this, options);
12225         },
12226
12227         /* @section
12228          * Classes extending L.Control will inherit the following methods:
12229          *
12230          * @method getPosition: string
12231          * Returns the position of the control.
12232          */
12233         getPosition: function () {
12234                 return this.options.position;
12235         },
12236
12237         // @method setPosition(position: string): this
12238         // Sets the position of the control.
12239         setPosition: function (position) {
12240                 var map = this._map;
12241
12242                 if (map) {
12243                         map.removeControl(this);
12244                 }
12245
12246                 this.options.position = position;
12247
12248                 if (map) {
12249                         map.addControl(this);
12250                 }
12251
12252                 return this;
12253         },
12254
12255         // @method getContainer: HTMLElement
12256         // Returns the HTMLElement that contains the control.
12257         getContainer: function () {
12258                 return this._container;
12259         },
12260
12261         // @method addTo(map: Map): this
12262         // Adds the control to the given map.
12263         addTo: function (map) {
12264                 this.remove();
12265                 this._map = map;
12266
12267                 var container = this._container = this.onAdd(map),
12268                     pos = this.getPosition(),
12269                     corner = map._controlCorners[pos];
12270
12271                 L.DomUtil.addClass(container, 'leaflet-control');
12272
12273                 if (pos.indexOf('bottom') !== -1) {
12274                         corner.insertBefore(container, corner.firstChild);
12275                 } else {
12276                         corner.appendChild(container);
12277                 }
12278
12279                 return this;
12280         },
12281
12282         // @method remove: this
12283         // Removes the control from the map it is currently active on.
12284         remove: function () {
12285                 if (!this._map) {
12286                         return this;
12287                 }
12288
12289                 L.DomUtil.remove(this._container);
12290
12291                 if (this.onRemove) {
12292                         this.onRemove(this._map);
12293                 }
12294
12295                 this._map = null;
12296
12297                 return this;
12298         },
12299
12300         _refocusOnMap: function (e) {
12301                 // if map exists and event is not a keyboard event
12302                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
12303                         this._map.getContainer().focus();
12304                 }
12305         }
12306 });
12307
12308 L.control = function (options) {
12309         return new L.Control(options);
12310 };
12311
12312 /* @section Extension methods
12313  * @uninheritable
12314  *
12315  * Every control should extend from `L.Control` and (re-)implement the following methods.
12316  *
12317  * @method onAdd(map: Map): HTMLElement
12318  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
12319  *
12320  * @method onRemove(map: Map)
12321  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
12322  */
12323
12324 /* @namespace Map
12325  * @section Methods for Layers and Controls
12326  */
12327 L.Map.include({
12328         // @method addControl(control: Control): this
12329         // Adds the given control to the map
12330         addControl: function (control) {
12331                 control.addTo(this);
12332                 return this;
12333         },
12334
12335         // @method removeControl(control: Control): this
12336         // Removes the given control from the map
12337         removeControl: function (control) {
12338                 control.remove();
12339                 return this;
12340         },
12341
12342         _initControlPos: function () {
12343                 var corners = this._controlCorners = {},
12344                     l = 'leaflet-',
12345                     container = this._controlContainer =
12346                             L.DomUtil.create('div', l + 'control-container', this._container);
12347
12348                 function createCorner(vSide, hSide) {
12349                         var className = l + vSide + ' ' + l + hSide;
12350
12351                         corners[vSide + hSide] = L.DomUtil.create('div', className, container);
12352                 }
12353
12354                 createCorner('top', 'left');
12355                 createCorner('top', 'right');
12356                 createCorner('bottom', 'left');
12357                 createCorner('bottom', 'right');
12358         },
12359
12360         _clearControlPos: function () {
12361                 L.DomUtil.remove(this._controlContainer);
12362         }
12363 });
12364
12365
12366
12367 /*
12368  * @class Control.Zoom
12369  * @aka L.Control.Zoom
12370  * @inherits Control
12371  *
12372  * 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`.
12373  */
12374
12375 L.Control.Zoom = L.Control.extend({
12376         // @section
12377         // @aka Control.Zoom options
12378         options: {
12379                 position: 'topleft',
12380
12381                 // @option zoomInText: String = '+'
12382                 // The text set on the 'zoom in' button.
12383                 zoomInText: '+',
12384
12385                 // @option zoomInTitle: String = 'Zoom in'
12386                 // The title set on the 'zoom in' button.
12387                 zoomInTitle: 'Zoom in',
12388
12389                 // @option zoomOutText: String = '-'
12390                 // The text set on the 'zoom out' button.
12391                 zoomOutText: '-',
12392
12393                 // @option zoomOutTitle: String = 'Zoom out'
12394                 // The title set on the 'zoom out' button.
12395                 zoomOutTitle: 'Zoom out'
12396         },
12397
12398         onAdd: function (map) {
12399                 var zoomName = 'leaflet-control-zoom',
12400                     container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
12401                     options = this.options;
12402
12403                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
12404                         zoomName + '-in',  container, this._zoomIn);
12405                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
12406                         zoomName + '-out', container, this._zoomOut);
12407
12408                 this._updateDisabled();
12409                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
12410
12411                 return container;
12412         },
12413
12414         onRemove: function (map) {
12415                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
12416         },
12417
12418         disable: function () {
12419                 this._disabled = true;
12420                 this._updateDisabled();
12421                 return this;
12422         },
12423
12424         enable: function () {
12425                 this._disabled = false;
12426                 this._updateDisabled();
12427                 return this;
12428         },
12429
12430         _zoomIn: function (e) {
12431                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
12432                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12433                 }
12434         },
12435
12436         _zoomOut: function (e) {
12437                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
12438                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12439                 }
12440         },
12441
12442         _createButton: function (html, title, className, container, fn) {
12443                 var link = L.DomUtil.create('a', className, container);
12444                 link.innerHTML = html;
12445                 link.href = '#';
12446                 link.title = title;
12447
12448                 /*
12449                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
12450                  */
12451                 link.setAttribute('role', 'button');
12452                 link.setAttribute('aria-label', title);
12453
12454                 L.DomEvent
12455                     .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
12456                     .on(link, 'click', L.DomEvent.stop)
12457                     .on(link, 'click', fn, this)
12458                     .on(link, 'click', this._refocusOnMap, this);
12459
12460                 return link;
12461         },
12462
12463         _updateDisabled: function () {
12464                 var map = this._map,
12465                     className = 'leaflet-disabled';
12466
12467                 L.DomUtil.removeClass(this._zoomInButton, className);
12468                 L.DomUtil.removeClass(this._zoomOutButton, className);
12469
12470                 if (this._disabled || map._zoom === map.getMinZoom()) {
12471                         L.DomUtil.addClass(this._zoomOutButton, className);
12472                 }
12473                 if (this._disabled || map._zoom === map.getMaxZoom()) {
12474                         L.DomUtil.addClass(this._zoomInButton, className);
12475                 }
12476         }
12477 });
12478
12479 // @namespace Map
12480 // @section Control options
12481 // @option zoomControl: Boolean = true
12482 // Whether a [zoom control](#control-zoom) is added to the map by default.
12483 L.Map.mergeOptions({
12484         zoomControl: true
12485 });
12486
12487 L.Map.addInitHook(function () {
12488         if (this.options.zoomControl) {
12489                 this.zoomControl = new L.Control.Zoom();
12490                 this.addControl(this.zoomControl);
12491         }
12492 });
12493
12494 // @namespace Control.Zoom
12495 // @factory L.control.zoom(options: Control.Zoom options)
12496 // Creates a zoom control
12497 L.control.zoom = function (options) {
12498         return new L.Control.Zoom(options);
12499 };
12500
12501
12502
12503 /*
12504  * @class Control.Attribution
12505  * @aka L.Control.Attribution
12506  * @inherits Control
12507  *
12508  * 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.
12509  */
12510
12511 L.Control.Attribution = L.Control.extend({
12512         // @section
12513         // @aka Control.Attribution options
12514         options: {
12515                 position: 'bottomright',
12516
12517                 // @option prefix: String = 'Leaflet'
12518                 // The HTML text shown before the attributions. Pass `false` to disable.
12519                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
12520         },
12521
12522         initialize: function (options) {
12523                 L.setOptions(this, options);
12524
12525                 this._attributions = {};
12526         },
12527
12528         onAdd: function (map) {
12529                 map.attributionControl = this;
12530                 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
12531                 if (L.DomEvent) {
12532                         L.DomEvent.disableClickPropagation(this._container);
12533                 }
12534
12535                 // TODO ugly, refactor
12536                 for (var i in map._layers) {
12537                         if (map._layers[i].getAttribution) {
12538                                 this.addAttribution(map._layers[i].getAttribution());
12539                         }
12540                 }
12541
12542                 this._update();
12543
12544                 return this._container;
12545         },
12546
12547         // @method setPrefix(prefix: String): this
12548         // Sets the text before the attributions.
12549         setPrefix: function (prefix) {
12550                 this.options.prefix = prefix;
12551                 this._update();
12552                 return this;
12553         },
12554
12555         // @method addAttribution(text: String): this
12556         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
12557         addAttribution: function (text) {
12558                 if (!text) { return this; }
12559
12560                 if (!this._attributions[text]) {
12561                         this._attributions[text] = 0;
12562                 }
12563                 this._attributions[text]++;
12564
12565                 this._update();
12566
12567                 return this;
12568         },
12569
12570         // @method removeAttribution(text: String): this
12571         // Removes an attribution text.
12572         removeAttribution: function (text) {
12573                 if (!text) { return this; }
12574
12575                 if (this._attributions[text]) {
12576                         this._attributions[text]--;
12577                         this._update();
12578                 }
12579
12580                 return this;
12581         },
12582
12583         _update: function () {
12584                 if (!this._map) { return; }
12585
12586                 var attribs = [];
12587
12588                 for (var i in this._attributions) {
12589                         if (this._attributions[i]) {
12590                                 attribs.push(i);
12591                         }
12592                 }
12593
12594                 var prefixAndAttribs = [];
12595
12596                 if (this.options.prefix) {
12597                         prefixAndAttribs.push(this.options.prefix);
12598                 }
12599                 if (attribs.length) {
12600                         prefixAndAttribs.push(attribs.join(', '));
12601                 }
12602
12603                 this._container.innerHTML = prefixAndAttribs.join(' | ');
12604         }
12605 });
12606
12607 // @namespace Map
12608 // @section Control options
12609 // @option attributionControl: Boolean = true
12610 // Whether a [attribution control](#control-attribution) is added to the map by default.
12611 L.Map.mergeOptions({
12612         attributionControl: true
12613 });
12614
12615 L.Map.addInitHook(function () {
12616         if (this.options.attributionControl) {
12617                 new L.Control.Attribution().addTo(this);
12618         }
12619 });
12620
12621 // @namespace Control.Attribution
12622 // @factory L.control.attribution(options: Control.Attribution options)
12623 // Creates an attribution control.
12624 L.control.attribution = function (options) {
12625         return new L.Control.Attribution(options);
12626 };
12627
12628
12629
12630 /*
12631  * @class Control.Scale
12632  * @aka L.Control.Scale
12633  * @inherits Control
12634  *
12635  * 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`.
12636  *
12637  * @example
12638  *
12639  * ```js
12640  * L.control.scale().addTo(map);
12641  * ```
12642  */
12643
12644 L.Control.Scale = L.Control.extend({
12645         // @section
12646         // @aka Control.Scale options
12647         options: {
12648                 position: 'bottomleft',
12649
12650                 // @option maxWidth: Number = 100
12651                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
12652                 maxWidth: 100,
12653
12654                 // @option metric: Boolean = True
12655                 // Whether to show the metric scale line (m/km).
12656                 metric: true,
12657
12658                 // @option imperial: Boolean = True
12659                 // Whether to show the imperial scale line (mi/ft).
12660                 imperial: true
12661
12662                 // @option updateWhenIdle: Boolean = false
12663                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12664         },
12665
12666         onAdd: function (map) {
12667                 var className = 'leaflet-control-scale',
12668                     container = L.DomUtil.create('div', className),
12669                     options = this.options;
12670
12671                 this._addScales(options, className + '-line', container);
12672
12673                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12674                 map.whenReady(this._update, this);
12675
12676                 return container;
12677         },
12678
12679         onRemove: function (map) {
12680                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12681         },
12682
12683         _addScales: function (options, className, container) {
12684                 if (options.metric) {
12685                         this._mScale = L.DomUtil.create('div', className, container);
12686                 }
12687                 if (options.imperial) {
12688                         this._iScale = L.DomUtil.create('div', className, container);
12689                 }
12690         },
12691
12692         _update: function () {
12693                 var map = this._map,
12694                     y = map.getSize().y / 2;
12695
12696                 var maxMeters = map.distance(
12697                                 map.containerPointToLatLng([0, y]),
12698                                 map.containerPointToLatLng([this.options.maxWidth, y]));
12699
12700                 this._updateScales(maxMeters);
12701         },
12702
12703         _updateScales: function (maxMeters) {
12704                 if (this.options.metric && maxMeters) {
12705                         this._updateMetric(maxMeters);
12706                 }
12707                 if (this.options.imperial && maxMeters) {
12708                         this._updateImperial(maxMeters);
12709                 }
12710         },
12711
12712         _updateMetric: function (maxMeters) {
12713                 var meters = this._getRoundNum(maxMeters),
12714                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12715
12716                 this._updateScale(this._mScale, label, meters / maxMeters);
12717         },
12718
12719         _updateImperial: function (maxMeters) {
12720                 var maxFeet = maxMeters * 3.2808399,
12721                     maxMiles, miles, feet;
12722
12723                 if (maxFeet > 5280) {
12724                         maxMiles = maxFeet / 5280;
12725                         miles = this._getRoundNum(maxMiles);
12726                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12727
12728                 } else {
12729                         feet = this._getRoundNum(maxFeet);
12730                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12731                 }
12732         },
12733
12734         _updateScale: function (scale, text, ratio) {
12735                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12736                 scale.innerHTML = text;
12737         },
12738
12739         _getRoundNum: function (num) {
12740                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12741                     d = num / pow10;
12742
12743                 d = d >= 10 ? 10 :
12744                     d >= 5 ? 5 :
12745                     d >= 3 ? 3 :
12746                     d >= 2 ? 2 : 1;
12747
12748                 return pow10 * d;
12749         }
12750 });
12751
12752
12753 // @factory L.control.scale(options?: Control.Scale options)
12754 // Creates an scale control with the given options.
12755 L.control.scale = function (options) {
12756         return new L.Control.Scale(options);
12757 };
12758
12759
12760
12761 /*
12762  * @class Control.Layers
12763  * @aka L.Control.Layers
12764  * @inherits Control
12765  *
12766  * 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.html)). Extends `Control`.
12767  *
12768  * @example
12769  *
12770  * ```js
12771  * var baseLayers = {
12772  *      "Mapbox": mapbox,
12773  *      "OpenStreetMap": osm
12774  * };
12775  *
12776  * var overlays = {
12777  *      "Marker": marker,
12778  *      "Roads": roadsLayer
12779  * };
12780  *
12781  * L.control.layers(baseLayers, overlays).addTo(map);
12782  * ```
12783  *
12784  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12785  *
12786  * ```js
12787  * {
12788  *     "<someName1>": layer1,
12789  *     "<someName2>": layer2
12790  * }
12791  * ```
12792  *
12793  * The layer names can contain HTML, which allows you to add additional styling to the items:
12794  *
12795  * ```js
12796  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12797  * ```
12798  */
12799
12800
12801 L.Control.Layers = L.Control.extend({
12802         // @section
12803         // @aka Control.Layers options
12804         options: {
12805                 // @option collapsed: Boolean = true
12806                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12807                 collapsed: true,
12808                 position: 'topright',
12809
12810                 // @option autoZIndex: Boolean = true
12811                 // 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.
12812                 autoZIndex: true,
12813
12814                 // @option hideSingleBase: Boolean = false
12815                 // If `true`, the base layers in the control will be hidden when there is only one.
12816                 hideSingleBase: false,
12817
12818                 // @option sortLayers: Boolean = false
12819                 // Whether to sort the layers. When `false`, layers will keep the order
12820                 // in which they were added to the control.
12821                 sortLayers: false,
12822
12823                 // @option sortFunction: Function = *
12824                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
12825                 // that will be used for sorting the layers, when `sortLayers` is `true`.
12826                 // The function receives both the `L.Layer` instances and their names, as in
12827                 // `sortFunction(layerA, layerB, nameA, nameB)`.
12828                 // By default, it sorts layers alphabetically by their name.
12829                 sortFunction: function (layerA, layerB, nameA, nameB) {
12830                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
12831                 }
12832         },
12833
12834         initialize: function (baseLayers, overlays, options) {
12835                 L.setOptions(this, options);
12836
12837                 this._layers = [];
12838                 this._lastZIndex = 0;
12839                 this._handlingClick = false;
12840
12841                 for (var i in baseLayers) {
12842                         this._addLayer(baseLayers[i], i);
12843                 }
12844
12845                 for (i in overlays) {
12846                         this._addLayer(overlays[i], i, true);
12847                 }
12848         },
12849
12850         onAdd: function (map) {
12851                 this._initLayout();
12852                 this._update();
12853
12854                 this._map = map;
12855                 map.on('zoomend', this._checkDisabledLayers, this);
12856
12857                 return this._container;
12858         },
12859
12860         onRemove: function () {
12861                 this._map.off('zoomend', this._checkDisabledLayers, this);
12862
12863                 for (var i = 0; i < this._layers.length; i++) {
12864                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
12865                 }
12866         },
12867
12868         // @method addBaseLayer(layer: Layer, name: String): this
12869         // Adds a base layer (radio button entry) with the given name to the control.
12870         addBaseLayer: function (layer, name) {
12871                 this._addLayer(layer, name);
12872                 return (this._map) ? this._update() : this;
12873         },
12874
12875         // @method addOverlay(layer: Layer, name: String): this
12876         // Adds an overlay (checkbox entry) with the given name to the control.
12877         addOverlay: function (layer, name) {
12878                 this._addLayer(layer, name, true);
12879                 return (this._map) ? this._update() : this;
12880         },
12881
12882         // @method removeLayer(layer: Layer): this
12883         // Remove the given layer from the control.
12884         removeLayer: function (layer) {
12885                 layer.off('add remove', this._onLayerChange, this);
12886
12887                 var obj = this._getLayer(L.stamp(layer));
12888                 if (obj) {
12889                         this._layers.splice(this._layers.indexOf(obj), 1);
12890                 }
12891                 return (this._map) ? this._update() : this;
12892         },
12893
12894         // @method expand(): this
12895         // Expand the control container if collapsed.
12896         expand: function () {
12897                 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12898                 this._form.style.height = null;
12899                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12900                 if (acceptableHeight < this._form.clientHeight) {
12901                         L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12902                         this._form.style.height = acceptableHeight + 'px';
12903                 } else {
12904                         L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12905                 }
12906                 this._checkDisabledLayers();
12907                 return this;
12908         },
12909
12910         // @method collapse(): this
12911         // Collapse the control container if expanded.
12912         collapse: function () {
12913                 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12914                 return this;
12915         },
12916
12917         _initLayout: function () {
12918                 var className = 'leaflet-control-layers',
12919                     container = this._container = L.DomUtil.create('div', className);
12920
12921                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
12922                 container.setAttribute('aria-haspopup', true);
12923
12924                 L.DomEvent.disableClickPropagation(container);
12925                 if (!L.Browser.touch) {
12926                         L.DomEvent.disableScrollPropagation(container);
12927                 }
12928
12929                 var form = this._form = L.DomUtil.create('form', className + '-list');
12930
12931                 if (!L.Browser.android) {
12932                         L.DomEvent.on(container, {
12933                                 mouseenter: this.expand,
12934                                 mouseleave: this.collapse
12935                         }, this);
12936                 }
12937
12938                 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
12939                 link.href = '#';
12940                 link.title = 'Layers';
12941
12942                 if (L.Browser.touch) {
12943                         L.DomEvent
12944                             .on(link, 'click', L.DomEvent.stop)
12945                             .on(link, 'click', this.expand, this);
12946                 } else {
12947                         L.DomEvent.on(link, 'focus', this.expand, this);
12948                 }
12949
12950                 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
12951                 L.DomEvent.on(form, 'click', function () {
12952                         setTimeout(L.bind(this._onInputClick, this), 0);
12953                 }, this);
12954
12955                 this._map.on('click', this.collapse, this);
12956                 // TODO keyboard accessibility
12957
12958                 if (!this.options.collapsed) {
12959                         this.expand();
12960                 }
12961
12962                 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
12963                 this._separator = L.DomUtil.create('div', className + '-separator', form);
12964                 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
12965
12966                 container.appendChild(form);
12967         },
12968
12969         _getLayer: function (id) {
12970                 for (var i = 0; i < this._layers.length; i++) {
12971
12972                         if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
12973                                 return this._layers[i];
12974                         }
12975                 }
12976         },
12977
12978         _addLayer: function (layer, name, overlay) {
12979                 layer.on('add remove', this._onLayerChange, this);
12980
12981                 this._layers.push({
12982                         layer: layer,
12983                         name: name,
12984                         overlay: overlay
12985                 });
12986
12987                 if (this.options.sortLayers) {
12988                         this._layers.sort(L.bind(function (a, b) {
12989                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
12990                         }, this));
12991                 }
12992
12993                 if (this.options.autoZIndex && layer.setZIndex) {
12994                         this._lastZIndex++;
12995                         layer.setZIndex(this._lastZIndex);
12996                 }
12997         },
12998
12999         _update: function () {
13000                 if (!this._container) { return this; }
13001
13002                 L.DomUtil.empty(this._baseLayersList);
13003                 L.DomUtil.empty(this._overlaysList);
13004
13005                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
13006
13007                 for (i = 0; i < this._layers.length; i++) {
13008                         obj = this._layers[i];
13009                         this._addItem(obj);
13010                         overlaysPresent = overlaysPresent || obj.overlay;
13011                         baseLayersPresent = baseLayersPresent || !obj.overlay;
13012                         baseLayersCount += !obj.overlay ? 1 : 0;
13013                 }
13014
13015                 // Hide base layers section if there's only one layer.
13016                 if (this.options.hideSingleBase) {
13017                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
13018                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
13019                 }
13020
13021                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
13022
13023                 return this;
13024         },
13025
13026         _onLayerChange: function (e) {
13027                 if (!this._handlingClick) {
13028                         this._update();
13029                 }
13030
13031                 var obj = this._getLayer(L.stamp(e.target));
13032
13033                 // @namespace Map
13034                 // @section Layer events
13035                 // @event baselayerchange: LayersControlEvent
13036                 // Fired when the base layer is changed through the [layer control](#control-layers).
13037                 // @event overlayadd: LayersControlEvent
13038                 // Fired when an overlay is selected through the [layer control](#control-layers).
13039                 // @event overlayremove: LayersControlEvent
13040                 // Fired when an overlay is deselected through the [layer control](#control-layers).
13041                 // @namespace Control.Layers
13042                 var type = obj.overlay ?
13043                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
13044                         (e.type === 'add' ? 'baselayerchange' : null);
13045
13046                 if (type) {
13047                         this._map.fire(type, obj);
13048                 }
13049         },
13050
13051         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
13052         _createRadioElement: function (name, checked) {
13053
13054                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
13055                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
13056
13057                 var radioFragment = document.createElement('div');
13058                 radioFragment.innerHTML = radioHtml;
13059
13060                 return radioFragment.firstChild;
13061         },
13062
13063         _addItem: function (obj) {
13064                 var label = document.createElement('label'),
13065                     checked = this._map.hasLayer(obj.layer),
13066                     input;
13067
13068                 if (obj.overlay) {
13069                         input = document.createElement('input');
13070                         input.type = 'checkbox';
13071                         input.className = 'leaflet-control-layers-selector';
13072                         input.defaultChecked = checked;
13073                 } else {
13074                         input = this._createRadioElement('leaflet-base-layers', checked);
13075                 }
13076
13077                 input.layerId = L.stamp(obj.layer);
13078
13079                 L.DomEvent.on(input, 'click', this._onInputClick, this);
13080
13081                 var name = document.createElement('span');
13082                 name.innerHTML = ' ' + obj.name;
13083
13084                 // Helps from preventing layer control flicker when checkboxes are disabled
13085                 // https://github.com/Leaflet/Leaflet/issues/2771
13086                 var holder = document.createElement('div');
13087
13088                 label.appendChild(holder);
13089                 holder.appendChild(input);
13090                 holder.appendChild(name);
13091
13092                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
13093                 container.appendChild(label);
13094
13095                 this._checkDisabledLayers();
13096                 return label;
13097         },
13098
13099         _onInputClick: function () {
13100                 var inputs = this._form.getElementsByTagName('input'),
13101                     input, layer, hasLayer;
13102                 var addedLayers = [],
13103                     removedLayers = [];
13104
13105                 this._handlingClick = true;
13106
13107                 for (var i = inputs.length - 1; i >= 0; i--) {
13108                         input = inputs[i];
13109                         layer = this._getLayer(input.layerId).layer;
13110                         hasLayer = this._map.hasLayer(layer);
13111
13112                         if (input.checked && !hasLayer) {
13113                                 addedLayers.push(layer);
13114
13115                         } else if (!input.checked && hasLayer) {
13116                                 removedLayers.push(layer);
13117                         }
13118                 }
13119
13120                 // Bugfix issue 2318: Should remove all old layers before readding new ones
13121                 for (i = 0; i < removedLayers.length; i++) {
13122                         this._map.removeLayer(removedLayers[i]);
13123                 }
13124                 for (i = 0; i < addedLayers.length; i++) {
13125                         this._map.addLayer(addedLayers[i]);
13126                 }
13127
13128                 this._handlingClick = false;
13129
13130                 this._refocusOnMap();
13131         },
13132
13133         _checkDisabledLayers: function () {
13134                 var inputs = this._form.getElementsByTagName('input'),
13135                     input,
13136                     layer,
13137                     zoom = this._map.getZoom();
13138
13139                 for (var i = inputs.length - 1; i >= 0; i--) {
13140                         input = inputs[i];
13141                         layer = this._getLayer(input.layerId).layer;
13142                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
13143                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
13144
13145                 }
13146         },
13147
13148         _expand: function () {
13149                 // Backward compatibility, remove me in 1.1.
13150                 return this.expand();
13151         },
13152
13153         _collapse: function () {
13154                 // Backward compatibility, remove me in 1.1.
13155                 return this.collapse();
13156         }
13157
13158 });
13159
13160
13161 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
13162 // 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.
13163 L.control.layers = function (baseLayers, overlays, options) {
13164         return new L.Control.Layers(baseLayers, overlays, options);
13165 };
13166
13167
13168
13169 }(window, document));
13170 //# sourceMappingURL=leaflet-src.map