Bug 25858: Use bitwise OR for setting a bit in borrowers.flag
[koha.git] / koha-tmpl / intranet-tmpl / lib / leaflet / leaflet-src.js
blobd416065f2d2f7bd278062dc31bd734e6c78a4314
1 /*
2  Leaflet 1.0.3+ed36a04, 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.3+ed36a04"
8 };
10 function expose() {
11         var oldL = window.L;
13         L.noConflict = function () {
14                 window.L = oldL;
15                 return this;
16         };
18         window.L = L;
21 // define Leaflet for Node module pattern loaders, including Browserify
22 if (typeof module === 'object' && typeof module.exports === 'object') {
23         module.exports = L;
25 // define Leaflet as an AMD module
26 } else if (typeof define === 'function' && define.amd) {
27         define(L);
30 // define Leaflet as a global L variable, saving the original L to restore later if needed
31 if (typeof window !== 'undefined') {
32         expose();
38  * @namespace Util
39  *
40  * Various utility functions, used by Leaflet internally.
41  */
43 L.Util = {
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;
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         },
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         })(),
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;
75                 if (fn.bind) {
76                         return fn.bind.apply(fn, slice.call(arguments, 1));
77                 }
79                 var args = slice.call(arguments, 2);
81                 return function () {
82                         return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
83                 };
84         },
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         },
95         // @property lastId: Number
96         // Last unique ID used by [`stamp()`](#util-stamp)
97         lastId: 0,
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;
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                 };
118                 wrapperFn = function () {
119                         if (lock) {
120                                 // called too soon, queue to call later
121                                 args = arguments;
123                         } else {
124                                 // call and lock until later
125                                 fn.apply(context, arguments);
126                                 setTimeout(later, time);
127                                 lock = true;
128                         }
129                 };
131                 return wrapperFn;
132         },
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         },
145         // @function falseFn(): Function
146         // Returns a function which always returns `false`.
147         falseFn: function () { return false; },
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         },
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         },
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         },
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         },
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         },
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];
202                         if (value === undefined) {
203                                 throw new Error('No value provided for variable ' + str);
205                         } else if (typeof value === 'function') {
206                                 value = value(data);
207                         }
208                         return value;
209                 });
210         },
212         templateRe: /\{ *([\w_\-]+) *\}/g,
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         },
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         },
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='
236 (function () {
237         // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
239         function getPrefixed(name) {
240                 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
241         }
243         var lastTime = 0;
245         // fallback for IE 7-8
246         function timeoutDefer(fn) {
247                 var time = +new Date(),
248                     timeToCall = Math.max(0, 16 - (time - lastTime));
250                 lastTime = time + timeToCall;
251                 return window.setTimeout(fn, timeToCall);
252         }
254         var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255             cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
256                        getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
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         };
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 })();
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;
291 // @class Class
292 // @aka L.Class
294 // @section
295 // @uninheritable
297 // Thanks to John Resig and Dean Edwards for inspiration!
299 L.Class = function () {};
301 L.Class.extend = function (props) {
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 () {
308                 // call the constructor
309                 if (this.initialize) {
310                         this.initialize.apply(this, arguments);
311                 }
313                 // call all constructor hooks
314                 this.callInitHooks();
315         };
317         var parentProto = NewClass.__super__ = this.prototype;
319         var proto = L.Util.create(parentProto);
320         proto.constructor = NewClass;
322         NewClass.prototype = proto;
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         }
331         // mix static properties into the class
332         if (props.statics) {
333                 L.extend(NewClass, props.statics);
334                 delete props.statics;
335         }
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         }
343         // merge options
344         if (proto.options) {
345                 props.options = L.Util.extend(L.Util.create(proto.options), props.options);
346         }
348         // mix given properties into the prototype
349         L.extend(proto, props);
351         proto._initHooks = [];
353         // add method for calling all hooks
354         proto.callInitHooks = function () {
356                 if (this._initHooksCalled) { return; }
358                 if (parentProto.callInitHooks) {
359                         parentProto.callInitHooks.call(this);
360                 }
362                 this._initHooksCalled = true;
364                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
365                         proto._initHooks[i].call(this);
366                 }
367         };
369         return NewClass;
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;
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;
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);
392         var init = typeof fn === 'function' ? fn : function () {
393                 this[fn].apply(this, args);
394         };
396         this.prototype._initHooks = this.prototype._initHooks || [];
397         this.prototype._initHooks.push(init);
398         return this;
404  * @class Evented
405  * @aka L.Evented
406  * @inherits Class
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).
410  * @example
412  * ```js
413  * map.on('click', function(e) {
414  *      alert(e.latlng);
415  * } );
416  * ```
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:
420  * ```js
421  * function onClick(e) { ... }
423  * map.on('click', onClick);
424  * map.off('click', onClick);
425  * ```
426  */
429 L.Evented = L.Class.extend({
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) {
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                         }
448                 } else {
449                         // types can be a string of space-separated words
450                         types = L.Util.splitWords(types);
452                         for (var i = 0, len = types.length; i < len; i++) {
453                                 this._on(types[i], fn, context);
454                         }
455                 }
457                 return this;
458         },
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) {
473                 if (!types) {
474                         // clear all listeners if called without arguments
475                         delete this._events;
477                 } else if (typeof types === 'object') {
478                         for (var type in types) {
479                                 this._off(type, types[type], fn);
480                         }
482                 } else {
483                         types = L.Util.splitWords(types);
485                         for (var i = 0, len = types.length; i < len; i++) {
486                                 this._off(types[i], fn, context);
487                         }
488                 }
490                 return this;
491         },
493         // attach listener (without syntactic sugar now)
494         _on: function (type, fn, context) {
495                 this._events = this._events || {};
497                 /* get/init listeners for type */
498                 var typeListeners = this._events[type];
499                 if (!typeListeners) {
500                         typeListeners = [];
501                         this._events[type] = typeListeners;
502                 }
504                 if (context === this) {
505                         // Less memory footprint.
506                         context = undefined;
507                 }
508                 var newListener = {fn: fn, ctx: context},
509                     listeners = typeListeners;
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                 }
518                 listeners.push(newListener);
519         },
521         _off: function (type, fn, context) {
522                 var listeners,
523                     i,
524                     len;
526                 if (!this._events) { return; }
528                 listeners = this._events[type];
530                 if (!listeners) {
531                         return;
532                 }
534                 if (!fn) {
535                         // Set all removed listeners to noop so they are not called if remove happens in fire
536                         for (i = 0, len = listeners.length; i < len; i++) {
537                                 listeners[i].fn = L.Util.falseFn;
538                         }
539                         // clear all listeners for a type if function isn't specified
540                         delete this._events[type];
541                         return;
542                 }
544                 if (context === this) {
545                         context = undefined;
546                 }
548                 if (listeners) {
550                         // find fn and remove it
551                         for (i = 0, len = listeners.length; i < len; i++) {
552                                 var l = listeners[i];
553                                 if (l.ctx !== context) { continue; }
554                                 if (l.fn === fn) {
556                                         // set the removed listener to noop so that's not called if remove happens in fire
557                                         l.fn = L.Util.falseFn;
559                                         if (this._firingCount) {
560                                                 /* copy array in case events are being fired */
561                                                 this._events[type] = listeners = listeners.slice();
562                                         }
563                                         listeners.splice(i, 1);
565                                         return;
566                                 }
567                         }
568                 }
569         },
571         // @method fire(type: String, data?: Object, propagate?: Boolean): this
572         // Fires an event of the specified type. You can optionally provide an data
573         // object — the first argument of the listener function will contain its
574         // properties. The event can optionally be propagated to event parents.
575         fire: function (type, data, propagate) {
576                 if (!this.listens(type, propagate)) { return this; }
578                 var event = L.Util.extend({}, data, {type: type, target: this});
580                 if (this._events) {
581                         var listeners = this._events[type];
583                         if (listeners) {
584                                 this._firingCount = (this._firingCount + 1) || 1;
585                                 for (var i = 0, len = listeners.length; i < len; i++) {
586                                         var l = listeners[i];
587                                         l.fn.call(l.ctx || this, event);
588                                 }
590                                 this._firingCount--;
591                         }
592                 }
594                 if (propagate) {
595                         // propagate the event to parents (set with addEventParent)
596                         this._propagateEvent(event);
597                 }
599                 return this;
600         },
602         // @method listens(type: String): Boolean
603         // Returns `true` if a particular event type has any listeners attached to it.
604         listens: function (type, propagate) {
605                 var listeners = this._events && this._events[type];
606                 if (listeners && listeners.length) { return true; }
608                 if (propagate) {
609                         // also check parents for listeners if event propagates
610                         for (var id in this._eventParents) {
611                                 if (this._eventParents[id].listens(type, propagate)) { return true; }
612                         }
613                 }
614                 return false;
615         },
617         // @method once(…): this
618         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
619         once: function (types, fn, context) {
621                 if (typeof types === 'object') {
622                         for (var type in types) {
623                                 this.once(type, types[type], fn);
624                         }
625                         return this;
626                 }
628                 var handler = L.bind(function () {
629                         this
630                             .off(types, fn, context)
631                             .off(types, handler, context);
632                 }, this);
634                 // add a listener that's executed once and removed after that
635                 return this
636                     .on(types, fn, context)
637                     .on(types, handler, context);
638         },
640         // @method addEventParent(obj: Evented): this
641         // Adds an event parent - an `Evented` that will receive propagated events
642         addEventParent: function (obj) {
643                 this._eventParents = this._eventParents || {};
644                 this._eventParents[L.stamp(obj)] = obj;
645                 return this;
646         },
648         // @method removeEventParent(obj: Evented): this
649         // Removes an event parent, so it will stop receiving propagated events
650         removeEventParent: function (obj) {
651                 if (this._eventParents) {
652                         delete this._eventParents[L.stamp(obj)];
653                 }
654                 return this;
655         },
657         _propagateEvent: function (e) {
658                 for (var id in this._eventParents) {
659                         this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
660                 }
661         }
664 var proto = L.Evented.prototype;
666 // aliases; we should ditch those eventually
668 // @method addEventListener(…): this
669 // Alias to [`on(…)`](#evented-on)
670 proto.addEventListener = proto.on;
672 // @method removeEventListener(…): this
673 // Alias to [`off(…)`](#evented-off)
675 // @method clearAllEventListeners(…): this
676 // Alias to [`off()`](#evented-off)
677 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
679 // @method addOneTimeEventListener(…): this
680 // Alias to [`once(…)`](#evented-once)
681 proto.addOneTimeEventListener = proto.once;
683 // @method fireEvent(…): this
684 // Alias to [`fire(…)`](#evented-fire)
685 proto.fireEvent = proto.fire;
687 // @method hasEventListeners(…): Boolean
688 // Alias to [`listens(…)`](#evented-listens)
689 proto.hasEventListeners = proto.listens;
691 L.Mixin = {Events: proto};
696  * @namespace Browser
697  * @aka L.Browser
699  * A namespace with static properties for browser/feature detection used by Leaflet internally.
701  * @example
703  * ```js
704  * if (L.Browser.ielt9) {
705  *   alert('Upgrade your browser, dude!');
706  * }
707  * ```
708  */
710 (function () {
712         var ua = navigator.userAgent.toLowerCase(),
713             doc = document.documentElement,
715             ie = 'ActiveXObject' in window,
717             webkit    = ua.indexOf('webkit') !== -1,
718             phantomjs = ua.indexOf('phantom') !== -1,
719             android23 = ua.search('android [23]') !== -1,
720             chrome    = ua.indexOf('chrome') !== -1,
721             gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,
723             win = navigator.platform.indexOf('Win') === 0,
725             mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
726             msPointer = !window.PointerEvent && window.MSPointerEvent,
727             pointer = window.PointerEvent || msPointer,
729             ie3d = ie && ('transition' in doc.style),
730             webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
731             gecko3d = 'MozPerspective' in doc.style,
732             opera12 = 'OTransition' in doc.style;
735         var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
736                         (window.DocumentTouch && document instanceof window.DocumentTouch));
738         L.Browser = {
740                 // @property ie: Boolean
741                 // `true` for all Internet Explorer versions (not Edge).
742                 ie: ie,
744                 // @property ielt9: Boolean
745                 // `true` for Internet Explorer versions less than 9.
746                 ielt9: ie && !document.addEventListener,
748                 // @property edge: Boolean
749                 // `true` for the Edge web browser.
750                 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
752                 // @property webkit: Boolean
753                 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
754                 webkit: webkit,
756                 // @property gecko: Boolean
757                 // `true` for gecko-based browsers like Firefox.
758                 gecko: gecko,
760                 // @property android: Boolean
761                 // `true` for any browser running on an Android platform.
762                 android: ua.indexOf('android') !== -1,
764                 // @property android23: Boolean
765                 // `true` for browsers running on Android 2 or Android 3.
766                 android23: android23,
768                 // @property chrome: Boolean
769                 // `true` for the Chrome browser.
770                 chrome: chrome,
772                 // @property safari: Boolean
773                 // `true` for the Safari browser.
774                 safari: !chrome && ua.indexOf('safari') !== -1,
777                 // @property win: Boolean
778                 // `true` when the browser is running in a Windows platform
779                 win: win,
782                 // @property ie3d: Boolean
783                 // `true` for all Internet Explorer versions supporting CSS transforms.
784                 ie3d: ie3d,
786                 // @property webkit3d: Boolean
787                 // `true` for webkit-based browsers supporting CSS transforms.
788                 webkit3d: webkit3d,
790                 // @property gecko3d: Boolean
791                 // `true` for gecko-based browsers supporting CSS transforms.
792                 gecko3d: gecko3d,
794                 // @property opera12: Boolean
795                 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
796                 opera12: opera12,
798                 // @property any3d: Boolean
799                 // `true` for all browsers supporting CSS transforms.
800                 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
803                 // @property mobile: Boolean
804                 // `true` for all browsers running in a mobile device.
805                 mobile: mobile,
807                 // @property mobileWebkit: Boolean
808                 // `true` for all webkit-based browsers in a mobile device.
809                 mobileWebkit: mobile && webkit,
811                 // @property mobileWebkit3d: Boolean
812                 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
813                 mobileWebkit3d: mobile && webkit3d,
815                 // @property mobileOpera: Boolean
816                 // `true` for the Opera browser in a mobile device.
817                 mobileOpera: mobile && window.opera,
819                 // @property mobileGecko: Boolean
820                 // `true` for gecko-based browsers running in a mobile device.
821                 mobileGecko: mobile && gecko,
824                 // @property touch: Boolean
825                 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
826                 // This does not necessarily mean that the browser is running in a computer with
827                 // a touchscreen, it only means that the browser is capable of understanding
828                 // touch events.
829                 touch: !!touch,
831                 // @property msPointer: Boolean
832                 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
833                 msPointer: !!msPointer,
835                 // @property pointer: Boolean
836                 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
837                 pointer: !!pointer,
840                 // @property retina: Boolean
841                 // `true` for browsers on a high-resolution "retina" screen.
842                 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
843         };
845 }());
850  * @class Point
851  * @aka L.Point
853  * Represents a point with `x` and `y` coordinates in pixels.
855  * @example
857  * ```js
858  * var point = L.point(200, 300);
859  * ```
861  * 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:
863  * ```js
864  * map.panBy([200, 300]);
865  * map.panBy(L.point(200, 300));
866  * ```
867  */
869 L.Point = function (x, y, round) {
870         // @property x: Number; The `x` coordinate of the point
871         this.x = (round ? Math.round(x) : x);
872         // @property y: Number; The `y` coordinate of the point
873         this.y = (round ? Math.round(y) : y);
876 L.Point.prototype = {
878         // @method clone(): Point
879         // Returns a copy of the current point.
880         clone: function () {
881                 return new L.Point(this.x, this.y);
882         },
884         // @method add(otherPoint: Point): Point
885         // Returns the result of addition of the current and the given points.
886         add: function (point) {
887                 // non-destructive, returns a new point
888                 return this.clone()._add(L.point(point));
889         },
891         _add: function (point) {
892                 // destructive, used directly for performance in situations where it's safe to modify existing point
893                 this.x += point.x;
894                 this.y += point.y;
895                 return this;
896         },
898         // @method subtract(otherPoint: Point): Point
899         // Returns the result of subtraction of the given point from the current.
900         subtract: function (point) {
901                 return this.clone()._subtract(L.point(point));
902         },
904         _subtract: function (point) {
905                 this.x -= point.x;
906                 this.y -= point.y;
907                 return this;
908         },
910         // @method divideBy(num: Number): Point
911         // Returns the result of division of the current point by the given number.
912         divideBy: function (num) {
913                 return this.clone()._divideBy(num);
914         },
916         _divideBy: function (num) {
917                 this.x /= num;
918                 this.y /= num;
919                 return this;
920         },
922         // @method multiplyBy(num: Number): Point
923         // Returns the result of multiplication of the current point by the given number.
924         multiplyBy: function (num) {
925                 return this.clone()._multiplyBy(num);
926         },
928         _multiplyBy: function (num) {
929                 this.x *= num;
930                 this.y *= num;
931                 return this;
932         },
934         // @method scaleBy(scale: Point): Point
935         // Multiply each coordinate of the current point by each coordinate of
936         // `scale`. In linear algebra terms, multiply the point by the
937         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
938         // defined by `scale`.
939         scaleBy: function (point) {
940                 return new L.Point(this.x * point.x, this.y * point.y);
941         },
943         // @method unscaleBy(scale: Point): Point
944         // Inverse of `scaleBy`. Divide each coordinate of the current point by
945         // each coordinate of `scale`.
946         unscaleBy: function (point) {
947                 return new L.Point(this.x / point.x, this.y / point.y);
948         },
950         // @method round(): Point
951         // Returns a copy of the current point with rounded coordinates.
952         round: function () {
953                 return this.clone()._round();
954         },
956         _round: function () {
957                 this.x = Math.round(this.x);
958                 this.y = Math.round(this.y);
959                 return this;
960         },
962         // @method floor(): Point
963         // Returns a copy of the current point with floored coordinates (rounded down).
964         floor: function () {
965                 return this.clone()._floor();
966         },
968         _floor: function () {
969                 this.x = Math.floor(this.x);
970                 this.y = Math.floor(this.y);
971                 return this;
972         },
974         // @method ceil(): Point
975         // Returns a copy of the current point with ceiled coordinates (rounded up).
976         ceil: function () {
977                 return this.clone()._ceil();
978         },
980         _ceil: function () {
981                 this.x = Math.ceil(this.x);
982                 this.y = Math.ceil(this.y);
983                 return this;
984         },
986         // @method distanceTo(otherPoint: Point): Number
987         // Returns the cartesian distance between the current and the given points.
988         distanceTo: function (point) {
989                 point = L.point(point);
991                 var x = point.x - this.x,
992                     y = point.y - this.y;
994                 return Math.sqrt(x * x + y * y);
995         },
997         // @method equals(otherPoint: Point): Boolean
998         // Returns `true` if the given point has the same coordinates.
999         equals: function (point) {
1000                 point = L.point(point);
1002                 return point.x === this.x &&
1003                        point.y === this.y;
1004         },
1006         // @method contains(otherPoint: Point): Boolean
1007         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1008         contains: function (point) {
1009                 point = L.point(point);
1011                 return Math.abs(point.x) <= Math.abs(this.x) &&
1012                        Math.abs(point.y) <= Math.abs(this.y);
1013         },
1015         // @method toString(): String
1016         // Returns a string representation of the point for debugging purposes.
1017         toString: function () {
1018                 return 'Point(' +
1019                         L.Util.formatNum(this.x) + ', ' +
1020                         L.Util.formatNum(this.y) + ')';
1021         }
1024 // @factory L.point(x: Number, y: Number, round?: Boolean)
1025 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1027 // @alternative
1028 // @factory L.point(coords: Number[])
1029 // Expects an array of the form `[x, y]` instead.
1031 // @alternative
1032 // @factory L.point(coords: Object)
1033 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1034 L.point = function (x, y, round) {
1035         if (x instanceof L.Point) {
1036                 return x;
1037         }
1038         if (L.Util.isArray(x)) {
1039                 return new L.Point(x[0], x[1]);
1040         }
1041         if (x === undefined || x === null) {
1042                 return x;
1043         }
1044         if (typeof x === 'object' && 'x' in x && 'y' in x) {
1045                 return new L.Point(x.x, x.y);
1046         }
1047         return new L.Point(x, y, round);
1053  * @class Bounds
1054  * @aka L.Bounds
1056  * Represents a rectangular area in pixel coordinates.
1058  * @example
1060  * ```js
1061  * var p1 = L.point(10, 10),
1062  * p2 = L.point(40, 60),
1063  * bounds = L.bounds(p1, p2);
1064  * ```
1066  * 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:
1068  * ```js
1069  * otherBounds.intersects([[10, 10], [40, 60]]);
1070  * ```
1071  */
1073 L.Bounds = function (a, b) {
1074         if (!a) { return; }
1076         var points = b ? [a, b] : a;
1078         for (var i = 0, len = points.length; i < len; i++) {
1079                 this.extend(points[i]);
1080         }
1083 L.Bounds.prototype = {
1084         // @method extend(point: Point): this
1085         // Extends the bounds to contain the given point.
1086         extend: function (point) { // (Point)
1087                 point = L.point(point);
1089                 // @property min: Point
1090                 // The top left corner of the rectangle.
1091                 // @property max: Point
1092                 // The bottom right corner of the rectangle.
1093                 if (!this.min && !this.max) {
1094                         this.min = point.clone();
1095                         this.max = point.clone();
1096                 } else {
1097                         this.min.x = Math.min(point.x, this.min.x);
1098                         this.max.x = Math.max(point.x, this.max.x);
1099                         this.min.y = Math.min(point.y, this.min.y);
1100                         this.max.y = Math.max(point.y, this.max.y);
1101                 }
1102                 return this;
1103         },
1105         // @method getCenter(round?: Boolean): Point
1106         // Returns the center point of the bounds.
1107         getCenter: function (round) {
1108                 return new L.Point(
1109                         (this.min.x + this.max.x) / 2,
1110                         (this.min.y + this.max.y) / 2, round);
1111         },
1113         // @method getBottomLeft(): Point
1114         // Returns the bottom-left point of the bounds.
1115         getBottomLeft: function () {
1116                 return new L.Point(this.min.x, this.max.y);
1117         },
1119         // @method getTopRight(): Point
1120         // Returns the top-right point of the bounds.
1121         getTopRight: function () { // -> Point
1122                 return new L.Point(this.max.x, this.min.y);
1123         },
1125         // @method getSize(): Point
1126         // Returns the size of the given bounds
1127         getSize: function () {
1128                 return this.max.subtract(this.min);
1129         },
1131         // @method contains(otherBounds: Bounds): Boolean
1132         // Returns `true` if the rectangle contains the given one.
1133         // @alternative
1134         // @method contains(point: Point): Boolean
1135         // Returns `true` if the rectangle contains the given point.
1136         contains: function (obj) {
1137                 var min, max;
1139                 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1140                         obj = L.point(obj);
1141                 } else {
1142                         obj = L.bounds(obj);
1143                 }
1145                 if (obj instanceof L.Bounds) {
1146                         min = obj.min;
1147                         max = obj.max;
1148                 } else {
1149                         min = max = obj;
1150                 }
1152                 return (min.x >= this.min.x) &&
1153                        (max.x <= this.max.x) &&
1154                        (min.y >= this.min.y) &&
1155                        (max.y <= this.max.y);
1156         },
1158         // @method intersects(otherBounds: Bounds): Boolean
1159         // Returns `true` if the rectangle intersects the given bounds. Two bounds
1160         // intersect if they have at least one point in common.
1161         intersects: function (bounds) { // (Bounds) -> Boolean
1162                 bounds = L.bounds(bounds);
1164                 var min = this.min,
1165                     max = this.max,
1166                     min2 = bounds.min,
1167                     max2 = bounds.max,
1168                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1169                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1171                 return xIntersects && yIntersects;
1172         },
1174         // @method overlaps(otherBounds: Bounds): Boolean
1175         // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1176         // overlap if their intersection is an area.
1177         overlaps: function (bounds) { // (Bounds) -> Boolean
1178                 bounds = L.bounds(bounds);
1180                 var min = this.min,
1181                     max = this.max,
1182                     min2 = bounds.min,
1183                     max2 = bounds.max,
1184                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1185                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1187                 return xOverlaps && yOverlaps;
1188         },
1190         isValid: function () {
1191                 return !!(this.min && this.max);
1192         }
1196 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1197 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1198 // @alternative
1199 // @factory L.bounds(points: Point[])
1200 // Creates a Bounds object from the points it contains
1201 L.bounds = function (a, b) {
1202         if (!a || a instanceof L.Bounds) {
1203                 return a;
1204         }
1205         return new L.Bounds(a, b);
1211  * @class Transformation
1212  * @aka L.Transformation
1214  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1215  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1216  * the reverse. Used by Leaflet in its projections code.
1218  * @example
1220  * ```js
1221  * var transformation = new L.Transformation(2, 5, -1, 10),
1222  *      p = L.point(1, 2),
1223  *      p2 = transformation.transform(p), //  L.point(7, 8)
1224  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1225  * ```
1226  */
1229 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1230 // Creates a `Transformation` object with the given coefficients.
1231 L.Transformation = function (a, b, c, d) {
1232         this._a = a;
1233         this._b = b;
1234         this._c = c;
1235         this._d = d;
1238 L.Transformation.prototype = {
1239         // @method transform(point: Point, scale?: Number): Point
1240         // Returns a transformed point, optionally multiplied by the given scale.
1241         // Only accepts actual `L.Point` instances, not arrays.
1242         transform: function (point, scale) { // (Point, Number) -> Point
1243                 return this._transform(point.clone(), scale);
1244         },
1246         // destructive transform (faster)
1247         _transform: function (point, scale) {
1248                 scale = scale || 1;
1249                 point.x = scale * (this._a * point.x + this._b);
1250                 point.y = scale * (this._c * point.y + this._d);
1251                 return point;
1252         },
1254         // @method untransform(point: Point, scale?: Number): Point
1255         // Returns the reverse transformation of the given point, optionally divided
1256         // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1257         untransform: function (point, scale) {
1258                 scale = scale || 1;
1259                 return new L.Point(
1260                         (point.x / scale - this._b) / this._a,
1261                         (point.y / scale - this._d) / this._c);
1262         }
1268  * @namespace DomUtil
1270  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1271  * tree, used by Leaflet internally.
1273  * Most functions expecting or returning a `HTMLElement` also work for
1274  * SVG elements. The only difference is that classes refer to CSS classes
1275  * in HTML and SVG classes in SVG.
1276  */
1278 L.DomUtil = {
1280         // @function get(id: String|HTMLElement): HTMLElement
1281         // Returns an element given its DOM id, or returns the element itself
1282         // if it was passed directly.
1283         get: function (id) {
1284                 return typeof id === 'string' ? document.getElementById(id) : id;
1285         },
1287         // @function getStyle(el: HTMLElement, styleAttrib: String): String
1288         // Returns the value for a certain style attribute on an element,
1289         // including computed values or values set through CSS.
1290         getStyle: function (el, style) {
1292                 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1294                 if ((!value || value === 'auto') && document.defaultView) {
1295                         var css = document.defaultView.getComputedStyle(el, null);
1296                         value = css ? css[style] : null;
1297                 }
1299                 return value === 'auto' ? null : value;
1300         },
1302         // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1303         // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1304         create: function (tagName, className, container) {
1306                 var el = document.createElement(tagName);
1307                 el.className = className || '';
1309                 if (container) {
1310                         container.appendChild(el);
1311                 }
1313                 return el;
1314         },
1316         // @function remove(el: HTMLElement)
1317         // Removes `el` from its parent element
1318         remove: function (el) {
1319                 var parent = el.parentNode;
1320                 if (parent) {
1321                         parent.removeChild(el);
1322                 }
1323         },
1325         // @function empty(el: HTMLElement)
1326         // Removes all of `el`'s children elements from `el`
1327         empty: function (el) {
1328                 while (el.firstChild) {
1329                         el.removeChild(el.firstChild);
1330                 }
1331         },
1333         // @function toFront(el: HTMLElement)
1334         // Makes `el` the last children of its parent, so it renders in front of the other children.
1335         toFront: function (el) {
1336                 el.parentNode.appendChild(el);
1337         },
1339         // @function toBack(el: HTMLElement)
1340         // Makes `el` the first children of its parent, so it renders back from the other children.
1341         toBack: function (el) {
1342                 var parent = el.parentNode;
1343                 parent.insertBefore(el, parent.firstChild);
1344         },
1346         // @function hasClass(el: HTMLElement, name: String): Boolean
1347         // Returns `true` if the element's class attribute contains `name`.
1348         hasClass: function (el, name) {
1349                 if (el.classList !== undefined) {
1350                         return el.classList.contains(name);
1351                 }
1352                 var className = L.DomUtil.getClass(el);
1353                 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1354         },
1356         // @function addClass(el: HTMLElement, name: String)
1357         // Adds `name` to the element's class attribute.
1358         addClass: function (el, name) {
1359                 if (el.classList !== undefined) {
1360                         var classes = L.Util.splitWords(name);
1361                         for (var i = 0, len = classes.length; i < len; i++) {
1362                                 el.classList.add(classes[i]);
1363                         }
1364                 } else if (!L.DomUtil.hasClass(el, name)) {
1365                         var className = L.DomUtil.getClass(el);
1366                         L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1367                 }
1368         },
1370         // @function removeClass(el: HTMLElement, name: String)
1371         // Removes `name` from the element's class attribute.
1372         removeClass: function (el, name) {
1373                 if (el.classList !== undefined) {
1374                         el.classList.remove(name);
1375                 } else {
1376                         L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1377                 }
1378         },
1380         // @function setClass(el: HTMLElement, name: String)
1381         // Sets the element's class.
1382         setClass: function (el, name) {
1383                 if (el.className.baseVal === undefined) {
1384                         el.className = name;
1385                 } else {
1386                         // in case of SVG element
1387                         el.className.baseVal = name;
1388                 }
1389         },
1391         // @function getClass(el: HTMLElement): String
1392         // Returns the element's class.
1393         getClass: function (el) {
1394                 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1395         },
1397         // @function setOpacity(el: HTMLElement, opacity: Number)
1398         // Set the opacity of an element (including old IE support).
1399         // `opacity` must be a number from `0` to `1`.
1400         setOpacity: function (el, value) {
1402                 if ('opacity' in el.style) {
1403                         el.style.opacity = value;
1405                 } else if ('filter' in el.style) {
1406                         L.DomUtil._setOpacityIE(el, value);
1407                 }
1408         },
1410         _setOpacityIE: function (el, value) {
1411                 var filter = false,
1412                     filterName = 'DXImageTransform.Microsoft.Alpha';
1414                 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1415                 try {
1416                         filter = el.filters.item(filterName);
1417                 } catch (e) {
1418                         // don't set opacity to 1 if we haven't already set an opacity,
1419                         // it isn't needed and breaks transparent pngs.
1420                         if (value === 1) { return; }
1421                 }
1423                 value = Math.round(value * 100);
1425                 if (filter) {
1426                         filter.Enabled = (value !== 100);
1427                         filter.Opacity = value;
1428                 } else {
1429                         el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1430                 }
1431         },
1433         // @function testProp(props: String[]): String|false
1434         // Goes through the array of style names and returns the first name
1435         // that is a valid style name for an element. If no such name is found,
1436         // it returns false. Useful for vendor-prefixed styles like `transform`.
1437         testProp: function (props) {
1439                 var style = document.documentElement.style;
1441                 for (var i = 0; i < props.length; i++) {
1442                         if (props[i] in style) {
1443                                 return props[i];
1444                         }
1445                 }
1446                 return false;
1447         },
1449         // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1450         // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1451         // and optionally scaled by `scale`. Does not have an effect if the
1452         // browser doesn't support 3D CSS transforms.
1453         setTransform: function (el, offset, scale) {
1454                 var pos = offset || new L.Point(0, 0);
1456                 el.style[L.DomUtil.TRANSFORM] =
1457                         (L.Browser.ie3d ?
1458                                 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1459                                 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1460                         (scale ? ' scale(' + scale + ')' : '');
1461         },
1463         // @function setPosition(el: HTMLElement, position: Point)
1464         // Sets the position of `el` to coordinates specified by `position`,
1465         // using CSS translate or top/left positioning depending on the browser
1466         // (used by Leaflet internally to position its layers).
1467         setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1469                 /*eslint-disable */
1470                 el._leaflet_pos = point;
1471                 /*eslint-enable */
1473                 if (L.Browser.any3d) {
1474                         L.DomUtil.setTransform(el, point);
1475                 } else {
1476                         el.style.left = point.x + 'px';
1477                         el.style.top = point.y + 'px';
1478                 }
1479         },
1481         // @function getPosition(el: HTMLElement): Point
1482         // Returns the coordinates of an element previously positioned with setPosition.
1483         getPosition: function (el) {
1484                 // this method is only used for elements previously positioned using setPosition,
1485                 // so it's safe to cache the position for performance
1487                 return el._leaflet_pos || new L.Point(0, 0);
1488         }
1492 (function () {
1493         // prefix style property names
1495         // @property TRANSFORM: String
1496         // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1497         L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1498                         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1501         // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1502         // the same for the transitionend event, in particular the Android 4.1 stock browser
1504         // @property TRANSITION: String
1505         // Vendor-prefixed transform style name.
1506         var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1507                         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1509         L.DomUtil.TRANSITION_END =
1510                         transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1512         // @function disableTextSelection()
1513         // Prevents the user from generating `selectstart` DOM events, usually generated
1514         // when the user drags the mouse through a page with text. Used internally
1515         // by Leaflet to override the behaviour of any click-and-drag interaction on
1516         // the map. Affects drag interactions on the whole document.
1518         // @function enableTextSelection()
1519         // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1520         if ('onselectstart' in document) {
1521                 L.DomUtil.disableTextSelection = function () {
1522                         L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1523                 };
1524                 L.DomUtil.enableTextSelection = function () {
1525                         L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1526                 };
1528         } else {
1529                 var userSelectProperty = L.DomUtil.testProp(
1530                         ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1532                 L.DomUtil.disableTextSelection = function () {
1533                         if (userSelectProperty) {
1534                                 var style = document.documentElement.style;
1535                                 this._userSelect = style[userSelectProperty];
1536                                 style[userSelectProperty] = 'none';
1537                         }
1538                 };
1539                 L.DomUtil.enableTextSelection = function () {
1540                         if (userSelectProperty) {
1541                                 document.documentElement.style[userSelectProperty] = this._userSelect;
1542                                 delete this._userSelect;
1543                         }
1544                 };
1545         }
1547         // @function disableImageDrag()
1548         // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1549         // for `dragstart` DOM events, usually generated when the user drags an image.
1550         L.DomUtil.disableImageDrag = function () {
1551                 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1552         };
1554         // @function enableImageDrag()
1555         // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1556         L.DomUtil.enableImageDrag = function () {
1557                 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1558         };
1560         // @function preventOutline(el: HTMLElement)
1561         // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1562         // of the element `el` invisible. Used internally by Leaflet to prevent
1563         // focusable elements from displaying an outline when the user performs a
1564         // drag interaction on them.
1565         L.DomUtil.preventOutline = function (element) {
1566                 while (element.tabIndex === -1) {
1567                         element = element.parentNode;
1568                 }
1569                 if (!element || !element.style) { return; }
1570                 L.DomUtil.restoreOutline();
1571                 this._outlineElement = element;
1572                 this._outlineStyle = element.style.outline;
1573                 element.style.outline = 'none';
1574                 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1575         };
1577         // @function restoreOutline()
1578         // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1579         L.DomUtil.restoreOutline = function () {
1580                 if (!this._outlineElement) { return; }
1581                 this._outlineElement.style.outline = this._outlineStyle;
1582                 delete this._outlineElement;
1583                 delete this._outlineStyle;
1584                 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1585         };
1586 })();
1590 /* @class LatLng
1591  * @aka L.LatLng
1593  * Represents a geographical point with a certain latitude and longitude.
1595  * @example
1597  * ```
1598  * var latlng = L.latLng(50.5, 30.5);
1599  * ```
1601  * 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:
1603  * ```
1604  * map.panTo([50, 30]);
1605  * map.panTo({lon: 30, lat: 50});
1606  * map.panTo({lat: 50, lng: 30});
1607  * map.panTo(L.latLng(50, 30));
1608  * ```
1609  */
1611 L.LatLng = function (lat, lng, alt) {
1612         if (isNaN(lat) || isNaN(lng)) {
1613                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1614         }
1616         // @property lat: Number
1617         // Latitude in degrees
1618         this.lat = +lat;
1620         // @property lng: Number
1621         // Longitude in degrees
1622         this.lng = +lng;
1624         // @property alt: Number
1625         // Altitude in meters (optional)
1626         if (alt !== undefined) {
1627                 this.alt = +alt;
1628         }
1631 L.LatLng.prototype = {
1632         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1633         // 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.
1634         equals: function (obj, maxMargin) {
1635                 if (!obj) { return false; }
1637                 obj = L.latLng(obj);
1639                 var margin = Math.max(
1640                         Math.abs(this.lat - obj.lat),
1641                         Math.abs(this.lng - obj.lng));
1643                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1644         },
1646         // @method toString(): String
1647         // Returns a string representation of the point (for debugging purposes).
1648         toString: function (precision) {
1649                 return 'LatLng(' +
1650                         L.Util.formatNum(this.lat, precision) + ', ' +
1651                         L.Util.formatNum(this.lng, precision) + ')';
1652         },
1654         // @method distanceTo(otherLatLng: LatLng): Number
1655         // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1656         distanceTo: function (other) {
1657                 return L.CRS.Earth.distance(this, L.latLng(other));
1658         },
1660         // @method wrap(): LatLng
1661         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1662         wrap: function () {
1663                 return L.CRS.Earth.wrapLatLng(this);
1664         },
1666         // @method toBounds(sizeInMeters: Number): LatLngBounds
1667         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1668         toBounds: function (sizeInMeters) {
1669                 var latAccuracy = 180 * sizeInMeters / 40075017,
1670                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1672                 return L.latLngBounds(
1673                         [this.lat - latAccuracy, this.lng - lngAccuracy],
1674                         [this.lat + latAccuracy, this.lng + lngAccuracy]);
1675         },
1677         clone: function () {
1678                 return new L.LatLng(this.lat, this.lng, this.alt);
1679         }
1684 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1685 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1687 // @alternative
1688 // @factory L.latLng(coords: Array): LatLng
1689 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1691 // @alternative
1692 // @factory L.latLng(coords: Object): LatLng
1693 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1695 L.latLng = function (a, b, c) {
1696         if (a instanceof L.LatLng) {
1697                 return a;
1698         }
1699         if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1700                 if (a.length === 3) {
1701                         return new L.LatLng(a[0], a[1], a[2]);
1702                 }
1703                 if (a.length === 2) {
1704                         return new L.LatLng(a[0], a[1]);
1705                 }
1706                 return null;
1707         }
1708         if (a === undefined || a === null) {
1709                 return a;
1710         }
1711         if (typeof a === 'object' && 'lat' in a) {
1712                 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1713         }
1714         if (b === undefined) {
1715                 return null;
1716         }
1717         return new L.LatLng(a, b, c);
1723  * @class LatLngBounds
1724  * @aka L.LatLngBounds
1726  * Represents a rectangular geographical area on a map.
1728  * @example
1730  * ```js
1731  * var corner1 = L.latLng(40.712, -74.227),
1732  * corner2 = L.latLng(40.774, -74.125),
1733  * bounds = L.latLngBounds(corner1, corner2);
1734  * ```
1736  * 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:
1738  * ```js
1739  * map.fitBounds([
1740  *      [40.712, -74.227],
1741  *      [40.774, -74.125]
1742  * ]);
1743  * ```
1745  * 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.
1746  */
1748 L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1749         if (!corner1) { return; }
1751         var latlngs = corner2 ? [corner1, corner2] : corner1;
1753         for (var i = 0, len = latlngs.length; i < len; i++) {
1754                 this.extend(latlngs[i]);
1755         }
1758 L.LatLngBounds.prototype = {
1760         // @method extend(latlng: LatLng): this
1761         // Extend the bounds to contain the given point
1763         // @alternative
1764         // @method extend(otherBounds: LatLngBounds): this
1765         // Extend the bounds to contain the given bounds
1766         extend: function (obj) {
1767                 var sw = this._southWest,
1768                     ne = this._northEast,
1769                     sw2, ne2;
1771                 if (obj instanceof L.LatLng) {
1772                         sw2 = obj;
1773                         ne2 = obj;
1775                 } else if (obj instanceof L.LatLngBounds) {
1776                         sw2 = obj._southWest;
1777                         ne2 = obj._northEast;
1779                         if (!sw2 || !ne2) { return this; }
1781                 } else {
1782                         return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1783                 }
1785                 if (!sw && !ne) {
1786                         this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1787                         this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1788                 } else {
1789                         sw.lat = Math.min(sw2.lat, sw.lat);
1790                         sw.lng = Math.min(sw2.lng, sw.lng);
1791                         ne.lat = Math.max(ne2.lat, ne.lat);
1792                         ne.lng = Math.max(ne2.lng, ne.lng);
1793                 }
1795                 return this;
1796         },
1798         // @method pad(bufferRatio: Number): LatLngBounds
1799         // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1800         pad: function (bufferRatio) {
1801                 var sw = this._southWest,
1802                     ne = this._northEast,
1803                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1804                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1806                 return new L.LatLngBounds(
1807                         new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1808                         new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1809         },
1811         // @method getCenter(): LatLng
1812         // Returns the center point of the bounds.
1813         getCenter: function () {
1814                 return new L.LatLng(
1815                         (this._southWest.lat + this._northEast.lat) / 2,
1816                         (this._southWest.lng + this._northEast.lng) / 2);
1817         },
1819         // @method getSouthWest(): LatLng
1820         // Returns the south-west point of the bounds.
1821         getSouthWest: function () {
1822                 return this._southWest;
1823         },
1825         // @method getNorthEast(): LatLng
1826         // Returns the north-east point of the bounds.
1827         getNorthEast: function () {
1828                 return this._northEast;
1829         },
1831         // @method getNorthWest(): LatLng
1832         // Returns the north-west point of the bounds.
1833         getNorthWest: function () {
1834                 return new L.LatLng(this.getNorth(), this.getWest());
1835         },
1837         // @method getSouthEast(): LatLng
1838         // Returns the south-east point of the bounds.
1839         getSouthEast: function () {
1840                 return new L.LatLng(this.getSouth(), this.getEast());
1841         },
1843         // @method getWest(): Number
1844         // Returns the west longitude of the bounds
1845         getWest: function () {
1846                 return this._southWest.lng;
1847         },
1849         // @method getSouth(): Number
1850         // Returns the south latitude of the bounds
1851         getSouth: function () {
1852                 return this._southWest.lat;
1853         },
1855         // @method getEast(): Number
1856         // Returns the east longitude of the bounds
1857         getEast: function () {
1858                 return this._northEast.lng;
1859         },
1861         // @method getNorth(): Number
1862         // Returns the north latitude of the bounds
1863         getNorth: function () {
1864                 return this._northEast.lat;
1865         },
1867         // @method contains(otherBounds: LatLngBounds): Boolean
1868         // Returns `true` if the rectangle contains the given one.
1870         // @alternative
1871         // @method contains (latlng: LatLng): Boolean
1872         // Returns `true` if the rectangle contains the given point.
1873         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1874                 if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
1875                         obj = L.latLng(obj);
1876                 } else {
1877                         obj = L.latLngBounds(obj);
1878                 }
1880                 var sw = this._southWest,
1881                     ne = this._northEast,
1882                     sw2, ne2;
1884                 if (obj instanceof L.LatLngBounds) {
1885                         sw2 = obj.getSouthWest();
1886                         ne2 = obj.getNorthEast();
1887                 } else {
1888                         sw2 = ne2 = obj;
1889                 }
1891                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1892                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1893         },
1895         // @method intersects(otherBounds: LatLngBounds): Boolean
1896         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1897         intersects: function (bounds) {
1898                 bounds = L.latLngBounds(bounds);
1900                 var sw = this._southWest,
1901                     ne = this._northEast,
1902                     sw2 = bounds.getSouthWest(),
1903                     ne2 = bounds.getNorthEast(),
1905                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1906                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1908                 return latIntersects && lngIntersects;
1909         },
1911         // @method overlaps(otherBounds: Bounds): Boolean
1912         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1913         overlaps: function (bounds) {
1914                 bounds = L.latLngBounds(bounds);
1916                 var sw = this._southWest,
1917                     ne = this._northEast,
1918                     sw2 = bounds.getSouthWest(),
1919                     ne2 = bounds.getNorthEast(),
1921                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1922                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1924                 return latOverlaps && lngOverlaps;
1925         },
1927         // @method toBBoxString(): String
1928         // 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.
1929         toBBoxString: function () {
1930                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1931         },
1933         // @method equals(otherBounds: LatLngBounds): Boolean
1934         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1935         equals: function (bounds) {
1936                 if (!bounds) { return false; }
1938                 bounds = L.latLngBounds(bounds);
1940                 return this._southWest.equals(bounds.getSouthWest()) &&
1941                        this._northEast.equals(bounds.getNorthEast());
1942         },
1944         // @method isValid(): Boolean
1945         // Returns `true` if the bounds are properly initialized.
1946         isValid: function () {
1947                 return !!(this._southWest && this._northEast);
1948         }
1951 // TODO International date line?
1953 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1954 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1956 // @alternative
1957 // @factory L.latLngBounds(latlngs: LatLng[])
1958 // 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).
1959 L.latLngBounds = function (a, b) {
1960         if (a instanceof L.LatLngBounds) {
1961                 return a;
1962         }
1963         return new L.LatLngBounds(a, b);
1969  * @namespace Projection
1970  * @section
1971  * Leaflet comes with a set of already defined Projections out of the box:
1973  * @projection L.Projection.LonLat
1975  * Equirectangular, or Plate Carree projection — the most simple projection,
1976  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1977  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1978  * `EPSG:3395` and `Simple` CRS.
1979  */
1981 L.Projection = {};
1983 L.Projection.LonLat = {
1984         project: function (latlng) {
1985                 return new L.Point(latlng.lng, latlng.lat);
1986         },
1988         unproject: function (point) {
1989                 return new L.LatLng(point.y, point.x);
1990         },
1992         bounds: L.bounds([-180, -90], [180, 90])
1998  * @namespace Projection
1999  * @projection L.Projection.SphericalMercator
2001  * Spherical Mercator projection — the most common projection for online maps,
2002  * used by almost all free and commercial tile providers. Assumes that Earth is
2003  * a sphere. Used by the `EPSG:3857` CRS.
2004  */
2006 L.Projection.SphericalMercator = {
2008         R: 6378137,
2009         MAX_LATITUDE: 85.0511287798,
2011         project: function (latlng) {
2012                 var d = Math.PI / 180,
2013                     max = this.MAX_LATITUDE,
2014                     lat = Math.max(Math.min(max, latlng.lat), -max),
2015                     sin = Math.sin(lat * d);
2017                 return new L.Point(
2018                                 this.R * latlng.lng * d,
2019                                 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2020         },
2022         unproject: function (point) {
2023                 var d = 180 / Math.PI;
2025                 return new L.LatLng(
2026                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2027                         point.x * d / this.R);
2028         },
2030         bounds: (function () {
2031                 var d = 6378137 * Math.PI;
2032                 return L.bounds([-d, -d], [d, d]);
2033         })()
2039  * @class CRS
2040  * @aka L.CRS
2041  * Abstract class that defines coordinate reference systems for projecting
2042  * geographical points into pixel (screen) coordinates and back (and to
2043  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2044  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2046  * Leaflet defines the most usual CRSs by default. If you want to use a
2047  * CRS not defined by default, take a look at the
2048  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2049  */
2051 L.CRS = {
2052         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2053         // Projects geographical coordinates into pixel coordinates for a given zoom.
2054         latLngToPoint: function (latlng, zoom) {
2055                 var projectedPoint = this.projection.project(latlng),
2056                     scale = this.scale(zoom);
2058                 return this.transformation._transform(projectedPoint, scale);
2059         },
2061         // @method pointToLatLng(point: Point, zoom: Number): LatLng
2062         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2063         // zoom into geographical coordinates.
2064         pointToLatLng: function (point, zoom) {
2065                 var scale = this.scale(zoom),
2066                     untransformedPoint = this.transformation.untransform(point, scale);
2068                 return this.projection.unproject(untransformedPoint);
2069         },
2071         // @method project(latlng: LatLng): Point
2072         // Projects geographical coordinates into coordinates in units accepted for
2073         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2074         project: function (latlng) {
2075                 return this.projection.project(latlng);
2076         },
2078         // @method unproject(point: Point): LatLng
2079         // Given a projected coordinate returns the corresponding LatLng.
2080         // The inverse of `project`.
2081         unproject: function (point) {
2082                 return this.projection.unproject(point);
2083         },
2085         // @method scale(zoom: Number): Number
2086         // Returns the scale used when transforming projected coordinates into
2087         // pixel coordinates for a particular zoom. For example, it returns
2088         // `256 * 2^zoom` for Mercator-based CRS.
2089         scale: function (zoom) {
2090                 return 256 * Math.pow(2, zoom);
2091         },
2093         // @method zoom(scale: Number): Number
2094         // Inverse of `scale()`, returns the zoom level corresponding to a scale
2095         // factor of `scale`.
2096         zoom: function (scale) {
2097                 return Math.log(scale / 256) / Math.LN2;
2098         },
2100         // @method getProjectedBounds(zoom: Number): Bounds
2101         // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2102         getProjectedBounds: function (zoom) {
2103                 if (this.infinite) { return null; }
2105                 var b = this.projection.bounds,
2106                     s = this.scale(zoom),
2107                     min = this.transformation.transform(b.min, s),
2108                     max = this.transformation.transform(b.max, s);
2110                 return L.bounds(min, max);
2111         },
2113         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2114         // Returns the distance between two geographical coordinates.
2116         // @property code: String
2117         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2118         //
2119         // @property wrapLng: Number[]
2120         // An array of two numbers defining whether the longitude (horizontal) coordinate
2121         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2122         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2123         //
2124         // @property wrapLat: Number[]
2125         // Like `wrapLng`, but for the latitude (vertical) axis.
2127         // wrapLng: [min, max],
2128         // wrapLat: [min, max],
2130         // @property infinite: Boolean
2131         // If true, the coordinate space will be unbounded (infinite in both axes)
2132         infinite: false,
2134         // @method wrapLatLng(latlng: LatLng): LatLng
2135         // Returns a `LatLng` where lat and lng has been wrapped according to the
2136         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2137         // Only accepts actual `L.LatLng` instances, not arrays.
2138         wrapLatLng: function (latlng) {
2139                 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2140                     lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2141                     alt = latlng.alt;
2143                 return L.latLng(lat, lng, alt);
2144         },
2146         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
2147         // Returns a `LatLngBounds` with the same size as the given one, ensuring
2148         // that its center is within the CRS's bounds.
2149         // Only accepts actual `L.LatLngBounds` instances, not arrays.
2150         wrapLatLngBounds: function (bounds) {
2151                 var center = bounds.getCenter(),
2152                     newCenter = this.wrapLatLng(center),
2153                     latShift = center.lat - newCenter.lat,
2154                     lngShift = center.lng - newCenter.lng;
2156                 if (latShift === 0 && lngShift === 0) {
2157                         return bounds;
2158                 }
2160                 var sw = bounds.getSouthWest(),
2161                     ne = bounds.getNorthEast(),
2162                     newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
2163                     newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});
2165                 return new L.LatLngBounds(newSw, newNe);
2166         }
2172  * @namespace CRS
2173  * @crs L.CRS.Simple
2175  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2176  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2177  * axis should still be inverted (going from bottom to top). `distance()` returns
2178  * simple euclidean distance.
2179  */
2181 L.CRS.Simple = L.extend({}, L.CRS, {
2182         projection: L.Projection.LonLat,
2183         transformation: new L.Transformation(1, 0, -1, 0),
2185         scale: function (zoom) {
2186                 return Math.pow(2, zoom);
2187         },
2189         zoom: function (scale) {
2190                 return Math.log(scale) / Math.LN2;
2191         },
2193         distance: function (latlng1, latlng2) {
2194                 var dx = latlng2.lng - latlng1.lng,
2195                     dy = latlng2.lat - latlng1.lat;
2197                 return Math.sqrt(dx * dx + dy * dy);
2198         },
2200         infinite: true
2206  * @namespace CRS
2207  * @crs L.CRS.Earth
2209  * Serves as the base for CRS that are global such that they cover the earth.
2210  * Can only be used as the base for other CRS and cannot be used directly,
2211  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2212  * meters.
2213  */
2215 L.CRS.Earth = L.extend({}, L.CRS, {
2216         wrapLng: [-180, 180],
2218         // Mean Earth Radius, as recommended for use by
2219         // the International Union of Geodesy and Geophysics,
2220         // see http://rosettacode.org/wiki/Haversine_formula
2221         R: 6371000,
2223         // distance between two geographical points using spherical law of cosines approximation
2224         distance: function (latlng1, latlng2) {
2225                 var rad = Math.PI / 180,
2226                     lat1 = latlng1.lat * rad,
2227                     lat2 = latlng2.lat * rad,
2228                     a = Math.sin(lat1) * Math.sin(lat2) +
2229                         Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2231                 return this.R * Math.acos(Math.min(a, 1));
2232         }
2238  * @namespace CRS
2239  * @crs L.CRS.EPSG3857
2241  * The most common CRS for online maps, used by almost all free and commercial
2242  * tile providers. Uses Spherical Mercator projection. Set in by default in
2243  * Map's `crs` option.
2244  */
2246 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2247         code: 'EPSG:3857',
2248         projection: L.Projection.SphericalMercator,
2250         transformation: (function () {
2251                 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2252                 return new L.Transformation(scale, 0.5, -scale, 0.5);
2253         }())
2256 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2257         code: 'EPSG:900913'
2263  * @namespace CRS
2264  * @crs L.CRS.EPSG4326
2266  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2268  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
2269  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
2270  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
2271  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
2272  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
2273  */
2275 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2276         code: 'EPSG:4326',
2277         projection: L.Projection.LonLat,
2278         transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2284  * @class Map
2285  * @aka L.Map
2286  * @inherits Evented
2288  * The central class of the API — it is used to create a map on a page and manipulate it.
2290  * @example
2292  * ```js
2293  * // initialize the map on the "map" div with a given center and zoom
2294  * var map = L.map('map', {
2295  *      center: [51.505, -0.09],
2296  *      zoom: 13
2297  * });
2298  * ```
2300  */
2302 L.Map = L.Evented.extend({
2304         options: {
2305                 // @section Map State Options
2306                 // @option crs: CRS = L.CRS.EPSG3857
2307                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2308                 // sure what it means.
2309                 crs: L.CRS.EPSG3857,
2311                 // @option center: LatLng = undefined
2312                 // Initial geographic center of the map
2313                 center: undefined,
2315                 // @option zoom: Number = undefined
2316                 // Initial map zoom level
2317                 zoom: undefined,
2319                 // @option minZoom: Number = undefined
2320                 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2321                 minZoom: undefined,
2323                 // @option maxZoom: Number = undefined
2324                 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2325                 maxZoom: undefined,
2327                 // @option layers: Layer[] = []
2328                 // Array of layers that will be added to the map initially
2329                 layers: [],
2331                 // @option maxBounds: LatLngBounds = null
2332                 // When this option is set, the map restricts the view to the given
2333                 // geographical bounds, bouncing the user back if the user tries to pan
2334                 // outside the view. To set the restriction dynamically, use
2335                 // [`setMaxBounds`](#map-setmaxbounds) method.
2336                 maxBounds: undefined,
2338                 // @option renderer: Renderer = *
2339                 // The default method for drawing vector layers on the map. `L.SVG`
2340                 // or `L.Canvas` by default depending on browser support.
2341                 renderer: undefined,
2344                 // @section Animation Options
2345                 // @option zoomAnimation: Boolean = true
2346                 // Whether the map zoom animation is enabled. By default it's enabled
2347                 // in all browsers that support CSS3 Transitions except Android.
2348                 zoomAnimation: true,
2350                 // @option zoomAnimationThreshold: Number = 4
2351                 // Won't animate zoom if the zoom difference exceeds this value.
2352                 zoomAnimationThreshold: 4,
2354                 // @option fadeAnimation: Boolean = true
2355                 // Whether the tile fade animation is enabled. By default it's enabled
2356                 // in all browsers that support CSS3 Transitions except Android.
2357                 fadeAnimation: true,
2359                 // @option markerZoomAnimation: Boolean = true
2360                 // Whether markers animate their zoom with the zoom animation, if disabled
2361                 // they will disappear for the length of the animation. By default it's
2362                 // enabled in all browsers that support CSS3 Transitions except Android.
2363                 markerZoomAnimation: true,
2365                 // @option transform3DLimit: Number = 2^23
2366                 // Defines the maximum size of a CSS translation transform. The default
2367                 // value should not be changed unless a web browser positions layers in
2368                 // the wrong place after doing a large `panBy`.
2369                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2371                 // @section Interaction Options
2372                 // @option zoomSnap: Number = 1
2373                 // Forces the map's zoom level to always be a multiple of this, particularly
2374                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2375                 // By default, the zoom level snaps to the nearest integer; lower values
2376                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2377                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2378                 zoomSnap: 1,
2380                 // @option zoomDelta: Number = 1
2381                 // Controls how much the map's zoom level will change after a
2382                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2383                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2384                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2385                 zoomDelta: 1,
2387                 // @option trackResize: Boolean = true
2388                 // Whether the map automatically handles browser window resize to update itself.
2389                 trackResize: true
2390         },
2392         initialize: function (id, options) { // (HTMLElement or String, Object)
2393                 options = L.setOptions(this, options);
2395                 this._initContainer(id);
2396                 this._initLayout();
2398                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2399                 this._onResize = L.bind(this._onResize, this);
2401                 this._initEvents();
2403                 if (options.maxBounds) {
2404                         this.setMaxBounds(options.maxBounds);
2405                 }
2407                 if (options.zoom !== undefined) {
2408                         this._zoom = this._limitZoom(options.zoom);
2409                 }
2411                 if (options.center && options.zoom !== undefined) {
2412                         this.setView(L.latLng(options.center), options.zoom, {reset: true});
2413                 }
2415                 this._handlers = [];
2416                 this._layers = {};
2417                 this._zoomBoundLayers = {};
2418                 this._sizeChanged = true;
2420                 this.callInitHooks();
2422                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
2423                 this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
2424                                 this.options.zoomAnimation;
2426                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
2427                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
2428                 if (this._zoomAnimated) {
2429                         this._createAnimProxy();
2430                         L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
2431                 }
2433                 this._addLayers(this.options.layers);
2434         },
2437         // @section Methods for modifying map state
2439         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2440         // Sets the view of the map (geographical center and zoom) with the given
2441         // animation options.
2442         setView: function (center, zoom, options) {
2444                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
2445                 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
2446                 options = options || {};
2448                 this._stop();
2450                 if (this._loaded && !options.reset && options !== true) {
2452                         if (options.animate !== undefined) {
2453                                 options.zoom = L.extend({animate: options.animate}, options.zoom);
2454                                 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
2455                         }
2457                         // try animating pan or zoom
2458                         var moved = (this._zoom !== zoom) ?
2459                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
2460                                 this._tryAnimatedPan(center, options.pan);
2462                         if (moved) {
2463                                 // prevent resize handler call, the view will refresh after animation anyway
2464                                 clearTimeout(this._sizeTimer);
2465                                 return this;
2466                         }
2467                 }
2469                 // animation didn't start, just reset the map view
2470                 this._resetView(center, zoom);
2472                 return this;
2473         },
2475         // @method setZoom(zoom: Number, options: Zoom/pan options): this
2476         // Sets the zoom of the map.
2477         setZoom: function (zoom, options) {
2478                 if (!this._loaded) {
2479                         this._zoom = zoom;
2480                         return this;
2481                 }
2482                 return this.setView(this.getCenter(), zoom, {zoom: options});
2483         },
2485         // @method zoomIn(delta?: Number, options?: Zoom options): this
2486         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2487         zoomIn: function (delta, options) {
2488                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2489                 return this.setZoom(this._zoom + delta, options);
2490         },
2492         // @method zoomOut(delta?: Number, options?: Zoom options): this
2493         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2494         zoomOut: function (delta, options) {
2495                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2496                 return this.setZoom(this._zoom - delta, options);
2497         },
2499         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2500         // Zooms the map while keeping a specified geographical point on the map
2501         // stationary (e.g. used internally for scroll zoom and double-click zoom).
2502         // @alternative
2503         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2504         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2505         setZoomAround: function (latlng, zoom, options) {
2506                 var scale = this.getZoomScale(zoom),
2507                     viewHalf = this.getSize().divideBy(2),
2508                     containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2510                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2511                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2513                 return this.setView(newCenter, zoom, {zoom: options});
2514         },
2516         _getBoundsCenterZoom: function (bounds, options) {
2518                 options = options || {};
2519                 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2521                 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2522                     paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2524                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2526                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2528                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2530                     swPoint = this.project(bounds.getSouthWest(), zoom),
2531                     nePoint = this.project(bounds.getNorthEast(), zoom),
2532                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2534                 return {
2535                         center: center,
2536                         zoom: zoom
2537                 };
2538         },
2540         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
2541         // Sets a map view that contains the given geographical bounds with the
2542         // maximum zoom level possible.
2543         fitBounds: function (bounds, options) {
2545                 bounds = L.latLngBounds(bounds);
2547                 if (!bounds.isValid()) {
2548                         throw new Error('Bounds are not valid.');
2549                 }
2551                 var target = this._getBoundsCenterZoom(bounds, options);
2552                 return this.setView(target.center, target.zoom, options);
2553         },
2555         // @method fitWorld(options?: fitBounds options): this
2556         // Sets a map view that mostly contains the whole world with the maximum
2557         // zoom level possible.
2558         fitWorld: function (options) {
2559                 return this.fitBounds([[-90, -180], [90, 180]], options);
2560         },
2562         // @method panTo(latlng: LatLng, options?: Pan options): this
2563         // Pans the map to a given center.
2564         panTo: function (center, options) { // (LatLng)
2565                 return this.setView(center, this._zoom, {pan: options});
2566         },
2568         // @method panBy(offset: Point): this
2569         // Pans the map by a given number of pixels (animated).
2570         panBy: function (offset, options) {
2571                 offset = L.point(offset).round();
2572                 options = options || {};
2574                 if (!offset.x && !offset.y) {
2575                         return this.fire('moveend');
2576                 }
2577                 // If we pan too far, Chrome gets issues with tiles
2578                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
2579                 if (options.animate !== true && !this.getSize().contains(offset)) {
2580                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
2581                         return this;
2582                 }
2584                 if (!this._panAnim) {
2585                         this._panAnim = new L.PosAnimation();
2587                         this._panAnim.on({
2588                                 'step': this._onPanTransitionStep,
2589                                 'end': this._onPanTransitionEnd
2590                         }, this);
2591                 }
2593                 // don't fire movestart if animating inertia
2594                 if (!options.noMoveStart) {
2595                         this.fire('movestart');
2596                 }
2598                 // animate pan unless animate: false specified
2599                 if (options.animate !== false) {
2600                         L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
2602                         var newPos = this._getMapPanePos().subtract(offset).round();
2603                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
2604                 } else {
2605                         this._rawPanBy(offset);
2606                         this.fire('move').fire('moveend');
2607                 }
2609                 return this;
2610         },
2612         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
2613         // Sets the view of the map (geographical center and zoom) performing a smooth
2614         // pan-zoom animation.
2615         flyTo: function (targetCenter, targetZoom, options) {
2617                 options = options || {};
2618                 if (options.animate === false || !L.Browser.any3d) {
2619                         return this.setView(targetCenter, targetZoom, options);
2620                 }
2622                 this._stop();
2624                 var from = this.project(this.getCenter()),
2625                     to = this.project(targetCenter),
2626                     size = this.getSize(),
2627                     startZoom = this._zoom;
2629                 targetCenter = L.latLng(targetCenter);
2630                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
2632                 var w0 = Math.max(size.x, size.y),
2633                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
2634                     u1 = (to.distanceTo(from)) || 1,
2635                     rho = 1.42,
2636                     rho2 = rho * rho;
2638                 function r(i) {
2639                         var s1 = i ? -1 : 1,
2640                             s2 = i ? w1 : w0,
2641                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
2642                             b1 = 2 * s2 * rho2 * u1,
2643                             b = t1 / b1,
2644                             sq = Math.sqrt(b * b + 1) - b;
2646                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
2647                             // thus triggering an infinite loop in flyTo
2648                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
2650                         return log;
2651                 }
2653                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
2654                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
2655                 function tanh(n) { return sinh(n) / cosh(n); }
2657                 var r0 = r(0);
2659                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
2660                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
2662                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
2664                 var start = Date.now(),
2665                     S = (r(1) - r0) / rho,
2666                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
2668                 function frame() {
2669                         var t = (Date.now() - start) / duration,
2670                             s = easeOut(t) * S;
2672                         if (t <= 1) {
2673                                 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
2675                                 this._move(
2676                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
2677                                         this.getScaleZoom(w0 / w(s), startZoom),
2678                                         {flyTo: true});
2680                         } else {
2681                                 this
2682                                         ._move(targetCenter, targetZoom)
2683                                         ._moveEnd(true);
2684                         }
2685                 }
2687                 this._moveStart(true);
2689                 frame.call(this);
2690                 return this;
2691         },
2693         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
2694         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
2695         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
2696         flyToBounds: function (bounds, options) {
2697                 var target = this._getBoundsCenterZoom(bounds, options);
2698                 return this.flyTo(target.center, target.zoom, options);
2699         },
2701         // @method setMaxBounds(bounds: Bounds): this
2702         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2703         setMaxBounds: function (bounds) {
2704                 bounds = L.latLngBounds(bounds);
2706                 if (!bounds.isValid()) {
2707                         this.options.maxBounds = null;
2708                         return this.off('moveend', this._panInsideMaxBounds);
2709                 } else if (this.options.maxBounds) {
2710                         this.off('moveend', this._panInsideMaxBounds);
2711                 }
2713                 this.options.maxBounds = bounds;
2715                 if (this._loaded) {
2716                         this._panInsideMaxBounds();
2717                 }
2719                 return this.on('moveend', this._panInsideMaxBounds);
2720         },
2722         // @method setMinZoom(zoom: Number): this
2723         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2724         setMinZoom: function (zoom) {
2725                 this.options.minZoom = zoom;
2727                 if (this._loaded && this.getZoom() < this.options.minZoom) {
2728                         return this.setZoom(zoom);
2729                 }
2731                 return this;
2732         },
2734         // @method setMaxZoom(zoom: Number): this
2735         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2736         setMaxZoom: function (zoom) {
2737                 this.options.maxZoom = zoom;
2739                 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2740                         return this.setZoom(zoom);
2741                 }
2743                 return this;
2744         },
2746         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2747         // 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.
2748         panInsideBounds: function (bounds, options) {
2749                 this._enforcingBounds = true;
2750                 var center = this.getCenter(),
2751                     newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2753                 if (!center.equals(newCenter)) {
2754                         this.panTo(newCenter, options);
2755                 }
2757                 this._enforcingBounds = false;
2758                 return this;
2759         },
2761         // @method invalidateSize(options: Zoom/Pan options): this
2762         // Checks if the map container size changed and updates the map if so —
2763         // call it after you've changed the map size dynamically, also animating
2764         // pan by default. If `options.pan` is `false`, panning will not occur.
2765         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2766         // that it doesn't happen often even if the method is called many
2767         // times in a row.
2769         // @alternative
2770         // @method invalidateSize(animate: Boolean): this
2771         // Checks if the map container size changed and updates the map if so —
2772         // call it after you've changed the map size dynamically, also animating
2773         // pan by default.
2774         invalidateSize: function (options) {
2775                 if (!this._loaded) { return this; }
2777                 options = L.extend({
2778                         animate: false,
2779                         pan: true
2780                 }, options === true ? {animate: true} : options);
2782                 var oldSize = this.getSize();
2783                 this._sizeChanged = true;
2784                 this._lastCenter = null;
2786                 var newSize = this.getSize(),
2787                     oldCenter = oldSize.divideBy(2).round(),
2788                     newCenter = newSize.divideBy(2).round(),
2789                     offset = oldCenter.subtract(newCenter);
2791                 if (!offset.x && !offset.y) { return this; }
2793                 if (options.animate && options.pan) {
2794                         this.panBy(offset);
2796                 } else {
2797                         if (options.pan) {
2798                                 this._rawPanBy(offset);
2799                         }
2801                         this.fire('move');
2803                         if (options.debounceMoveend) {
2804                                 clearTimeout(this._sizeTimer);
2805                                 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2806                         } else {
2807                                 this.fire('moveend');
2808                         }
2809                 }
2811                 // @section Map state change events
2812                 // @event resize: ResizeEvent
2813                 // Fired when the map is resized.
2814                 return this.fire('resize', {
2815                         oldSize: oldSize,
2816                         newSize: newSize
2817                 });
2818         },
2820         // @section Methods for modifying map state
2821         // @method stop(): this
2822         // Stops the currently running `panTo` or `flyTo` animation, if any.
2823         stop: function () {
2824                 this.setZoom(this._limitZoom(this._zoom));
2825                 if (!this.options.zoomSnap) {
2826                         this.fire('viewreset');
2827                 }
2828                 return this._stop();
2829         },
2831         // @section Geolocation methods
2832         // @method locate(options?: Locate options): this
2833         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
2834         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
2835         // and optionally sets the map view to the user's location with respect to
2836         // detection accuracy (or to the world view if geolocation failed).
2837         // Note that, if your page doesn't use HTTPS, this method will fail in
2838         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
2839         // See `Locate options` for more details.
2840         locate: function (options) {
2842                 options = this._locateOptions = L.extend({
2843                         timeout: 10000,
2844                         watch: false
2845                         // setView: false
2846                         // maxZoom: <Number>
2847                         // maximumAge: 0
2848                         // enableHighAccuracy: false
2849                 }, options);
2851                 if (!('geolocation' in navigator)) {
2852                         this._handleGeolocationError({
2853                                 code: 0,
2854                                 message: 'Geolocation not supported.'
2855                         });
2856                         return this;
2857                 }
2859                 var onResponse = L.bind(this._handleGeolocationResponse, this),
2860                     onError = L.bind(this._handleGeolocationError, this);
2862                 if (options.watch) {
2863                         this._locationWatchId =
2864                                 navigator.geolocation.watchPosition(onResponse, onError, options);
2865                 } else {
2866                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
2867                 }
2868                 return this;
2869         },
2871         // @method stopLocate(): this
2872         // Stops watching location previously initiated by `map.locate({watch: true})`
2873         // and aborts resetting the map view if map.locate was called with
2874         // `{setView: true}`.
2875         stopLocate: function () {
2876                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
2877                         navigator.geolocation.clearWatch(this._locationWatchId);
2878                 }
2879                 if (this._locateOptions) {
2880                         this._locateOptions.setView = false;
2881                 }
2882                 return this;
2883         },
2885         _handleGeolocationError: function (error) {
2886                 var c = error.code,
2887                     message = error.message ||
2888                             (c === 1 ? 'permission denied' :
2889                             (c === 2 ? 'position unavailable' : 'timeout'));
2891                 if (this._locateOptions.setView && !this._loaded) {
2892                         this.fitWorld();
2893                 }
2895                 // @section Location events
2896                 // @event locationerror: ErrorEvent
2897                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
2898                 this.fire('locationerror', {
2899                         code: c,
2900                         message: 'Geolocation error: ' + message + '.'
2901                 });
2902         },
2904         _handleGeolocationResponse: function (pos) {
2905                 var lat = pos.coords.latitude,
2906                     lng = pos.coords.longitude,
2907                     latlng = new L.LatLng(lat, lng),
2908                     bounds = latlng.toBounds(pos.coords.accuracy),
2909                     options = this._locateOptions;
2911                 if (options.setView) {
2912                         var zoom = this.getBoundsZoom(bounds);
2913                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
2914                 }
2916                 var data = {
2917                         latlng: latlng,
2918                         bounds: bounds,
2919                         timestamp: pos.timestamp
2920                 };
2922                 for (var i in pos.coords) {
2923                         if (typeof pos.coords[i] === 'number') {
2924                                 data[i] = pos.coords[i];
2925                         }
2926                 }
2928                 // @event locationfound: LocationEvent
2929                 // Fired when geolocation (using the [`locate`](#map-locate) method)
2930                 // went successfully.
2931                 this.fire('locationfound', data);
2932         },
2934         // TODO handler.addTo
2935         // TODO Appropiate docs section?
2936         // @section Other Methods
2937         // @method addHandler(name: String, HandlerClass: Function): this
2938         // Adds a new `Handler` to the map, given its name and constructor function.
2939         addHandler: function (name, HandlerClass) {
2940                 if (!HandlerClass) { return this; }
2942                 var handler = this[name] = new HandlerClass(this);
2944                 this._handlers.push(handler);
2946                 if (this.options[name]) {
2947                         handler.enable();
2948                 }
2950                 return this;
2951         },
2953         // @method remove(): this
2954         // Destroys the map and clears all related event listeners.
2955         remove: function () {
2957                 this._initEvents(true);
2959                 if (this._containerId !== this._container._leaflet_id) {
2960                         throw new Error('Map container is being reused by another instance');
2961                 }
2963                 try {
2964                         // throws error in IE6-8
2965                         delete this._container._leaflet_id;
2966                         delete this._containerId;
2967                 } catch (e) {
2968                         /*eslint-disable */
2969                         this._container._leaflet_id = undefined;
2970                         /*eslint-enable */
2971                         this._containerId = undefined;
2972                 }
2974                 L.DomUtil.remove(this._mapPane);
2976                 if (this._clearControlPos) {
2977                         this._clearControlPos();
2978                 }
2980                 this._clearHandlers();
2982                 if (this._loaded) {
2983                         // @section Map state change events
2984                         // @event unload: Event
2985                         // Fired when the map is destroyed with [remove](#map-remove) method.
2986                         this.fire('unload');
2987                 }
2989                 for (var i in this._layers) {
2990                         this._layers[i].remove();
2991                 }
2993                 return this;
2994         },
2996         // @section Other Methods
2997         // @method createPane(name: String, container?: HTMLElement): HTMLElement
2998         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2999         // then returns it. The pane is created as a children of `container`, or
3000         // as a children of the main map pane if not set.
3001         createPane: function (name, container) {
3002                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3003                     pane = L.DomUtil.create('div', className, container || this._mapPane);
3005                 if (name) {
3006                         this._panes[name] = pane;
3007                 }
3008                 return pane;
3009         },
3011         // @section Methods for Getting Map State
3013         // @method getCenter(): LatLng
3014         // Returns the geographical center of the map view
3015         getCenter: function () {
3016                 this._checkIfLoaded();
3018                 if (this._lastCenter && !this._moved()) {
3019                         return this._lastCenter;
3020                 }
3021                 return this.layerPointToLatLng(this._getCenterLayerPoint());
3022         },
3024         // @method getZoom(): Number
3025         // Returns the current zoom level of the map view
3026         getZoom: function () {
3027                 return this._zoom;
3028         },
3030         // @method getBounds(): LatLngBounds
3031         // Returns the geographical bounds visible in the current map view
3032         getBounds: function () {
3033                 var bounds = this.getPixelBounds(),
3034                     sw = this.unproject(bounds.getBottomLeft()),
3035                     ne = this.unproject(bounds.getTopRight());
3037                 return new L.LatLngBounds(sw, ne);
3038         },
3040         // @method getMinZoom(): Number
3041         // 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.
3042         getMinZoom: function () {
3043                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3044         },
3046         // @method getMaxZoom(): Number
3047         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3048         getMaxZoom: function () {
3049                 return this.options.maxZoom === undefined ?
3050                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3051                         this.options.maxZoom;
3052         },
3054         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3055         // Returns the maximum zoom level on which the given bounds fit to the map
3056         // view in its entirety. If `inside` (optional) is set to `true`, the method
3057         // instead returns the minimum zoom level on which the map view fits into
3058         // the given bounds in its entirety.
3059         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3060                 bounds = L.latLngBounds(bounds);
3061                 padding = L.point(padding || [0, 0]);
3063                 var zoom = this.getZoom() || 0,
3064                     min = this.getMinZoom(),
3065                     max = this.getMaxZoom(),
3066                     nw = bounds.getNorthWest(),
3067                     se = bounds.getSouthEast(),
3068                     size = this.getSize().subtract(padding),
3069                     boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3070                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3072                 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
3073                 zoom = this.getScaleZoom(scale, zoom);
3075                 if (snap) {
3076                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3077                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3078                 }
3080                 return Math.max(min, Math.min(max, zoom));
3081         },
3083         // @method getSize(): Point
3084         // Returns the current size of the map container (in pixels).
3085         getSize: function () {
3086                 if (!this._size || this._sizeChanged) {
3087                         this._size = new L.Point(
3088                                 this._container.clientWidth || 0,
3089                                 this._container.clientHeight || 0);
3091                         this._sizeChanged = false;
3092                 }
3093                 return this._size.clone();
3094         },
3096         // @method getPixelBounds(): Bounds
3097         // Returns the bounds of the current map view in projected pixel
3098         // coordinates (sometimes useful in layer and overlay implementations).
3099         getPixelBounds: function (center, zoom) {
3100                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3101                 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3102         },
3104         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3105         // the map pane? "left point of the map layer" can be confusing, specially
3106         // since there can be negative offsets.
3107         // @method getPixelOrigin(): Point
3108         // Returns the projected pixel coordinates of the top left point of
3109         // the map layer (useful in custom layer and overlay implementations).
3110         getPixelOrigin: function () {
3111                 this._checkIfLoaded();
3112                 return this._pixelOrigin;
3113         },
3115         // @method getPixelWorldBounds(zoom?: Number): Bounds
3116         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3117         // If `zoom` is omitted, the map's current zoom level is used.
3118         getPixelWorldBounds: function (zoom) {
3119                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3120         },
3122         // @section Other Methods
3124         // @method getPane(pane: String|HTMLElement): HTMLElement
3125         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3126         getPane: function (pane) {
3127                 return typeof pane === 'string' ? this._panes[pane] : pane;
3128         },
3130         // @method getPanes(): Object
3131         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3132         // the panes as values.
3133         getPanes: function () {
3134                 return this._panes;
3135         },
3137         // @method getContainer: HTMLElement
3138         // Returns the HTML element that contains the map.
3139         getContainer: function () {
3140                 return this._container;
3141         },
3144         // @section Conversion Methods
3146         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3147         // Returns the scale factor to be applied to a map transition from zoom level
3148         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3149         getZoomScale: function (toZoom, fromZoom) {
3150                 // TODO replace with universal implementation after refactoring projections
3151                 var crs = this.options.crs;
3152                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3153                 return crs.scale(toZoom) / crs.scale(fromZoom);
3154         },
3156         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3157         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3158         // level and everything is scaled by a factor of `scale`. Inverse of
3159         // [`getZoomScale`](#map-getZoomScale).
3160         getScaleZoom: function (scale, fromZoom) {
3161                 var crs = this.options.crs;
3162                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3163                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3164                 return isNaN(zoom) ? Infinity : zoom;
3165         },
3167         // @method project(latlng: LatLng, zoom: Number): Point
3168         // Projects a geographical coordinate `LatLng` according to the projection
3169         // of the map's CRS, then scales it according to `zoom` and the CRS's
3170         // `Transformation`. The result is pixel coordinate relative to
3171         // the CRS origin.
3172         project: function (latlng, zoom) {
3173                 zoom = zoom === undefined ? this._zoom : zoom;
3174                 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
3175         },
3177         // @method unproject(point: Point, zoom: Number): LatLng
3178         // Inverse of [`project`](#map-project).
3179         unproject: function (point, zoom) {
3180                 zoom = zoom === undefined ? this._zoom : zoom;
3181                 return this.options.crs.pointToLatLng(L.point(point), zoom);
3182         },
3184         // @method layerPointToLatLng(point: Point): LatLng
3185         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3186         // returns the corresponding geographical coordinate (for the current zoom level).
3187         layerPointToLatLng: function (point) {
3188                 var projectedPoint = L.point(point).add(this.getPixelOrigin());
3189                 return this.unproject(projectedPoint);
3190         },
3192         // @method latLngToLayerPoint(latlng: LatLng): Point
3193         // Given a geographical coordinate, returns the corresponding pixel coordinate
3194         // relative to the [origin pixel](#map-getpixelorigin).
3195         latLngToLayerPoint: function (latlng) {
3196                 var projectedPoint = this.project(L.latLng(latlng))._round();
3197                 return projectedPoint._subtract(this.getPixelOrigin());
3198         },
3200         // @method wrapLatLng(latlng: LatLng): LatLng
3201         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3202         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3203         // CRS's bounds.
3204         // By default this means longitude is wrapped around the dateline so its
3205         // value is between -180 and +180 degrees.
3206         wrapLatLng: function (latlng) {
3207                 return this.options.crs.wrapLatLng(L.latLng(latlng));
3208         },
3210         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3211         // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3212         // its center is within the CRS's bounds.
3213         // By default this means the center longitude is wrapped around the dateline so its
3214         // value is between -180 and +180 degrees, and the majority of the bounds
3215         // overlaps the CRS's bounds.
3216         wrapLatLngBounds: function (latlng) {
3217                 return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng));
3218         },
3220         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3221         // Returns the distance between two geographical coordinates according to
3222         // the map's CRS. By default this measures distance in meters.
3223         distance: function (latlng1, latlng2) {
3224                 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
3225         },
3227         // @method containerPointToLayerPoint(point: Point): Point
3228         // Given a pixel coordinate relative to the map container, returns the corresponding
3229         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3230         containerPointToLayerPoint: function (point) { // (Point)
3231                 return L.point(point).subtract(this._getMapPanePos());
3232         },
3234         // @method layerPointToContainerPoint(point: Point): Point
3235         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3236         // returns the corresponding pixel coordinate relative to the map container.
3237         layerPointToContainerPoint: function (point) { // (Point)
3238                 return L.point(point).add(this._getMapPanePos());
3239         },
3241         // @method containerPointToLatLng(point: Point): LatLng
3242         // Given a pixel coordinate relative to the map container, returns
3243         // the corresponding geographical coordinate (for the current zoom level).
3244         containerPointToLatLng: function (point) {
3245                 var layerPoint = this.containerPointToLayerPoint(L.point(point));
3246                 return this.layerPointToLatLng(layerPoint);
3247         },
3249         // @method latLngToContainerPoint(latlng: LatLng): Point
3250         // Given a geographical coordinate, returns the corresponding pixel coordinate
3251         // relative to the map container.
3252         latLngToContainerPoint: function (latlng) {
3253                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
3254         },
3256         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3257         // Given a MouseEvent object, returns the pixel coordinate relative to the
3258         // map container where the event took place.
3259         mouseEventToContainerPoint: function (e) {
3260                 return L.DomEvent.getMousePosition(e, this._container);
3261         },
3263         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3264         // Given a MouseEvent object, returns the pixel coordinate relative to
3265         // the [origin pixel](#map-getpixelorigin) where the event took place.
3266         mouseEventToLayerPoint: function (e) {
3267                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3268         },
3270         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3271         // Given a MouseEvent object, returns geographical coordinate where the
3272         // event took place.
3273         mouseEventToLatLng: function (e) { // (MouseEvent)
3274                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3275         },
3278         // map initialization methods
3280         _initContainer: function (id) {
3281                 var container = this._container = L.DomUtil.get(id);
3283                 if (!container) {
3284                         throw new Error('Map container not found.');
3285                 } else if (container._leaflet_id) {
3286                         throw new Error('Map container is already initialized.');
3287                 }
3289                 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
3290                 this._containerId = L.Util.stamp(container);
3291         },
3293         _initLayout: function () {
3294                 var container = this._container;
3296                 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
3298                 L.DomUtil.addClass(container, 'leaflet-container' +
3299                         (L.Browser.touch ? ' leaflet-touch' : '') +
3300                         (L.Browser.retina ? ' leaflet-retina' : '') +
3301                         (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
3302                         (L.Browser.safari ? ' leaflet-safari' : '') +
3303                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3305                 var position = L.DomUtil.getStyle(container, 'position');
3307                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3308                         container.style.position = 'relative';
3309                 }
3311                 this._initPanes();
3313                 if (this._initControlPos) {
3314                         this._initControlPos();
3315                 }
3316         },
3318         _initPanes: function () {
3319                 var panes = this._panes = {};
3320                 this._paneRenderers = {};
3322                 // @section
3323                 //
3324                 // Panes are DOM elements used to control the ordering of layers on the map. You
3325                 // can access panes with [`map.getPane`](#map-getpane) or
3326                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3327                 // [`map.createPane`](#map-createpane) method.
3328                 //
3329                 // Every map has the following default panes that differ only in zIndex.
3330                 //
3331                 // @pane mapPane: HTMLElement = 'auto'
3332                 // Pane that contains all other map panes
3334                 this._mapPane = this.createPane('mapPane', this._container);
3335                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3337                 // @pane tilePane: HTMLElement = 200
3338                 // Pane for `GridLayer`s and `TileLayer`s
3339                 this.createPane('tilePane');
3340                 // @pane overlayPane: HTMLElement = 400
3341                 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3342                 this.createPane('shadowPane');
3343                 // @pane shadowPane: HTMLElement = 500
3344                 // Pane for overlay shadows (e.g. `Marker` shadows)
3345                 this.createPane('overlayPane');
3346                 // @pane markerPane: HTMLElement = 600
3347                 // Pane for `Icon`s of `Marker`s
3348                 this.createPane('markerPane');
3349                 // @pane tooltipPane: HTMLElement = 650
3350                 // Pane for tooltip.
3351                 this.createPane('tooltipPane');
3352                 // @pane popupPane: HTMLElement = 700
3353                 // Pane for `Popup`s.
3354                 this.createPane('popupPane');
3356                 if (!this.options.markerZoomAnimation) {
3357                         L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3358                         L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3359                 }
3360         },
3363         // private methods that modify map state
3365         // @section Map state change events
3366         _resetView: function (center, zoom) {
3367                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3369                 var loading = !this._loaded;
3370                 this._loaded = true;
3371                 zoom = this._limitZoom(zoom);
3373                 this.fire('viewprereset');
3375                 var zoomChanged = this._zoom !== zoom;
3376                 this
3377                         ._moveStart(zoomChanged)
3378                         ._move(center, zoom)
3379                         ._moveEnd(zoomChanged);
3381                 // @event viewreset: Event
3382                 // Fired when the map needs to redraw its content (this usually happens
3383                 // on map zoom or load). Very useful for creating custom overlays.
3384                 this.fire('viewreset');
3386                 // @event load: Event
3387                 // Fired when the map is initialized (when its center and zoom are set
3388                 // for the first time).
3389                 if (loading) {
3390                         this.fire('load');
3391                 }
3392         },
3394         _moveStart: function (zoomChanged) {
3395                 // @event zoomstart: Event
3396                 // Fired when the map zoom is about to change (e.g. before zoom animation).
3397                 // @event movestart: Event
3398                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3399                 if (zoomChanged) {
3400                         this.fire('zoomstart');
3401                 }
3402                 return this.fire('movestart');
3403         },
3405         _move: function (center, zoom, data) {
3406                 if (zoom === undefined) {
3407                         zoom = this._zoom;
3408                 }
3409                 var zoomChanged = this._zoom !== zoom;
3411                 this._zoom = zoom;
3412                 this._lastCenter = center;
3413                 this._pixelOrigin = this._getNewPixelOrigin(center);
3415                 // @event zoom: Event
3416                 // Fired repeatedly during any change in zoom level, including zoom
3417                 // and fly animations.
3418                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
3419                         this.fire('zoom', data);
3420                 }
3422                 // @event move: Event
3423                 // Fired repeatedly during any movement of the map, including pan and
3424                 // fly animations.
3425                 return this.fire('move', data);
3426         },
3428         _moveEnd: function (zoomChanged) {
3429                 // @event zoomend: Event
3430                 // Fired when the map has changed, after any animations.
3431                 if (zoomChanged) {
3432                         this.fire('zoomend');
3433                 }
3435                 // @event moveend: Event
3436                 // Fired when the center of the map stops changing (e.g. user stopped
3437                 // dragging the map).
3438                 return this.fire('moveend');
3439         },
3441         _stop: function () {
3442                 L.Util.cancelAnimFrame(this._flyToFrame);
3443                 if (this._panAnim) {
3444                         this._panAnim.stop();
3445                 }
3446                 return this;
3447         },
3449         _rawPanBy: function (offset) {
3450                 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3451         },
3453         _getZoomSpan: function () {
3454                 return this.getMaxZoom() - this.getMinZoom();
3455         },
3457         _panInsideMaxBounds: function () {
3458                 if (!this._enforcingBounds) {
3459                         this.panInsideBounds(this.options.maxBounds);
3460                 }
3461         },
3463         _checkIfLoaded: function () {
3464                 if (!this._loaded) {
3465                         throw new Error('Set map center and zoom first.');
3466                 }
3467         },
3469         // DOM event handling
3471         // @section Interaction events
3472         _initEvents: function (remove) {
3473                 if (!L.DomEvent) { return; }
3475                 this._targets = {};
3476                 this._targets[L.stamp(this._container)] = this;
3478                 var onOff = remove ? 'off' : 'on';
3480                 // @event click: MouseEvent
3481                 // Fired when the user clicks (or taps) the map.
3482                 // @event dblclick: MouseEvent
3483                 // Fired when the user double-clicks (or double-taps) the map.
3484                 // @event mousedown: MouseEvent
3485                 // Fired when the user pushes the mouse button on the map.
3486                 // @event mouseup: MouseEvent
3487                 // Fired when the user releases the mouse button on the map.
3488                 // @event mouseover: MouseEvent
3489                 // Fired when the mouse enters the map.
3490                 // @event mouseout: MouseEvent
3491                 // Fired when the mouse leaves the map.
3492                 // @event mousemove: MouseEvent
3493                 // Fired while the mouse moves over the map.
3494                 // @event contextmenu: MouseEvent
3495                 // Fired when the user pushes the right mouse button on the map, prevents
3496                 // default browser context menu from showing if there are listeners on
3497                 // this event. Also fired on mobile when the user holds a single touch
3498                 // for a second (also called long press).
3499                 // @event keypress: KeyboardEvent
3500                 // Fired when the user presses a key from the keyboard while the map is focused.
3501                 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3502                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3504                 if (this.options.trackResize) {
3505                         L.DomEvent[onOff](window, 'resize', this._onResize, this);
3506                 }
3508                 if (L.Browser.any3d && this.options.transform3DLimit) {
3509                         this[onOff]('moveend', this._onMoveEnd);
3510                 }
3511         },
3513         _onResize: function () {
3514                 L.Util.cancelAnimFrame(this._resizeRequest);
3515                 this._resizeRequest = L.Util.requestAnimFrame(
3516                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
3517         },
3519         _onScroll: function () {
3520                 this._container.scrollTop  = 0;
3521                 this._container.scrollLeft = 0;
3522         },
3524         _onMoveEnd: function () {
3525                 var pos = this._getMapPanePos();
3526                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3527                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3528                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3529                         this._resetView(this.getCenter(), this.getZoom());
3530                 }
3531         },
3533         _findEventTargets: function (e, type) {
3534                 var targets = [],
3535                     target,
3536                     isHover = type === 'mouseout' || type === 'mouseover',
3537                     src = e.target || e.srcElement,
3538                     dragging = false;
3540                 while (src) {
3541                         target = this._targets[L.stamp(src)];
3542                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3543                                 // Prevent firing click after you just dragged an object.
3544                                 dragging = true;
3545                                 break;
3546                         }
3547                         if (target && target.listens(type, true)) {
3548                                 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3549                                 targets.push(target);
3550                                 if (isHover) { break; }
3551                         }
3552                         if (src === this._container) { break; }
3553                         src = src.parentNode;
3554                 }
3555                 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3556                         targets = [this];
3557                 }
3558                 return targets;
3559         },
3561         _handleDOMEvent: function (e) {
3562                 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3564                 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3566                 if (type === 'mousedown') {
3567                         // prevents outline when clicking on keyboard-focusable element
3568                         L.DomUtil.preventOutline(e.target || e.srcElement);
3569                 }
3571                 this._fireDOMEvent(e, type);
3572         },
3574         _fireDOMEvent: function (e, type, targets) {
3576                 if (e.type === 'click') {
3577                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3578                         // @event preclick: MouseEvent
3579                         // Fired before mouse click on the map (sometimes useful when you
3580                         // want something to happen on click before any existing click
3581                         // handlers start running).
3582                         var synth = L.Util.extend({}, e);
3583                         synth.type = 'preclick';
3584                         this._fireDOMEvent(synth, synth.type, targets);
3585                 }
3587                 if (e._stopped) { return; }
3589                 // Find the layer the event is propagating from and its parents.
3590                 targets = (targets || []).concat(this._findEventTargets(e, type));
3592                 if (!targets.length) { return; }
3594                 var target = targets[0];
3595                 if (type === 'contextmenu' && target.listens(type, true)) {
3596                         L.DomEvent.preventDefault(e);
3597                 }
3599                 var data = {
3600                         originalEvent: e
3601                 };
3603                 if (e.type !== 'keypress') {
3604                         var isMarker = target instanceof L.Marker;
3605                         data.containerPoint = isMarker ?
3606                                         this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3607                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3608                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3609                 }
3611                 for (var i = 0; i < targets.length; i++) {
3612                         targets[i].fire(type, data, true);
3613                         if (data.originalEvent._stopped ||
3614                                 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3615                 }
3616         },
3618         _draggableMoved: function (obj) {
3619                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3620                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3621         },
3623         _clearHandlers: function () {
3624                 for (var i = 0, len = this._handlers.length; i < len; i++) {
3625                         this._handlers[i].disable();
3626                 }
3627         },
3629         // @section Other Methods
3631         // @method whenReady(fn: Function, context?: Object): this
3632         // Runs the given function `fn` when the map gets initialized with
3633         // a view (center and zoom) and at least one layer, or immediately
3634         // if it's already initialized, optionally passing a function context.
3635         whenReady: function (callback, context) {
3636                 if (this._loaded) {
3637                         callback.call(context || this, {target: this});
3638                 } else {
3639                         this.on('load', callback, context);
3640                 }
3641                 return this;
3642         },
3645         // private methods for getting map state
3647         _getMapPanePos: function () {
3648                 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3649         },
3651         _moved: function () {
3652                 var pos = this._getMapPanePos();
3653                 return pos && !pos.equals([0, 0]);
3654         },
3656         _getTopLeftPoint: function (center, zoom) {
3657                 var pixelOrigin = center && zoom !== undefined ?
3658                         this._getNewPixelOrigin(center, zoom) :
3659                         this.getPixelOrigin();
3660                 return pixelOrigin.subtract(this._getMapPanePos());
3661         },
3663         _getNewPixelOrigin: function (center, zoom) {
3664                 var viewHalf = this.getSize()._divideBy(2);
3665                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3666         },
3668         _latLngToNewLayerPoint: function (latlng, zoom, center) {
3669                 var topLeft = this._getNewPixelOrigin(center, zoom);
3670                 return this.project(latlng, zoom)._subtract(topLeft);
3671         },
3673         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
3674                 var topLeft = this._getNewPixelOrigin(center, zoom);
3675                 return L.bounds([
3676                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
3677                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
3678                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
3679                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
3680                 ]);
3681         },
3683         // layer point of the current center
3684         _getCenterLayerPoint: function () {
3685                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3686         },
3688         // offset of the specified place to the current center in pixels
3689         _getCenterOffset: function (latlng) {
3690                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3691         },
3693         // adjust center for view to get inside bounds
3694         _limitCenter: function (center, zoom, bounds) {
3696                 if (!bounds) { return center; }
3698                 var centerPoint = this.project(center, zoom),
3699                     viewHalf = this.getSize().divideBy(2),
3700                     viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3701                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3703                 // If offset is less than a pixel, ignore.
3704                 // This prevents unstable projections from getting into
3705                 // an infinite loop of tiny offsets.
3706                 if (offset.round().equals([0, 0])) {
3707                         return center;
3708                 }
3710                 return this.unproject(centerPoint.add(offset), zoom);
3711         },
3713         // adjust offset for view to get inside bounds
3714         _limitOffset: function (offset, bounds) {
3715                 if (!bounds) { return offset; }
3717                 var viewBounds = this.getPixelBounds(),
3718                     newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3720                 return offset.add(this._getBoundsOffset(newBounds, bounds));
3721         },
3723         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3724         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3725                 var projectedMaxBounds = L.bounds(
3726                         this.project(maxBounds.getNorthEast(), zoom),
3727                         this.project(maxBounds.getSouthWest(), zoom)
3728                     ),
3729                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3730                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3732                     dx = this._rebound(minOffset.x, -maxOffset.x),
3733                     dy = this._rebound(minOffset.y, -maxOffset.y);
3735                 return new L.Point(dx, dy);
3736         },
3738         _rebound: function (left, right) {
3739                 return left + right > 0 ?
3740                         Math.round(left - right) / 2 :
3741                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3742         },
3744         _limitZoom: function (zoom) {
3745                 var min = this.getMinZoom(),
3746                     max = this.getMaxZoom(),
3747                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3748                 if (snap) {
3749                         zoom = Math.round(zoom / snap) * snap;
3750                 }
3751                 return Math.max(min, Math.min(max, zoom));
3752         },
3754         _onPanTransitionStep: function () {
3755                 this.fire('move');
3756         },
3758         _onPanTransitionEnd: function () {
3759                 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
3760                 this.fire('moveend');
3761         },
3763         _tryAnimatedPan: function (center, options) {
3764                 // difference between the new and current centers in pixels
3765                 var offset = this._getCenterOffset(center)._floor();
3767                 // don't animate too far unless animate: true specified in options
3768                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
3770                 this.panBy(offset, options);
3772                 return true;
3773         },
3775         _createAnimProxy: function () {
3777                 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
3778                 this._panes.mapPane.appendChild(proxy);
3780                 this.on('zoomanim', function (e) {
3781                         var prop = L.DomUtil.TRANSFORM,
3782                             transform = proxy.style[prop];
3784                         L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
3786                         // workaround for case when transform is the same and so transitionend event is not fired
3787                         if (transform === proxy.style[prop] && this._animatingZoom) {
3788                                 this._onZoomTransitionEnd();
3789                         }
3790                 }, this);
3792                 this.on('load moveend', function () {
3793                         var c = this.getCenter(),
3794                             z = this.getZoom();
3795                         L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
3796                 }, this);
3797         },
3799         _catchTransitionEnd: function (e) {
3800                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
3801                         this._onZoomTransitionEnd();
3802                 }
3803         },
3805         _nothingToAnimate: function () {
3806                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
3807         },
3809         _tryAnimatedZoom: function (center, zoom, options) {
3811                 if (this._animatingZoom) { return true; }
3813                 options = options || {};
3815                 // don't animate if disabled, not supported or zoom difference is too large
3816                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
3817                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
3819                 // offset is the pixel coords of the zoom origin relative to the current center
3820                 var scale = this.getZoomScale(zoom),
3821                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
3823                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
3824                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
3826                 L.Util.requestAnimFrame(function () {
3827                         this
3828                             ._moveStart(true)
3829                             ._animateZoom(center, zoom, true);
3830                 }, this);
3832                 return true;
3833         },
3835         _animateZoom: function (center, zoom, startAnim, noUpdate) {
3836                 if (startAnim) {
3837                         this._animatingZoom = true;
3839                         // remember what center/zoom to set after animation
3840                         this._animateToCenter = center;
3841                         this._animateToZoom = zoom;
3843                         L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
3844                 }
3846                 // @event zoomanim: ZoomAnimEvent
3847                 // Fired on every frame of a zoom animation
3848                 this.fire('zoomanim', {
3849                         center: center,
3850                         zoom: zoom,
3851                         noUpdate: noUpdate
3852                 });
3854                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
3855                 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
3856         },
3858         _onZoomTransitionEnd: function () {
3859                 if (!this._animatingZoom) { return; }
3861                 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
3863                 this._animatingZoom = false;
3865                 this._move(this._animateToCenter, this._animateToZoom);
3867                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
3868                 L.Util.requestAnimFrame(function () {
3869                         this._moveEnd(true);
3870                 }, this);
3871         }
3874 // @section
3876 // @factory L.map(id: String, options?: Map options)
3877 // Instantiates a map object given the DOM ID of a `<div>` element
3878 // and optionally an object literal with `Map options`.
3880 // @alternative
3881 // @factory L.map(el: HTMLElement, options?: Map options)
3882 // Instantiates a map object given an instance of a `<div>` HTML element
3883 // and optionally an object literal with `Map options`.
3884 L.map = function (id, options) {
3885         return new L.Map(id, options);
3892  * @class Layer
3893  * @inherits Evented
3894  * @aka L.Layer
3895  * @aka ILayer
3897  * A set of methods from the Layer base class that all Leaflet layers use.
3898  * Inherits all methods, options and events from `L.Evented`.
3900  * @example
3902  * ```js
3903  * var layer = L.Marker(latlng).addTo(map);
3904  * layer.addTo(map);
3905  * layer.remove();
3906  * ```
3908  * @event add: Event
3909  * Fired after the layer is added to a map
3911  * @event remove: Event
3912  * Fired after the layer is removed from a map
3913  */
3916 L.Layer = L.Evented.extend({
3918         // Classes extending `L.Layer` will inherit the following options:
3919         options: {
3920                 // @option pane: String = 'overlayPane'
3921                 // 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.
3922                 pane: 'overlayPane',
3923                 nonBubblingEvents: [],  // Array of events that should not be bubbled to DOM parents (like the map),
3925                 // @option attribution: String = null
3926                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3927                 attribution: null
3928         },
3930         /* @section
3931          * Classes extending `L.Layer` will inherit the following methods:
3932          *
3933          * @method addTo(map: Map): this
3934          * Adds the layer to the given map
3935          */
3936         addTo: function (map) {
3937                 map.addLayer(this);
3938                 return this;
3939         },
3941         // @method remove: this
3942         // Removes the layer from the map it is currently active on.
3943         remove: function () {
3944                 return this.removeFrom(this._map || this._mapToAdd);
3945         },
3947         // @method removeFrom(map: Map): this
3948         // Removes the layer from the given map
3949         removeFrom: function (obj) {
3950                 if (obj) {
3951                         obj.removeLayer(this);
3952                 }
3953                 return this;
3954         },
3956         // @method getPane(name? : String): HTMLElement
3957         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3958         getPane: function (name) {
3959                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3960         },
3962         addInteractiveTarget: function (targetEl) {
3963                 this._map._targets[L.stamp(targetEl)] = this;
3964                 return this;
3965         },
3967         removeInteractiveTarget: function (targetEl) {
3968                 delete this._map._targets[L.stamp(targetEl)];
3969                 return this;
3970         },
3972         // @method getAttribution: String
3973         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3974         getAttribution: function () {
3975                 return this.options.attribution;
3976         },
3978         _layerAdd: function (e) {
3979                 var map = e.target;
3981                 // check in case layer gets added and then removed before the map is ready
3982                 if (!map.hasLayer(this)) { return; }
3984                 this._map = map;
3985                 this._zoomAnimated = map._zoomAnimated;
3987                 if (this.getEvents) {
3988                         var events = this.getEvents();
3989                         map.on(events, this);
3990                         this.once('remove', function () {
3991                                 map.off(events, this);
3992                         }, this);
3993                 }
3995                 this.onAdd(map);
3997                 if (this.getAttribution && map.attributionControl) {
3998                         map.attributionControl.addAttribution(this.getAttribution());
3999                 }
4001                 this.fire('add');
4002                 map.fire('layeradd', {layer: this});
4003         }
4006 /* @section Extension methods
4007  * @uninheritable
4009  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
4011  * @method onAdd(map: Map): this
4012  * 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).
4014  * @method onRemove(map: Map): this
4015  * 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).
4017  * @method getEvents(): Object
4018  * 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.
4020  * @method getAttribution(): String
4021  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
4023  * @method beforeAdd(map: Map): this
4024  * 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.
4025  */
4028 /* @namespace Map
4029  * @section Layer events
4031  * @event layeradd: LayerEvent
4032  * Fired when a new layer is added to the map.
4034  * @event layerremove: LayerEvent
4035  * Fired when some layer is removed from the map
4037  * @section Methods for Layers and Controls
4038  */
4039 L.Map.include({
4040         // @method addLayer(layer: Layer): this
4041         // Adds the given layer to the map
4042         addLayer: function (layer) {
4043                 var id = L.stamp(layer);
4044                 if (this._layers[id]) { return this; }
4045                 this._layers[id] = layer;
4047                 layer._mapToAdd = this;
4049                 if (layer.beforeAdd) {
4050                         layer.beforeAdd(this);
4051                 }
4053                 this.whenReady(layer._layerAdd, layer);
4055                 return this;
4056         },
4058         // @method removeLayer(layer: Layer): this
4059         // Removes the given layer from the map.
4060         removeLayer: function (layer) {
4061                 var id = L.stamp(layer);
4063                 if (!this._layers[id]) { return this; }
4065                 if (this._loaded) {
4066                         layer.onRemove(this);
4067                 }
4069                 if (layer.getAttribution && this.attributionControl) {
4070                         this.attributionControl.removeAttribution(layer.getAttribution());
4071                 }
4073                 delete this._layers[id];
4075                 if (this._loaded) {
4076                         this.fire('layerremove', {layer: layer});
4077                         layer.fire('remove');
4078                 }
4080                 layer._map = layer._mapToAdd = null;
4082                 return this;
4083         },
4085         // @method hasLayer(layer: Layer): Boolean
4086         // Returns `true` if the given layer is currently added to the map
4087         hasLayer: function (layer) {
4088                 return !!layer && (L.stamp(layer) in this._layers);
4089         },
4091         /* @method eachLayer(fn: Function, context?: Object): this
4092          * Iterates over the layers of the map, optionally specifying context of the iterator function.
4093          * ```
4094          * map.eachLayer(function(layer){
4095          *     layer.bindPopup('Hello');
4096          * });
4097          * ```
4098          */
4099         eachLayer: function (method, context) {
4100                 for (var i in this._layers) {
4101                         method.call(context, this._layers[i]);
4102                 }
4103                 return this;
4104         },
4106         _addLayers: function (layers) {
4107                 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
4109                 for (var i = 0, len = layers.length; i < len; i++) {
4110                         this.addLayer(layers[i]);
4111                 }
4112         },
4114         _addZoomLimit: function (layer) {
4115                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
4116                         this._zoomBoundLayers[L.stamp(layer)] = layer;
4117                         this._updateZoomLevels();
4118                 }
4119         },
4121         _removeZoomLimit: function (layer) {
4122                 var id = L.stamp(layer);
4124                 if (this._zoomBoundLayers[id]) {
4125                         delete this._zoomBoundLayers[id];
4126                         this._updateZoomLevels();
4127                 }
4128         },
4130         _updateZoomLevels: function () {
4131                 var minZoom = Infinity,
4132                     maxZoom = -Infinity,
4133                     oldZoomSpan = this._getZoomSpan();
4135                 for (var i in this._zoomBoundLayers) {
4136                         var options = this._zoomBoundLayers[i].options;
4138                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
4139                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
4140                 }
4142                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
4143                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
4145                 // @section Map state change events
4146                 // @event zoomlevelschange: Event
4147                 // Fired when the number of zoomlevels on the map is changed due
4148                 // to adding or removing a layer.
4149                 if (oldZoomSpan !== this._getZoomSpan()) {
4150                         this.fire('zoomlevelschange');
4151                 }
4153                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
4154                         this.setZoom(this._layersMaxZoom);
4155                 }
4156                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
4157                         this.setZoom(this._layersMinZoom);
4158                 }
4159         }
4165  * @namespace DomEvent
4166  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
4167  */
4169 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
4173 var eventsKey = '_leaflet_events';
4175 L.DomEvent = {
4177         // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
4178         // Adds a listener function (`fn`) to a particular DOM event type of the
4179         // element `el`. You can optionally specify the context of the listener
4180         // (object the `this` keyword will point to). You can also pass several
4181         // space-separated types (e.g. `'click dblclick'`).
4183         // @alternative
4184         // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
4185         // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4186         on: function (obj, types, fn, context) {
4188                 if (typeof types === 'object') {
4189                         for (var type in types) {
4190                                 this._on(obj, type, types[type], fn);
4191                         }
4192                 } else {
4193                         types = L.Util.splitWords(types);
4195                         for (var i = 0, len = types.length; i < len; i++) {
4196                                 this._on(obj, types[i], fn, context);
4197                         }
4198                 }
4200                 return this;
4201         },
4203         // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
4204         // Removes a previously added listener function. If no function is specified,
4205         // it will remove all the listeners of that particular DOM event from the element.
4206         // Note that if you passed a custom context to on, you must pass the same
4207         // context to `off` in order to remove the listener.
4209         // @alternative
4210         // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
4211         // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4212         off: function (obj, types, fn, context) {
4214                 if (typeof types === 'object') {
4215                         for (var type in types) {
4216                                 this._off(obj, type, types[type], fn);
4217                         }
4218                 } else {
4219                         types = L.Util.splitWords(types);
4221                         for (var i = 0, len = types.length; i < len; i++) {
4222                                 this._off(obj, types[i], fn, context);
4223                         }
4224                 }
4226                 return this;
4227         },
4229         _on: function (obj, type, fn, context) {
4230                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
4232                 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
4234                 var handler = function (e) {
4235                         return fn.call(context || obj, e || window.event);
4236                 };
4238                 var originalHandler = handler;
4240                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4241                         this.addPointerListener(obj, type, handler, id);
4243                 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener &&
4244                            !(L.Browser.pointer && L.Browser.chrome)) {
4245                         // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
4246                         // See #5180
4247                         this.addDoubleTapListener(obj, handler, id);
4249                 } else if ('addEventListener' in obj) {
4251                         if (type === 'mousewheel') {
4252                                 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4254                         } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
4255                                 handler = function (e) {
4256                                         e = e || window.event;
4257                                         if (L.DomEvent._isExternalTarget(obj, e)) {
4258                                                 originalHandler(e);
4259                                         }
4260                                 };
4261                                 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
4263                         } else {
4264                                 if (type === 'click' && L.Browser.android) {
4265                                         handler = function (e) {
4266                                                 return L.DomEvent._filterClick(e, originalHandler);
4267                                         };
4268                                 }
4269                                 obj.addEventListener(type, handler, false);
4270                         }
4272                 } else if ('attachEvent' in obj) {
4273                         obj.attachEvent('on' + type, handler);
4274                 }
4276                 obj[eventsKey] = obj[eventsKey] || {};
4277                 obj[eventsKey][id] = handler;
4279                 return this;
4280         },
4282         _off: function (obj, type, fn, context) {
4284                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
4285                     handler = obj[eventsKey] && obj[eventsKey][id];
4287                 if (!handler) { return this; }
4289                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4290                         this.removePointerListener(obj, type, id);
4292                 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
4293                         this.removeDoubleTapListener(obj, id);
4295                 } else if ('removeEventListener' in obj) {
4297                         if (type === 'mousewheel') {
4298                                 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4300                         } else {
4301                                 obj.removeEventListener(
4302                                         type === 'mouseenter' ? 'mouseover' :
4303                                         type === 'mouseleave' ? 'mouseout' : type, handler, false);
4304                         }
4306                 } else if ('detachEvent' in obj) {
4307                         obj.detachEvent('on' + type, handler);
4308                 }
4310                 obj[eventsKey][id] = null;
4312                 return this;
4313         },
4315         // @function stopPropagation(ev: DOMEvent): this
4316         // Stop the given event from propagation to parent elements. Used inside the listener functions:
4317         // ```js
4318         // L.DomEvent.on(div, 'click', function (ev) {
4319         //      L.DomEvent.stopPropagation(ev);
4320         // });
4321         // ```
4322         stopPropagation: function (e) {
4324                 if (e.stopPropagation) {
4325                         e.stopPropagation();
4326                 } else if (e.originalEvent) {  // In case of Leaflet event.
4327                         e.originalEvent._stopped = true;
4328                 } else {
4329                         e.cancelBubble = true;
4330                 }
4331                 L.DomEvent._skipped(e);
4333                 return this;
4334         },
4336         // @function disableScrollPropagation(el: HTMLElement): this
4337         // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
4338         disableScrollPropagation: function (el) {
4339                 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
4340         },
4342         // @function disableClickPropagation(el: HTMLElement): this
4343         // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
4344         // `'mousedown'` and `'touchstart'` events (plus browser variants).
4345         disableClickPropagation: function (el) {
4346                 var stop = L.DomEvent.stopPropagation;
4348                 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
4350                 return L.DomEvent.on(el, {
4351                         click: L.DomEvent._fakeStop,
4352                         dblclick: stop
4353                 });
4354         },
4356         // @function preventDefault(ev: DOMEvent): this
4357         // Prevents the default action of the DOM Event `ev` from happening (such as
4358         // following a link in the href of the a element, or doing a POST request
4359         // with page reload when a `<form>` is submitted).
4360         // Use it inside listener functions.
4361         preventDefault: function (e) {
4363                 if (e.preventDefault) {
4364                         e.preventDefault();
4365                 } else {
4366                         e.returnValue = false;
4367                 }
4368                 return this;
4369         },
4371         // @function stop(ev): this
4372         // Does `stopPropagation` and `preventDefault` at the same time.
4373         stop: function (e) {
4374                 return L.DomEvent
4375                         .preventDefault(e)
4376                         .stopPropagation(e);
4377         },
4379         // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
4380         // Gets normalized mouse position from a DOM event relative to the
4381         // `container` or to the whole page if not specified.
4382         getMousePosition: function (e, container) {
4383                 if (!container) {
4384                         return new L.Point(e.clientX, e.clientY);
4385                 }
4387                 var rect = container.getBoundingClientRect();
4389                 return new L.Point(
4390                         e.clientX - rect.left - container.clientLeft,
4391                         e.clientY - rect.top - container.clientTop);
4392         },
4394         // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
4395         // and Firefox scrolls device pixels, not CSS pixels
4396         _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
4397                         L.Browser.gecko ? window.devicePixelRatio :
4398                         1,
4400         // @function getWheelDelta(ev: DOMEvent): Number
4401         // Gets normalized wheel delta from a mousewheel DOM event, in vertical
4402         // pixels scrolled (negative if scrolling down).
4403         // Events from pointing devices without precise scrolling are mapped to
4404         // a best guess of 60 pixels.
4405         getWheelDelta: function (e) {
4406                 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
4407                        (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
4408                        (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
4409                        (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
4410                        (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
4411                        e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
4412                        (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
4413                        e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
4414                        0;
4415         },
4417         _skipEvents: {},
4419         _fakeStop: function (e) {
4420                 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
4421                 L.DomEvent._skipEvents[e.type] = true;
4422         },
4424         _skipped: function (e) {
4425                 var skipped = this._skipEvents[e.type];
4426                 // reset when checking, as it's only used in map container and propagates outside of the map
4427                 this._skipEvents[e.type] = false;
4428                 return skipped;
4429         },
4431         // check if element really left/entered the event target (for mouseenter/mouseleave)
4432         _isExternalTarget: function (el, e) {
4434                 var related = e.relatedTarget;
4436                 if (!related) { return true; }
4438                 try {
4439                         while (related && (related !== el)) {
4440                                 related = related.parentNode;
4441                         }
4442                 } catch (err) {
4443                         return false;
4444                 }
4445                 return (related !== el);
4446         },
4448         // this is a horrible workaround for a bug in Android where a single touch triggers two click events
4449         _filterClick: function (e, handler) {
4450                 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
4451                     elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
4453                 // are they closer together than 500ms yet more than 100ms?
4454                 // Android typically triggers them ~300ms apart while multiple listeners
4455                 // on the same event should be triggered far faster;
4456                 // or check if click is simulated on the element, and if it is, reject any non-simulated events
4458                 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
4459                         L.DomEvent.stop(e);
4460                         return;
4461                 }
4462                 L.DomEvent._lastClick = timeStamp;
4464                 handler(e);
4465         }
4468 // @function addListener(…): this
4469 // Alias to [`L.DomEvent.on`](#domevent-on)
4470 L.DomEvent.addListener = L.DomEvent.on;
4472 // @function removeListener(…): this
4473 // Alias to [`L.DomEvent.off`](#domevent-off)
4474 L.DomEvent.removeListener = L.DomEvent.off;
4479  * @class PosAnimation
4480  * @aka L.PosAnimation
4481  * @inherits Evented
4482  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
4484  * @example
4485  * ```js
4486  * var fx = new L.PosAnimation();
4487  * fx.run(el, [300, 500], 0.5);
4488  * ```
4490  * @constructor L.PosAnimation()
4491  * Creates a `PosAnimation` object.
4493  */
4495 L.PosAnimation = L.Evented.extend({
4497         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
4498         // Run an animation of a given element to a new position, optionally setting
4499         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
4500         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
4501         // `0.5` by default).
4502         run: function (el, newPos, duration, easeLinearity) {
4503                 this.stop();
4505                 this._el = el;
4506                 this._inProgress = true;
4507                 this._duration = duration || 0.25;
4508                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
4510                 this._startPos = L.DomUtil.getPosition(el);
4511                 this._offset = newPos.subtract(this._startPos);
4512                 this._startTime = +new Date();
4514                 // @event start: Event
4515                 // Fired when the animation starts
4516                 this.fire('start');
4518                 this._animate();
4519         },
4521         // @method stop()
4522         // Stops the animation (if currently running).
4523         stop: function () {
4524                 if (!this._inProgress) { return; }
4526                 this._step(true);
4527                 this._complete();
4528         },
4530         _animate: function () {
4531                 // animation loop
4532                 this._animId = L.Util.requestAnimFrame(this._animate, this);
4533                 this._step();
4534         },
4536         _step: function (round) {
4537                 var elapsed = (+new Date()) - this._startTime,
4538                     duration = this._duration * 1000;
4540                 if (elapsed < duration) {
4541                         this._runFrame(this._easeOut(elapsed / duration), round);
4542                 } else {
4543                         this._runFrame(1);
4544                         this._complete();
4545                 }
4546         },
4548         _runFrame: function (progress, round) {
4549                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
4550                 if (round) {
4551                         pos._round();
4552                 }
4553                 L.DomUtil.setPosition(this._el, pos);
4555                 // @event step: Event
4556                 // Fired continuously during the animation.
4557                 this.fire('step');
4558         },
4560         _complete: function () {
4561                 L.Util.cancelAnimFrame(this._animId);
4563                 this._inProgress = false;
4564                 // @event end: Event
4565                 // Fired when the animation ends.
4566                 this.fire('end');
4567         },
4569         _easeOut: function (t) {
4570                 return 1 - Math.pow(1 - t, this._easeOutPower);
4571         }
4577  * @namespace Projection
4578  * @projection L.Projection.Mercator
4580  * 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.
4581  */
4583 L.Projection.Mercator = {
4584         R: 6378137,
4585         R_MINOR: 6356752.314245179,
4587         bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
4589         project: function (latlng) {
4590                 var d = Math.PI / 180,
4591                     r = this.R,
4592                     y = latlng.lat * d,
4593                     tmp = this.R_MINOR / r,
4594                     e = Math.sqrt(1 - tmp * tmp),
4595                     con = e * Math.sin(y);
4597                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
4598                 y = -r * Math.log(Math.max(ts, 1E-10));
4600                 return new L.Point(latlng.lng * d * r, y);
4601         },
4603         unproject: function (point) {
4604                 var d = 180 / Math.PI,
4605                     r = this.R,
4606                     tmp = this.R_MINOR / r,
4607                     e = Math.sqrt(1 - tmp * tmp),
4608                     ts = Math.exp(-point.y / r),
4609                     phi = Math.PI / 2 - 2 * Math.atan(ts);
4611                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
4612                         con = e * Math.sin(phi);
4613                         con = Math.pow((1 - con) / (1 + con), e / 2);
4614                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
4615                         phi += dphi;
4616                 }
4618                 return new L.LatLng(phi * d, point.x * d / r);
4619         }
4625  * @namespace CRS
4626  * @crs L.CRS.EPSG3395
4628  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
4629  */
4631 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
4632         code: 'EPSG:3395',
4633         projection: L.Projection.Mercator,
4635         transformation: (function () {
4636                 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
4637                 return new L.Transformation(scale, 0.5, -scale, 0.5);
4638         }())
4644  * @class GridLayer
4645  * @inherits Layer
4646  * @aka L.GridLayer
4648  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
4649  * 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.
4652  * @section Synchronous usage
4653  * @example
4655  * 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.
4657  * ```js
4658  * var CanvasLayer = L.GridLayer.extend({
4659  *     createTile: function(coords){
4660  *         // create a <canvas> element for drawing
4661  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4663  *         // setup tile width and height according to the options
4664  *         var size = this.getTileSize();
4665  *         tile.width = size.x;
4666  *         tile.height = size.y;
4668  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
4669  *         var ctx = tile.getContext('2d');
4671  *         // return the tile so it can be rendered on screen
4672  *         return tile;
4673  *     }
4674  * });
4675  * ```
4677  * @section Asynchronous usage
4678  * @example
4680  * 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.
4682  * ```js
4683  * var CanvasLayer = L.GridLayer.extend({
4684  *     createTile: function(coords, done){
4685  *         var error;
4687  *         // create a <canvas> element for drawing
4688  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4690  *         // setup tile width and height according to the options
4691  *         var size = this.getTileSize();
4692  *         tile.width = size.x;
4693  *         tile.height = size.y;
4695  *         // draw something asynchronously and pass the tile to the done() callback
4696  *         setTimeout(function() {
4697  *             done(error, tile);
4698  *         }, 1000);
4700  *         return tile;
4701  *     }
4702  * });
4703  * ```
4705  * @section
4706  */
4709 L.GridLayer = L.Layer.extend({
4711         // @section
4712         // @aka GridLayer options
4713         options: {
4714                 // @option tileSize: Number|Point = 256
4715                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
4716                 tileSize: 256,
4718                 // @option opacity: Number = 1.0
4719                 // Opacity of the tiles. Can be used in the `createTile()` function.
4720                 opacity: 1,
4722                 // @option updateWhenIdle: Boolean = depends
4723                 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
4724                 updateWhenIdle: L.Browser.mobile,
4726                 // @option updateWhenZooming: Boolean = true
4727                 // 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.
4728                 updateWhenZooming: true,
4730                 // @option updateInterval: Number = 200
4731                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
4732                 updateInterval: 200,
4734                 // @option zIndex: Number = 1
4735                 // The explicit zIndex of the tile layer.
4736                 zIndex: 1,
4738                 // @option bounds: LatLngBounds = undefined
4739                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
4740                 bounds: null,
4742                 // @option minZoom: Number = 0
4743                 // The minimum zoom level that tiles will be loaded at. By default the entire map.
4744                 minZoom: 0,
4746                 // @option maxZoom: Number = undefined
4747                 // The maximum zoom level that tiles will be loaded at.
4748                 maxZoom: undefined,
4750                 // @option noWrap: Boolean = false
4751                 // Whether the layer is wrapped around the antimeridian. If `true`, the
4752                 // GridLayer will only be displayed once at low zoom levels. Has no
4753                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
4754                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
4755                 // tiles outside the CRS limits.
4756                 noWrap: false,
4758                 // @option pane: String = 'tilePane'
4759                 // `Map pane` where the grid layer will be added.
4760                 pane: 'tilePane',
4762                 // @option className: String = ''
4763                 // A custom class name to assign to the tile layer. Empty by default.
4764                 className: '',
4766                 // @option keepBuffer: Number = 2
4767                 // When panning the map, keep this many rows and columns of tiles before unloading them.
4768                 keepBuffer: 2
4769         },
4771         initialize: function (options) {
4772                 L.setOptions(this, options);
4773         },
4775         onAdd: function () {
4776                 this._initContainer();
4778                 this._levels = {};
4779                 this._tiles = {};
4781                 this._resetView();
4782                 this._update();
4783         },
4785         beforeAdd: function (map) {
4786                 map._addZoomLimit(this);
4787         },
4789         onRemove: function (map) {
4790                 this._removeAllTiles();
4791                 L.DomUtil.remove(this._container);
4792                 map._removeZoomLimit(this);
4793                 this._container = null;
4794                 this._tileZoom = null;
4795         },
4797         // @method bringToFront: this
4798         // Brings the tile layer to the top of all tile layers.
4799         bringToFront: function () {
4800                 if (this._map) {
4801                         L.DomUtil.toFront(this._container);
4802                         this._setAutoZIndex(Math.max);
4803                 }
4804                 return this;
4805         },
4807         // @method bringToBack: this
4808         // Brings the tile layer to the bottom of all tile layers.
4809         bringToBack: function () {
4810                 if (this._map) {
4811                         L.DomUtil.toBack(this._container);
4812                         this._setAutoZIndex(Math.min);
4813                 }
4814                 return this;
4815         },
4817         // @method getContainer: HTMLElement
4818         // Returns the HTML element that contains the tiles for this layer.
4819         getContainer: function () {
4820                 return this._container;
4821         },
4823         // @method setOpacity(opacity: Number): this
4824         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
4825         setOpacity: function (opacity) {
4826                 this.options.opacity = opacity;
4827                 this._updateOpacity();
4828                 return this;
4829         },
4831         // @method setZIndex(zIndex: Number): this
4832         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
4833         setZIndex: function (zIndex) {
4834                 this.options.zIndex = zIndex;
4835                 this._updateZIndex();
4837                 return this;
4838         },
4840         // @method isLoading: Boolean
4841         // Returns `true` if any tile in the grid layer has not finished loading.
4842         isLoading: function () {
4843                 return this._loading;
4844         },
4846         // @method redraw: this
4847         // Causes the layer to clear all the tiles and request them again.
4848         redraw: function () {
4849                 if (this._map) {
4850                         this._removeAllTiles();
4851                         this._update();
4852                 }
4853                 return this;
4854         },
4856         getEvents: function () {
4857                 var events = {
4858                         viewprereset: this._invalidateAll,
4859                         viewreset: this._resetView,
4860                         zoom: this._resetView,
4861                         moveend: this._onMoveEnd
4862                 };
4864                 if (!this.options.updateWhenIdle) {
4865                         // update tiles on move, but not more often than once per given interval
4866                         if (!this._onMove) {
4867                                 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4868                         }
4870                         events.move = this._onMove;
4871                 }
4873                 if (this._zoomAnimated) {
4874                         events.zoomanim = this._animateZoom;
4875                 }
4877                 return events;
4878         },
4880         // @section Extension methods
4881         // Layers extending `GridLayer` shall reimplement the following method.
4882         // @method createTile(coords: Object, done?: Function): HTMLElement
4883         // Called only internally, must be overriden by classes extending `GridLayer`.
4884         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4885         // is specified, it must be called when the tile has finished loading and drawing.
4886         createTile: function () {
4887                 return document.createElement('div');
4888         },
4890         // @section
4891         // @method getTileSize: Point
4892         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4893         getTileSize: function () {
4894                 var s = this.options.tileSize;
4895                 return s instanceof L.Point ? s : new L.Point(s, s);
4896         },
4898         _updateZIndex: function () {
4899                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4900                         this._container.style.zIndex = this.options.zIndex;
4901                 }
4902         },
4904         _setAutoZIndex: function (compare) {
4905                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4907                 var layers = this.getPane().children,
4908                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4910                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4912                         zIndex = layers[i].style.zIndex;
4914                         if (layers[i] !== this._container && zIndex) {
4915                                 edgeZIndex = compare(edgeZIndex, +zIndex);
4916                         }
4917                 }
4919                 if (isFinite(edgeZIndex)) {
4920                         this.options.zIndex = edgeZIndex + compare(-1, 1);
4921                         this._updateZIndex();
4922                 }
4923         },
4925         _updateOpacity: function () {
4926                 if (!this._map) { return; }
4928                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4929                 if (L.Browser.ielt9) { return; }
4931                 L.DomUtil.setOpacity(this._container, this.options.opacity);
4933                 var now = +new Date(),
4934                     nextFrame = false,
4935                     willPrune = false;
4937                 for (var key in this._tiles) {
4938                         var tile = this._tiles[key];
4939                         if (!tile.current || !tile.loaded) { continue; }
4941                         var fade = Math.min(1, (now - tile.loaded) / 200);
4943                         L.DomUtil.setOpacity(tile.el, fade);
4944                         if (fade < 1) {
4945                                 nextFrame = true;
4946                         } else {
4947                                 if (tile.active) { willPrune = true; }
4948                                 tile.active = true;
4949                         }
4950                 }
4952                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4954                 if (nextFrame) {
4955                         L.Util.cancelAnimFrame(this._fadeFrame);
4956                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4957                 }
4958         },
4960         _initContainer: function () {
4961                 if (this._container) { return; }
4963                 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4964                 this._updateZIndex();
4966                 if (this.options.opacity < 1) {
4967                         this._updateOpacity();
4968                 }
4970                 this.getPane().appendChild(this._container);
4971         },
4973         _updateLevels: function () {
4975                 var zoom = this._tileZoom,
4976                     maxZoom = this.options.maxZoom;
4978                 if (zoom === undefined) { return undefined; }
4980                 for (var z in this._levels) {
4981                         if (this._levels[z].el.children.length || z === zoom) {
4982                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4983                         } else {
4984                                 L.DomUtil.remove(this._levels[z].el);
4985                                 this._removeTilesAtZoom(z);
4986                                 delete this._levels[z];
4987                         }
4988                 }
4990                 var level = this._levels[zoom],
4991                     map = this._map;
4993                 if (!level) {
4994                         level = this._levels[zoom] = {};
4996                         level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4997                         level.el.style.zIndex = maxZoom;
4999                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
5000                         level.zoom = zoom;
5002                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
5004                         // force the browser to consider the newly added element for transition
5005                         L.Util.falseFn(level.el.offsetWidth);
5006                 }
5008                 this._level = level;
5010                 return level;
5011         },
5013         _pruneTiles: function () {
5014                 if (!this._map) {
5015                         return;
5016                 }
5018                 var key, tile;
5020                 var zoom = this._map.getZoom();
5021                 if (zoom > this.options.maxZoom ||
5022                         zoom < this.options.minZoom) {
5023                         this._removeAllTiles();
5024                         return;
5025                 }
5027                 for (key in this._tiles) {
5028                         tile = this._tiles[key];
5029                         tile.retain = tile.current;
5030                 }
5032                 for (key in this._tiles) {
5033                         tile = this._tiles[key];
5034                         if (tile.current && !tile.active) {
5035                                 var coords = tile.coords;
5036                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
5037                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
5038                                 }
5039                         }
5040                 }
5042                 for (key in this._tiles) {
5043                         if (!this._tiles[key].retain) {
5044                                 this._removeTile(key);
5045                         }
5046                 }
5047         },
5049         _removeTilesAtZoom: function (zoom) {
5050                 for (var key in this._tiles) {
5051                         if (this._tiles[key].coords.z !== zoom) {
5052                                 continue;
5053                         }
5054                         this._removeTile(key);
5055                 }
5056         },
5058         _removeAllTiles: function () {
5059                 for (var key in this._tiles) {
5060                         this._removeTile(key);
5061                 }
5062         },
5064         _invalidateAll: function () {
5065                 for (var z in this._levels) {
5066                         L.DomUtil.remove(this._levels[z].el);
5067                         delete this._levels[z];
5068                 }
5069                 this._removeAllTiles();
5071                 this._tileZoom = null;
5072         },
5074         _retainParent: function (x, y, z, minZoom) {
5075                 var x2 = Math.floor(x / 2),
5076                     y2 = Math.floor(y / 2),
5077                     z2 = z - 1,
5078                     coords2 = new L.Point(+x2, +y2);
5079                 coords2.z = +z2;
5081                 var key = this._tileCoordsToKey(coords2),
5082                     tile = this._tiles[key];
5084                 if (tile && tile.active) {
5085                         tile.retain = true;
5086                         return true;
5088                 } else if (tile && tile.loaded) {
5089                         tile.retain = true;
5090                 }
5092                 if (z2 > minZoom) {
5093                         return this._retainParent(x2, y2, z2, minZoom);
5094                 }
5096                 return false;
5097         },
5099         _retainChildren: function (x, y, z, maxZoom) {
5101                 for (var i = 2 * x; i < 2 * x + 2; i++) {
5102                         for (var j = 2 * y; j < 2 * y + 2; j++) {
5104                                 var coords = new L.Point(i, j);
5105                                 coords.z = z + 1;
5107                                 var key = this._tileCoordsToKey(coords),
5108                                     tile = this._tiles[key];
5110                                 if (tile && tile.active) {
5111                                         tile.retain = true;
5112                                         continue;
5114                                 } else if (tile && tile.loaded) {
5115                                         tile.retain = true;
5116                                 }
5118                                 if (z + 1 < maxZoom) {
5119                                         this._retainChildren(i, j, z + 1, maxZoom);
5120                                 }
5121                         }
5122                 }
5123         },
5125         _resetView: function (e) {
5126                 var animating = e && (e.pinch || e.flyTo);
5127                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
5128         },
5130         _animateZoom: function (e) {
5131                 this._setView(e.center, e.zoom, true, e.noUpdate);
5132         },
5134         _setView: function (center, zoom, noPrune, noUpdate) {
5135                 var tileZoom = Math.round(zoom);
5136                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
5137                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
5138                         tileZoom = undefined;
5139                 }
5141                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
5143                 if (!noUpdate || tileZoomChanged) {
5145                         this._tileZoom = tileZoom;
5147                         if (this._abortLoading) {
5148                                 this._abortLoading();
5149                         }
5151                         this._updateLevels();
5152                         this._resetGrid();
5154                         if (tileZoom !== undefined) {
5155                                 this._update(center);
5156                         }
5158                         if (!noPrune) {
5159                                 this._pruneTiles();
5160                         }
5162                         // Flag to prevent _updateOpacity from pruning tiles during
5163                         // a zoom anim or a pinch gesture
5164                         this._noPrune = !!noPrune;
5165                 }
5167                 this._setZoomTransforms(center, zoom);
5168         },
5170         _setZoomTransforms: function (center, zoom) {
5171                 for (var i in this._levels) {
5172                         this._setZoomTransform(this._levels[i], center, zoom);
5173                 }
5174         },
5176         _setZoomTransform: function (level, center, zoom) {
5177                 var scale = this._map.getZoomScale(zoom, level.zoom),
5178                     translate = level.origin.multiplyBy(scale)
5179                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
5181                 if (L.Browser.any3d) {
5182                         L.DomUtil.setTransform(level.el, translate, scale);
5183                 } else {
5184                         L.DomUtil.setPosition(level.el, translate);
5185                 }
5186         },
5188         _resetGrid: function () {
5189                 var map = this._map,
5190                     crs = map.options.crs,
5191                     tileSize = this._tileSize = this.getTileSize(),
5192                     tileZoom = this._tileZoom;
5194                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
5195                 if (bounds) {
5196                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
5197                 }
5199                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
5200                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
5201                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
5202                 ];
5203                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
5204                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
5205                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
5206                 ];
5207         },
5209         _onMoveEnd: function () {
5210                 if (!this._map || this._map._animatingZoom) { return; }
5212                 this._update();
5213         },
5215         _getTiledPixelBounds: function (center) {
5216                 var map = this._map,
5217                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
5218                     scale = map.getZoomScale(mapZoom, this._tileZoom),
5219                     pixelCenter = map.project(center, this._tileZoom).floor(),
5220                     halfSize = map.getSize().divideBy(scale * 2);
5222                 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
5223         },
5225         // Private method to load tiles in the grid's active zoom level according to map bounds
5226         _update: function (center) {
5227                 var map = this._map;
5228                 if (!map) { return; }
5229                 var zoom = map.getZoom();
5231                 if (center === undefined) { center = map.getCenter(); }
5232                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
5234                 var pixelBounds = this._getTiledPixelBounds(center),
5235                     tileRange = this._pxBoundsToTileRange(pixelBounds),
5236                     tileCenter = tileRange.getCenter(),
5237                     queue = [],
5238                     margin = this.options.keepBuffer,
5239                     noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
5240                                               tileRange.getTopRight().add([margin, -margin]));
5242                 for (var key in this._tiles) {
5243                         var c = this._tiles[key].coords;
5244                         if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
5245                                 this._tiles[key].current = false;
5246                         }
5247                 }
5249                 // _update just loads more tiles. If the tile zoom level differs too much
5250                 // from the map's, let _setView reset levels and prune old tiles.
5251                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
5253                 // create a queue of coordinates to load tiles from
5254                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
5255                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
5256                                 var coords = new L.Point(i, j);
5257                                 coords.z = this._tileZoom;
5259                                 if (!this._isValidTile(coords)) { continue; }
5261                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
5262                                 if (tile) {
5263                                         tile.current = true;
5264                                 } else {
5265                                         queue.push(coords);
5266                                 }
5267                         }
5268                 }
5270                 // sort tile queue to load tiles in order of their distance to center
5271                 queue.sort(function (a, b) {
5272                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
5273                 });
5275                 if (queue.length !== 0) {
5276                         // if it's the first batch of tiles to load
5277                         if (!this._loading) {
5278                                 this._loading = true;
5279                                 // @event loading: Event
5280                                 // Fired when the grid layer starts loading tiles.
5281                                 this.fire('loading');
5282                         }
5284                         // create DOM fragment to append tiles in one batch
5285                         var fragment = document.createDocumentFragment();
5287                         for (i = 0; i < queue.length; i++) {
5288                                 this._addTile(queue[i], fragment);
5289                         }
5291                         this._level.el.appendChild(fragment);
5292                 }
5293         },
5295         _isValidTile: function (coords) {
5296                 var crs = this._map.options.crs;
5298                 if (!crs.infinite) {
5299                         // don't load tile if it's out of bounds and not wrapped
5300                         var bounds = this._globalTileRange;
5301                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
5302                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
5303                 }
5305                 if (!this.options.bounds) { return true; }
5307                 // don't load tile if it doesn't intersect the bounds in options
5308                 var tileBounds = this._tileCoordsToBounds(coords);
5309                 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
5310         },
5312         _keyToBounds: function (key) {
5313                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
5314         },
5316         // converts tile coordinates to its geographical bounds
5317         _tileCoordsToBounds: function (coords) {
5319                 var map = this._map,
5320                     tileSize = this.getTileSize(),
5322                     nwPoint = coords.scaleBy(tileSize),
5323                     sePoint = nwPoint.add(tileSize),
5325                     nw = map.unproject(nwPoint, coords.z),
5326                     se = map.unproject(sePoint, coords.z),
5327                     bounds = new L.LatLngBounds(nw, se);
5329                 if (!this.options.noWrap) {
5330                         map.wrapLatLngBounds(bounds);
5331                 }
5333                 return bounds;
5334         },
5336         // converts tile coordinates to key for the tile cache
5337         _tileCoordsToKey: function (coords) {
5338                 return coords.x + ':' + coords.y + ':' + coords.z;
5339         },
5341         // converts tile cache key to coordinates
5342         _keyToTileCoords: function (key) {
5343                 var k = key.split(':'),
5344                     coords = new L.Point(+k[0], +k[1]);
5345                 coords.z = +k[2];
5346                 return coords;
5347         },
5349         _removeTile: function (key) {
5350                 var tile = this._tiles[key];
5351                 if (!tile) { return; }
5353                 L.DomUtil.remove(tile.el);
5355                 delete this._tiles[key];
5357                 // @event tileunload: TileEvent
5358                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
5359                 this.fire('tileunload', {
5360                         tile: tile.el,
5361                         coords: this._keyToTileCoords(key)
5362                 });
5363         },
5365         _initTile: function (tile) {
5366                 L.DomUtil.addClass(tile, 'leaflet-tile');
5368                 var tileSize = this.getTileSize();
5369                 tile.style.width = tileSize.x + 'px';
5370                 tile.style.height = tileSize.y + 'px';
5372                 tile.onselectstart = L.Util.falseFn;
5373                 tile.onmousemove = L.Util.falseFn;
5375                 // update opacity on tiles in IE7-8 because of filter inheritance problems
5376                 if (L.Browser.ielt9 && this.options.opacity < 1) {
5377                         L.DomUtil.setOpacity(tile, this.options.opacity);
5378                 }
5380                 // without this hack, tiles disappear after zoom on Chrome for Android
5381                 // https://github.com/Leaflet/Leaflet/issues/2078
5382                 if (L.Browser.android && !L.Browser.android23) {
5383                         tile.style.WebkitBackfaceVisibility = 'hidden';
5384                 }
5385         },
5387         _addTile: function (coords, container) {
5388                 var tilePos = this._getTilePos(coords),
5389                     key = this._tileCoordsToKey(coords);
5391                 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
5393                 this._initTile(tile);
5395                 // if createTile is defined with a second argument ("done" callback),
5396                 // we know that tile is async and will be ready later; otherwise
5397                 if (this.createTile.length < 2) {
5398                         // mark tile as ready, but delay one frame for opacity animation to happen
5399                         L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
5400                 }
5402                 L.DomUtil.setPosition(tile, tilePos);
5404                 // save tile in cache
5405                 this._tiles[key] = {
5406                         el: tile,
5407                         coords: coords,
5408                         current: true
5409                 };
5411                 container.appendChild(tile);
5412                 // @event tileloadstart: TileEvent
5413                 // Fired when a tile is requested and starts loading.
5414                 this.fire('tileloadstart', {
5415                         tile: tile,
5416                         coords: coords
5417                 });
5418         },
5420         _tileReady: function (coords, err, tile) {
5421                 if (!this._map) { return; }
5423                 if (err) {
5424                         // @event tileerror: TileErrorEvent
5425                         // Fired when there is an error loading a tile.
5426                         this.fire('tileerror', {
5427                                 error: err,
5428                                 tile: tile,
5429                                 coords: coords
5430                         });
5431                 }
5433                 var key = this._tileCoordsToKey(coords);
5435                 tile = this._tiles[key];
5436                 if (!tile) { return; }
5438                 tile.loaded = +new Date();
5439                 if (this._map._fadeAnimated) {
5440                         L.DomUtil.setOpacity(tile.el, 0);
5441                         L.Util.cancelAnimFrame(this._fadeFrame);
5442                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
5443                 } else {
5444                         tile.active = true;
5445                         this._pruneTiles();
5446                 }
5448                 if (!err) {
5449                         L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
5451                         // @event tileload: TileEvent
5452                         // Fired when a tile loads.
5453                         this.fire('tileload', {
5454                                 tile: tile.el,
5455                                 coords: coords
5456                         });
5457                 }
5459                 if (this._noTilesToLoad()) {
5460                         this._loading = false;
5461                         // @event load: Event
5462                         // Fired when the grid layer loaded all visible tiles.
5463                         this.fire('load');
5465                         if (L.Browser.ielt9 || !this._map._fadeAnimated) {
5466                                 L.Util.requestAnimFrame(this._pruneTiles, this);
5467                         } else {
5468                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
5469                                 // to trigger a pruning.
5470                                 setTimeout(L.bind(this._pruneTiles, this), 250);
5471                         }
5472                 }
5473         },
5475         _getTilePos: function (coords) {
5476                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
5477         },
5479         _wrapCoords: function (coords) {
5480                 var newCoords = new L.Point(
5481                         this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
5482                         this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
5483                 newCoords.z = coords.z;
5484                 return newCoords;
5485         },
5487         _pxBoundsToTileRange: function (bounds) {
5488                 var tileSize = this.getTileSize();
5489                 return new L.Bounds(
5490                         bounds.min.unscaleBy(tileSize).floor(),
5491                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
5492         },
5494         _noTilesToLoad: function () {
5495                 for (var key in this._tiles) {
5496                         if (!this._tiles[key].loaded) { return false; }
5497                 }
5498                 return true;
5499         }
5502 // @factory L.gridLayer(options?: GridLayer options)
5503 // Creates a new instance of GridLayer with the supplied options.
5504 L.gridLayer = function (options) {
5505         return new L.GridLayer(options);
5511  * @class TileLayer
5512  * @inherits GridLayer
5513  * @aka L.TileLayer
5514  * Used to load and display tile layers on the map. Extends `GridLayer`.
5516  * @example
5518  * ```js
5519  * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
5520  * ```
5522  * @section URL template
5523  * @example
5525  * A string of the following form:
5527  * ```
5528  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
5529  * ```
5531  * `{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.
5533  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
5535  * ```
5536  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
5537  * ```
5538  */
5541 L.TileLayer = L.GridLayer.extend({
5543         // @section
5544         // @aka TileLayer options
5545         options: {
5546                 // @option minZoom: Number = 0
5547                 // Minimum zoom number.
5548                 minZoom: 0,
5550                 // @option maxZoom: Number = 18
5551                 // Maximum zoom number.
5552                 maxZoom: 18,
5554                 // @option maxNativeZoom: Number = null
5555                 // Maximum zoom number the tile source has available. If it is specified,
5556                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
5557                 // from `maxNativeZoom` level and auto-scaled.
5558                 maxNativeZoom: null,
5560                 // @option minNativeZoom: Number = null
5561                 // Minimum zoom number the tile source has available. If it is specified,
5562                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
5563                 // from `minNativeZoom` level and auto-scaled.
5564                 minNativeZoom: null,
5566                 // @option subdomains: String|String[] = 'abc'
5567                 // 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.
5568                 subdomains: 'abc',
5570                 // @option errorTileUrl: String = ''
5571                 // URL to the tile image to show in place of the tile that failed to load.
5572                 errorTileUrl: '',
5574                 // @option zoomOffset: Number = 0
5575                 // The zoom number used in tile URLs will be offset with this value.
5576                 zoomOffset: 0,
5578                 // @option tms: Boolean = false
5579                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
5580                 tms: false,
5582                 // @option zoomReverse: Boolean = false
5583                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
5584                 zoomReverse: false,
5586                 // @option detectRetina: Boolean = false
5587                 // 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.
5588                 detectRetina: false,
5590                 // @option crossOrigin: Boolean = false
5591                 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
5592                 crossOrigin: false
5593         },
5595         initialize: function (url, options) {
5597                 this._url = url;
5599                 options = L.setOptions(this, options);
5601                 // detecting retina displays, adjusting tileSize and zoom levels
5602                 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
5604                         options.tileSize = Math.floor(options.tileSize / 2);
5606                         if (!options.zoomReverse) {
5607                                 options.zoomOffset++;
5608                                 options.maxZoom--;
5609                         } else {
5610                                 options.zoomOffset--;
5611                                 options.minZoom++;
5612                         }
5614                         options.minZoom = Math.max(0, options.minZoom);
5615                 }
5617                 if (typeof options.subdomains === 'string') {
5618                         options.subdomains = options.subdomains.split('');
5619                 }
5621                 // for https://github.com/Leaflet/Leaflet/issues/137
5622                 if (!L.Browser.android) {
5623                         this.on('tileunload', this._onTileRemove);
5624                 }
5625         },
5627         // @method setUrl(url: String, noRedraw?: Boolean): this
5628         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
5629         setUrl: function (url, noRedraw) {
5630                 this._url = url;
5632                 if (!noRedraw) {
5633                         this.redraw();
5634                 }
5635                 return this;
5636         },
5638         // @method createTile(coords: Object, done?: Function): HTMLElement
5639         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
5640         // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
5641         // callback is called when the tile has been loaded.
5642         createTile: function (coords, done) {
5643                 var tile = document.createElement('img');
5645                 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
5646                 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
5648                 if (this.options.crossOrigin) {
5649                         tile.crossOrigin = '';
5650                 }
5652                 /*
5653                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
5654                  http://www.w3.org/TR/WCAG20-TECHS/H67
5655                 */
5656                 tile.alt = '';
5658                 /*
5659                  Set role="presentation" to force screen readers to ignore this
5660                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
5661                 */
5662                 tile.setAttribute('role', 'presentation');
5664                 tile.src = this.getTileUrl(coords);
5666                 return tile;
5667         },
5669         // @section Extension methods
5670         // @uninheritable
5671         // Layers extending `TileLayer` might reimplement the following method.
5672         // @method getTileUrl(coords: Object): String
5673         // Called only internally, returns the URL for a tile given its coordinates.
5674         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
5675         getTileUrl: function (coords) {
5676                 var data = {
5677                         r: L.Browser.retina ? '@2x' : '',
5678                         s: this._getSubdomain(coords),
5679                         x: coords.x,
5680                         y: coords.y,
5681                         z: this._getZoomForUrl()
5682                 };
5683                 if (this._map && !this._map.options.crs.infinite) {
5684                         var invertedY = this._globalTileRange.max.y - coords.y;
5685                         if (this.options.tms) {
5686                                 data['y'] = invertedY;
5687                         }
5688                         data['-y'] = invertedY;
5689                 }
5691                 return L.Util.template(this._url, L.extend(data, this.options));
5692         },
5694         _tileOnLoad: function (done, tile) {
5695                 // For https://github.com/Leaflet/Leaflet/issues/3332
5696                 if (L.Browser.ielt9) {
5697                         setTimeout(L.bind(done, this, null, tile), 0);
5698                 } else {
5699                         done(null, tile);
5700                 }
5701         },
5703         _tileOnError: function (done, tile, e) {
5704                 var errorUrl = this.options.errorTileUrl;
5705                 if (errorUrl && tile.src !== errorUrl) {
5706                         tile.src = errorUrl;
5707                 }
5708                 done(e, tile);
5709         },
5711         getTileSize: function () {
5712                 var map = this._map,
5713                 tileSize = L.GridLayer.prototype.getTileSize.call(this),
5714                 zoom = this._tileZoom + this.options.zoomOffset,
5715                 minNativeZoom = this.options.minNativeZoom,
5716                 maxNativeZoom = this.options.maxNativeZoom;
5718                 // decrease tile size when scaling below minNativeZoom
5719                 if (minNativeZoom !== null && zoom < minNativeZoom) {
5720                         return tileSize.divideBy(map.getZoomScale(minNativeZoom, zoom)).round();
5721                 }
5723                 // increase tile size when scaling above maxNativeZoom
5724                 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5725                         return tileSize.divideBy(map.getZoomScale(maxNativeZoom, zoom)).round();
5726                 }
5728                 return tileSize;
5729         },
5731         _onTileRemove: function (e) {
5732                 e.tile.onload = null;
5733         },
5735         _getZoomForUrl: function () {
5736                 var zoom = this._tileZoom,
5737                 maxZoom = this.options.maxZoom,
5738                 zoomReverse = this.options.zoomReverse,
5739                 zoomOffset = this.options.zoomOffset,
5740                 minNativeZoom = this.options.minNativeZoom,
5741                 maxNativeZoom = this.options.maxNativeZoom;
5743                 if (zoomReverse) {
5744                         zoom = maxZoom - zoom;
5745                 }
5747                 zoom += zoomOffset;
5749                 if (minNativeZoom !== null && zoom < minNativeZoom) {
5750                         return minNativeZoom;
5751                 }
5753                 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5754                         return maxNativeZoom;
5755                 }
5757                 return zoom;
5758         },
5760         _getSubdomain: function (tilePoint) {
5761                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
5762                 return this.options.subdomains[index];
5763         },
5765         // stops loading all tiles in the background layer
5766         _abortLoading: function () {
5767                 var i, tile;
5768                 for (i in this._tiles) {
5769                         if (this._tiles[i].coords.z !== this._tileZoom) {
5770                                 tile = this._tiles[i].el;
5772                                 tile.onload = L.Util.falseFn;
5773                                 tile.onerror = L.Util.falseFn;
5775                                 if (!tile.complete) {
5776                                         tile.src = L.Util.emptyImageUrl;
5777                                         L.DomUtil.remove(tile);
5778                                 }
5779                         }
5780                 }
5781         }
5785 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
5786 // Instantiates a tile layer object given a `URL template` and optionally an options object.
5788 L.tileLayer = function (url, options) {
5789         return new L.TileLayer(url, options);
5795  * @class TileLayer.WMS
5796  * @inherits TileLayer
5797  * @aka L.TileLayer.WMS
5798  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
5800  * @example
5802  * ```js
5803  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
5804  *      layers: 'nexrad-n0r-900913',
5805  *      format: 'image/png',
5806  *      transparent: true,
5807  *      attribution: "Weather data © 2012 IEM Nexrad"
5808  * });
5809  * ```
5810  */
5812 L.TileLayer.WMS = L.TileLayer.extend({
5814         // @section
5815         // @aka TileLayer.WMS options
5816         // If any custom options not documented here are used, they will be sent to the
5817         // WMS server as extra parameters in each request URL. This can be useful for
5818         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
5819         defaultWmsParams: {
5820                 service: 'WMS',
5821                 request: 'GetMap',
5823                 // @option layers: String = ''
5824                 // **(required)** Comma-separated list of WMS layers to show.
5825                 layers: '',
5827                 // @option styles: String = ''
5828                 // Comma-separated list of WMS styles.
5829                 styles: '',
5831                 // @option format: String = 'image/jpeg'
5832                 // WMS image format (use `'image/png'` for layers with transparency).
5833                 format: 'image/jpeg',
5835                 // @option transparent: Boolean = false
5836                 // If `true`, the WMS service will return images with transparency.
5837                 transparent: false,
5839                 // @option version: String = '1.1.1'
5840                 // Version of the WMS service to use
5841                 version: '1.1.1'
5842         },
5844         options: {
5845                 // @option crs: CRS = null
5846                 // Coordinate Reference System to use for the WMS requests, defaults to
5847                 // map CRS. Don't change this if you're not sure what it means.
5848                 crs: null,
5850                 // @option uppercase: Boolean = false
5851                 // If `true`, WMS request parameter keys will be uppercase.
5852                 uppercase: false
5853         },
5855         initialize: function (url, options) {
5857                 this._url = url;
5859                 var wmsParams = L.extend({}, this.defaultWmsParams);
5861                 // all keys that are not TileLayer options go to WMS params
5862                 for (var i in options) {
5863                         if (!(i in this.options)) {
5864                                 wmsParams[i] = options[i];
5865                         }
5866                 }
5868                 options = L.setOptions(this, options);
5870                 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
5872                 this.wmsParams = wmsParams;
5873         },
5875         onAdd: function (map) {
5877                 this._crs = this.options.crs || map.options.crs;
5878                 this._wmsVersion = parseFloat(this.wmsParams.version);
5880                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
5881                 this.wmsParams[projectionKey] = this._crs.code;
5883                 L.TileLayer.prototype.onAdd.call(this, map);
5884         },
5886         getTileUrl: function (coords) {
5888                 var tileBounds = this._tileCoordsToBounds(coords),
5889                     nw = this._crs.project(tileBounds.getNorthWest()),
5890                     se = this._crs.project(tileBounds.getSouthEast()),
5892                     bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
5893                             [se.y, nw.x, nw.y, se.x] :
5894                             [nw.x, se.y, se.x, nw.y]).join(','),
5896                     url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5898                 return url +
5899                         L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5900                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5901         },
5903         // @method setParams(params: Object, noRedraw?: Boolean): this
5904         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5905         setParams: function (params, noRedraw) {
5907                 L.extend(this.wmsParams, params);
5909                 if (!noRedraw) {
5910                         this.redraw();
5911                 }
5913                 return this;
5914         }
5918 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5919 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5920 L.tileLayer.wms = function (url, options) {
5921         return new L.TileLayer.WMS(url, options);
5927  * @class ImageOverlay
5928  * @aka L.ImageOverlay
5929  * @inherits Interactive layer
5931  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5933  * @example
5935  * ```js
5936  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5937  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5938  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5939  * ```
5940  */
5942 L.ImageOverlay = L.Layer.extend({
5944         // @section
5945         // @aka ImageOverlay options
5946         options: {
5947                 // @option opacity: Number = 1.0
5948                 // The opacity of the image overlay.
5949                 opacity: 1,
5951                 // @option alt: String = ''
5952                 // Text for the `alt` attribute of the image (useful for accessibility).
5953                 alt: '',
5955                 // @option interactive: Boolean = false
5956                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5957                 interactive: false,
5959                 // @option crossOrigin: Boolean = false
5960                 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5961                 crossOrigin: false
5962         },
5964         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5965                 this._url = url;
5966                 this._bounds = L.latLngBounds(bounds);
5968                 L.setOptions(this, options);
5969         },
5971         onAdd: function () {
5972                 if (!this._image) {
5973                         this._initImage();
5975                         if (this.options.opacity < 1) {
5976                                 this._updateOpacity();
5977                         }
5978                 }
5980                 if (this.options.interactive) {
5981                         L.DomUtil.addClass(this._image, 'leaflet-interactive');
5982                         this.addInteractiveTarget(this._image);
5983                 }
5985                 this.getPane().appendChild(this._image);
5986                 this._reset();
5987         },
5989         onRemove: function () {
5990                 L.DomUtil.remove(this._image);
5991                 if (this.options.interactive) {
5992                         this.removeInteractiveTarget(this._image);
5993                 }
5994         },
5996         // @method setOpacity(opacity: Number): this
5997         // Sets the opacity of the overlay.
5998         setOpacity: function (opacity) {
5999                 this.options.opacity = opacity;
6001                 if (this._image) {
6002                         this._updateOpacity();
6003                 }
6004                 return this;
6005         },
6007         setStyle: function (styleOpts) {
6008                 if (styleOpts.opacity) {
6009                         this.setOpacity(styleOpts.opacity);
6010                 }
6011                 return this;
6012         },
6014         // @method bringToFront(): this
6015         // Brings the layer to the top of all overlays.
6016         bringToFront: function () {
6017                 if (this._map) {
6018                         L.DomUtil.toFront(this._image);
6019                 }
6020                 return this;
6021         },
6023         // @method bringToBack(): this
6024         // Brings the layer to the bottom of all overlays.
6025         bringToBack: function () {
6026                 if (this._map) {
6027                         L.DomUtil.toBack(this._image);
6028                 }
6029                 return this;
6030         },
6032         // @method setUrl(url: String): this
6033         // Changes the URL of the image.
6034         setUrl: function (url) {
6035                 this._url = url;
6037                 if (this._image) {
6038                         this._image.src = url;
6039                 }
6040                 return this;
6041         },
6043         // @method setBounds(bounds: LatLngBounds): this
6044         // Update the bounds that this ImageOverlay covers
6045         setBounds: function (bounds) {
6046                 this._bounds = bounds;
6048                 if (this._map) {
6049                         this._reset();
6050                 }
6051                 return this;
6052         },
6054         getEvents: function () {
6055                 var events = {
6056                         zoom: this._reset,
6057                         viewreset: this._reset
6058                 };
6060                 if (this._zoomAnimated) {
6061                         events.zoomanim = this._animateZoom;
6062                 }
6064                 return events;
6065         },
6067         // @method getBounds(): LatLngBounds
6068         // Get the bounds that this ImageOverlay covers
6069         getBounds: function () {
6070                 return this._bounds;
6071         },
6073         // @method getElement(): HTMLElement
6074         // Get the img element that represents the ImageOverlay on the map
6075         getElement: function () {
6076                 return this._image;
6077         },
6079         _initImage: function () {
6080                 var img = this._image = L.DomUtil.create('img',
6081                                 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
6083                 img.onselectstart = L.Util.falseFn;
6084                 img.onmousemove = L.Util.falseFn;
6086                 img.onload = L.bind(this.fire, this, 'load');
6088                 if (this.options.crossOrigin) {
6089                         img.crossOrigin = '';
6090                 }
6092                 img.src = this._url;
6093                 img.alt = this.options.alt;
6094         },
6096         _animateZoom: function (e) {
6097                 var scale = this._map.getZoomScale(e.zoom),
6098                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
6100                 L.DomUtil.setTransform(this._image, offset, scale);
6101         },
6103         _reset: function () {
6104                 var image = this._image,
6105                     bounds = new L.Bounds(
6106                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
6107                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
6108                     size = bounds.getSize();
6110                 L.DomUtil.setPosition(image, bounds.min);
6112                 image.style.width  = size.x + 'px';
6113                 image.style.height = size.y + 'px';
6114         },
6116         _updateOpacity: function () {
6117                 L.DomUtil.setOpacity(this._image, this.options.opacity);
6118         }
6121 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
6122 // Instantiates an image overlay object given the URL of the image and the
6123 // geographical bounds it is tied to.
6124 L.imageOverlay = function (url, bounds, options) {
6125         return new L.ImageOverlay(url, bounds, options);
6131  * @class Icon
6132  * @aka L.Icon
6133  * @inherits Layer
6135  * Represents an icon to provide when creating a marker.
6137  * @example
6139  * ```js
6140  * var myIcon = L.icon({
6141  *     iconUrl: 'my-icon.png',
6142  *     iconRetinaUrl: 'my-icon@2x.png',
6143  *     iconSize: [38, 95],
6144  *     iconAnchor: [22, 94],
6145  *     popupAnchor: [-3, -76],
6146  *     shadowUrl: 'my-icon-shadow.png',
6147  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
6148  *     shadowSize: [68, 95],
6149  *     shadowAnchor: [22, 94]
6150  * });
6152  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6153  * ```
6155  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6157  */
6159 L.Icon = L.Class.extend({
6161         /* @section
6162          * @aka Icon options
6163          *
6164          * @option iconUrl: String = null
6165          * **(required)** The URL to the icon image (absolute or relative to your script path).
6166          *
6167          * @option iconRetinaUrl: String = null
6168          * The URL to a retina sized version of the icon image (absolute or relative to your
6169          * script path). Used for Retina screen devices.
6170          *
6171          * @option iconSize: Point = null
6172          * Size of the icon image in pixels.
6173          *
6174          * @option iconAnchor: Point = null
6175          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6176          * will be aligned so that this point is at the marker's geographical location. Centered
6177          * by default if size is specified, also can be set in CSS with negative margins.
6178          *
6179          * @option popupAnchor: Point = null
6180          * The coordinates of the point from which popups will "open", relative to the icon anchor.
6181          *
6182          * @option shadowUrl: String = null
6183          * The URL to the icon shadow image. If not specified, no shadow image will be created.
6184          *
6185          * @option shadowRetinaUrl: String = null
6186          *
6187          * @option shadowSize: Point = null
6188          * Size of the shadow image in pixels.
6189          *
6190          * @option shadowAnchor: Point = null
6191          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6192          * as iconAnchor if not specified).
6193          *
6194          * @option className: String = ''
6195          * A custom class name to assign to both icon and shadow images. Empty by default.
6196          */
6198         initialize: function (options) {
6199                 L.setOptions(this, options);
6200         },
6202         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6203         // Called internally when the icon has to be shown, returns a `<img>` HTML element
6204         // styled according to the options.
6205         createIcon: function (oldIcon) {
6206                 return this._createIcon('icon', oldIcon);
6207         },
6209         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6210         // As `createIcon`, but for the shadow beneath it.
6211         createShadow: function (oldIcon) {
6212                 return this._createIcon('shadow', oldIcon);
6213         },
6215         _createIcon: function (name, oldIcon) {
6216                 var src = this._getIconUrl(name);
6218                 if (!src) {
6219                         if (name === 'icon') {
6220                                 throw new Error('iconUrl not set in Icon options (see the docs).');
6221                         }
6222                         return null;
6223                 }
6225                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6226                 this._setIconStyles(img, name);
6228                 return img;
6229         },
6231         _setIconStyles: function (img, name) {
6232                 var options = this.options;
6233                 var sizeOption = options[name + 'Size'];
6235                 if (typeof sizeOption === 'number') {
6236                         sizeOption = [sizeOption, sizeOption];
6237                 }
6239                 var size = L.point(sizeOption),
6240                     anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6241                             size && size.divideBy(2, true));
6243                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6245                 if (anchor) {
6246                         img.style.marginLeft = (-anchor.x) + 'px';
6247                         img.style.marginTop  = (-anchor.y) + 'px';
6248                 }
6250                 if (size) {
6251                         img.style.width  = size.x + 'px';
6252                         img.style.height = size.y + 'px';
6253                 }
6254         },
6256         _createImg: function (src, el) {
6257                 el = el || document.createElement('img');
6258                 el.src = src;
6259                 return el;
6260         },
6262         _getIconUrl: function (name) {
6263                 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6264         }
6268 // @factory L.icon(options: Icon options)
6269 // Creates an icon instance with the given options.
6270 L.icon = function (options) {
6271         return new L.Icon(options);
6277  * @miniclass Icon.Default (Icon)
6278  * @aka L.Icon.Default
6279  * @section
6281  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6282  * no icon is specified. Points to the blue marker image distributed with Leaflet
6283  * releases.
6285  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6286  * (which is a set of `Icon options`).
6288  * If you want to _completely_ replace the default icon, override the
6289  * `L.Marker.prototype.options.icon` with your own icon instead.
6290  */
6292 L.Icon.Default = L.Icon.extend({
6294         options: {
6295                 iconUrl:       'marker-icon.png',
6296                 iconRetinaUrl: 'marker-icon-2x.png',
6297                 shadowUrl:     'marker-shadow.png',
6298                 iconSize:    [25, 41],
6299                 iconAnchor:  [12, 41],
6300                 popupAnchor: [1, -34],
6301                 tooltipAnchor: [16, -28],
6302                 shadowSize:  [41, 41]
6303         },
6305         _getIconUrl: function (name) {
6306                 if (!L.Icon.Default.imagePath) {        // Deprecated, backwards-compatibility only
6307                         L.Icon.Default.imagePath = this._detectIconPath();
6308                 }
6310                 // @option imagePath: String
6311                 // `L.Icon.Default` will try to auto-detect the absolute location of the
6312                 // blue icon images. If you are placing these images in a non-standard
6313                 // way, set this option to point to the right absolute path.
6314                 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
6315         },
6317         _detectIconPath: function () {
6318                 var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
6319                 var path = L.DomUtil.getStyle(el, 'background-image') ||
6320                            L.DomUtil.getStyle(el, 'backgroundImage');   // IE8
6322                 document.body.removeChild(el);
6324                 return path.indexOf('url') === 0 ?
6325                         path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
6326         }
6332  * @class Marker
6333  * @inherits Interactive layer
6334  * @aka L.Marker
6335  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
6337  * @example
6339  * ```js
6340  * L.marker([50.5, 30.5]).addTo(map);
6341  * ```
6342  */
6344 L.Marker = L.Layer.extend({
6346         // @section
6347         // @aka Marker options
6348         options: {
6349                 // @option icon: Icon = *
6350                 // 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.
6351                 icon: new L.Icon.Default(),
6353                 // Option inherited from "Interactive layer" abstract class
6354                 interactive: true,
6356                 // @option draggable: Boolean = false
6357                 // Whether the marker is draggable with mouse/touch or not.
6358                 draggable: false,
6360                 // @option keyboard: Boolean = true
6361                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
6362                 keyboard: true,
6364                 // @option title: String = ''
6365                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
6366                 title: '',
6368                 // @option alt: String = ''
6369                 // Text for the `alt` attribute of the icon image (useful for accessibility).
6370                 alt: '',
6372                 // @option zIndexOffset: Number = 0
6373                 // 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).
6374                 zIndexOffset: 0,
6376                 // @option opacity: Number = 1.0
6377                 // The opacity of the marker.
6378                 opacity: 1,
6380                 // @option riseOnHover: Boolean = false
6381                 // If `true`, the marker will get on top of others when you hover the mouse over it.
6382                 riseOnHover: false,
6384                 // @option riseOffset: Number = 250
6385                 // The z-index offset used for the `riseOnHover` feature.
6386                 riseOffset: 250,
6388                 // @option pane: String = 'markerPane'
6389                 // `Map pane` where the markers icon will be added.
6390                 pane: 'markerPane',
6392                 // FIXME: shadowPane is no longer a valid option
6393                 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
6394         },
6396         /* @section
6397          *
6398          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
6399          */
6401         initialize: function (latlng, options) {
6402                 L.setOptions(this, options);
6403                 this._latlng = L.latLng(latlng);
6404         },
6406         onAdd: function (map) {
6407                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
6409                 if (this._zoomAnimated) {
6410                         map.on('zoomanim', this._animateZoom, this);
6411                 }
6413                 this._initIcon();
6414                 this.update();
6415         },
6417         onRemove: function (map) {
6418                 if (this.dragging && this.dragging.enabled()) {
6419                         this.options.draggable = true;
6420                         this.dragging.removeHooks();
6421                 }
6423                 if (this._zoomAnimated) {
6424                         map.off('zoomanim', this._animateZoom, this);
6425                 }
6427                 this._removeIcon();
6428                 this._removeShadow();
6429         },
6431         getEvents: function () {
6432                 return {
6433                         zoom: this.update,
6434                         viewreset: this.update
6435                 };
6436         },
6438         // @method getLatLng: LatLng
6439         // Returns the current geographical position of the marker.
6440         getLatLng: function () {
6441                 return this._latlng;
6442         },
6444         // @method setLatLng(latlng: LatLng): this
6445         // Changes the marker position to the given point.
6446         setLatLng: function (latlng) {
6447                 var oldLatLng = this._latlng;
6448                 this._latlng = L.latLng(latlng);
6449                 this.update();
6451                 // @event move: Event
6452                 // 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`.
6453                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
6454         },
6456         // @method setZIndexOffset(offset: Number): this
6457         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
6458         setZIndexOffset: function (offset) {
6459                 this.options.zIndexOffset = offset;
6460                 return this.update();
6461         },
6463         // @method setIcon(icon: Icon): this
6464         // Changes the marker icon.
6465         setIcon: function (icon) {
6467                 this.options.icon = icon;
6469                 if (this._map) {
6470                         this._initIcon();
6471                         this.update();
6472                 }
6474                 if (this._popup) {
6475                         this.bindPopup(this._popup, this._popup.options);
6476                 }
6478                 return this;
6479         },
6481         getElement: function () {
6482                 return this._icon;
6483         },
6485         update: function () {
6487                 if (this._icon) {
6488                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
6489                         this._setPos(pos);
6490                 }
6492                 return this;
6493         },
6495         _initIcon: function () {
6496                 var options = this.options,
6497                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6499                 var icon = options.icon.createIcon(this._icon),
6500                     addIcon = false;
6502                 // if we're not reusing the icon, remove the old one and init new one
6503                 if (icon !== this._icon) {
6504                         if (this._icon) {
6505                                 this._removeIcon();
6506                         }
6507                         addIcon = true;
6509                         if (options.title) {
6510                                 icon.title = options.title;
6511                         }
6512                         if (options.alt) {
6513                                 icon.alt = options.alt;
6514                         }
6515                 }
6517                 L.DomUtil.addClass(icon, classToAdd);
6519                 if (options.keyboard) {
6520                         icon.tabIndex = '0';
6521                 }
6523                 this._icon = icon;
6525                 if (options.riseOnHover) {
6526                         this.on({
6527                                 mouseover: this._bringToFront,
6528                                 mouseout: this._resetZIndex
6529                         });
6530                 }
6532                 var newShadow = options.icon.createShadow(this._shadow),
6533                     addShadow = false;
6535                 if (newShadow !== this._shadow) {
6536                         this._removeShadow();
6537                         addShadow = true;
6538                 }
6540                 if (newShadow) {
6541                         L.DomUtil.addClass(newShadow, classToAdd);
6542                         newShadow.alt = '';
6543                 }
6544                 this._shadow = newShadow;
6547                 if (options.opacity < 1) {
6548                         this._updateOpacity();
6549                 }
6552                 if (addIcon) {
6553                         this.getPane().appendChild(this._icon);
6554                 }
6555                 this._initInteraction();
6556                 if (newShadow && addShadow) {
6557                         this.getPane('shadowPane').appendChild(this._shadow);
6558                 }
6559         },
6561         _removeIcon: function () {
6562                 if (this.options.riseOnHover) {
6563                         this.off({
6564                                 mouseover: this._bringToFront,
6565                                 mouseout: this._resetZIndex
6566                         });
6567                 }
6569                 L.DomUtil.remove(this._icon);
6570                 this.removeInteractiveTarget(this._icon);
6572                 this._icon = null;
6573         },
6575         _removeShadow: function () {
6576                 if (this._shadow) {
6577                         L.DomUtil.remove(this._shadow);
6578                 }
6579                 this._shadow = null;
6580         },
6582         _setPos: function (pos) {
6583                 L.DomUtil.setPosition(this._icon, pos);
6585                 if (this._shadow) {
6586                         L.DomUtil.setPosition(this._shadow, pos);
6587                 }
6589                 this._zIndex = pos.y + this.options.zIndexOffset;
6591                 this._resetZIndex();
6592         },
6594         _updateZIndex: function (offset) {
6595                 this._icon.style.zIndex = this._zIndex + offset;
6596         },
6598         _animateZoom: function (opt) {
6599                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
6601                 this._setPos(pos);
6602         },
6604         _initInteraction: function () {
6606                 if (!this.options.interactive) { return; }
6608                 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
6610                 this.addInteractiveTarget(this._icon);
6612                 if (L.Handler.MarkerDrag) {
6613                         var draggable = this.options.draggable;
6614                         if (this.dragging) {
6615                                 draggable = this.dragging.enabled();
6616                                 this.dragging.disable();
6617                         }
6619                         this.dragging = new L.Handler.MarkerDrag(this);
6621                         if (draggable) {
6622                                 this.dragging.enable();
6623                         }
6624                 }
6625         },
6627         // @method setOpacity(opacity: Number): this
6628         // Changes the opacity of the marker.
6629         setOpacity: function (opacity) {
6630                 this.options.opacity = opacity;
6631                 if (this._map) {
6632                         this._updateOpacity();
6633                 }
6635                 return this;
6636         },
6638         _updateOpacity: function () {
6639                 var opacity = this.options.opacity;
6641                 L.DomUtil.setOpacity(this._icon, opacity);
6643                 if (this._shadow) {
6644                         L.DomUtil.setOpacity(this._shadow, opacity);
6645                 }
6646         },
6648         _bringToFront: function () {
6649                 this._updateZIndex(this.options.riseOffset);
6650         },
6652         _resetZIndex: function () {
6653                 this._updateZIndex(0);
6654         },
6656         _getPopupAnchor: function () {
6657                 return this.options.icon.options.popupAnchor || [0, 0];
6658         },
6660         _getTooltipAnchor: function () {
6661                 return this.options.icon.options.tooltipAnchor || [0, 0];
6662         }
6666 // factory L.marker(latlng: LatLng, options? : Marker options)
6668 // @factory L.marker(latlng: LatLng, options? : Marker options)
6669 // Instantiates a Marker object given a geographical point and optionally an options object.
6670 L.marker = function (latlng, options) {
6671         return new L.Marker(latlng, options);
6677  * @class DivIcon
6678  * @aka L.DivIcon
6679  * @inherits Icon
6681  * Represents a lightweight icon for markers that uses a simple `<div>`
6682  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
6684  * @example
6685  * ```js
6686  * var myIcon = L.divIcon({className: 'my-div-icon'});
6687  * // you can set .my-div-icon styles in CSS
6689  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6690  * ```
6692  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
6693  */
6695 L.DivIcon = L.Icon.extend({
6696         options: {
6697                 // @section
6698                 // @aka DivIcon options
6699                 iconSize: [12, 12], // also can be set through CSS
6701                 // iconAnchor: (Point),
6702                 // popupAnchor: (Point),
6704                 // @option html: String = ''
6705                 // Custom HTML code to put inside the div element, empty by default.
6706                 html: false,
6708                 // @option bgPos: Point = [0, 0]
6709                 // Optional relative position of the background, in pixels
6710                 bgPos: null,
6712                 className: 'leaflet-div-icon'
6713         },
6715         createIcon: function (oldIcon) {
6716                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
6717                     options = this.options;
6719                 div.innerHTML = options.html !== false ? options.html : '';
6721                 if (options.bgPos) {
6722                         var bgPos = L.point(options.bgPos);
6723                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
6724                 }
6725                 this._setIconStyles(div, 'icon');
6727                 return div;
6728         },
6730         createShadow: function () {
6731                 return null;
6732         }
6735 // @factory L.divIcon(options: DivIcon options)
6736 // Creates a `DivIcon` instance with the given options.
6737 L.divIcon = function (options) {
6738         return new L.DivIcon(options);
6744  * @class DivOverlay
6745  * @inherits Layer
6746  * @aka L.DivOverlay
6747  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
6748  */
6750 // @namespace DivOverlay
6751 L.DivOverlay = L.Layer.extend({
6753         // @section
6754         // @aka DivOverlay options
6755         options: {
6756                 // @option offset: Point = Point(0, 7)
6757                 // The offset of the popup position. Useful to control the anchor
6758                 // of the popup when opening it on some overlays.
6759                 offset: [0, 7],
6761                 // @option className: String = ''
6762                 // A custom CSS class name to assign to the popup.
6763                 className: '',
6765                 // @option pane: String = 'popupPane'
6766                 // `Map pane` where the popup will be added.
6767                 pane: 'popupPane'
6768         },
6770         initialize: function (options, source) {
6771                 L.setOptions(this, options);
6773                 this._source = source;
6774         },
6776         onAdd: function (map) {
6777                 this._zoomAnimated = map._zoomAnimated;
6779                 if (!this._container) {
6780                         this._initLayout();
6781                 }
6783                 if (map._fadeAnimated) {
6784                         L.DomUtil.setOpacity(this._container, 0);
6785                 }
6787                 clearTimeout(this._removeTimeout);
6788                 this.getPane().appendChild(this._container);
6789                 this.update();
6791                 if (map._fadeAnimated) {
6792                         L.DomUtil.setOpacity(this._container, 1);
6793                 }
6795                 this.bringToFront();
6796         },
6798         onRemove: function (map) {
6799                 if (map._fadeAnimated) {
6800                         L.DomUtil.setOpacity(this._container, 0);
6801                         this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
6802                 } else {
6803                         L.DomUtil.remove(this._container);
6804                 }
6805         },
6807         // @namespace Popup
6808         // @method getLatLng: LatLng
6809         // Returns the geographical point of popup.
6810         getLatLng: function () {
6811                 return this._latlng;
6812         },
6814         // @method setLatLng(latlng: LatLng): this
6815         // Sets the geographical point where the popup will open.
6816         setLatLng: function (latlng) {
6817                 this._latlng = L.latLng(latlng);
6818                 if (this._map) {
6819                         this._updatePosition();
6820                         this._adjustPan();
6821                 }
6822                 return this;
6823         },
6825         // @method getContent: String|HTMLElement
6826         // Returns the content of the popup.
6827         getContent: function () {
6828                 return this._content;
6829         },
6831         // @method setContent(htmlContent: String|HTMLElement|Function): this
6832         // 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.
6833         setContent: function (content) {
6834                 this._content = content;
6835                 this.update();
6836                 return this;
6837         },
6839         // @method getElement: String|HTMLElement
6840         // Alias for [getContent()](#popup-getcontent)
6841         getElement: function () {
6842                 return this._container;
6843         },
6845         // @method update: null
6846         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
6847         update: function () {
6848                 if (!this._map) { return; }
6850                 this._container.style.visibility = 'hidden';
6852                 this._updateContent();
6853                 this._updateLayout();
6854                 this._updatePosition();
6856                 this._container.style.visibility = '';
6858                 this._adjustPan();
6859         },
6861         getEvents: function () {
6862                 var events = {
6863                         zoom: this._updatePosition,
6864                         viewreset: this._updatePosition
6865                 };
6867                 if (this._zoomAnimated) {
6868                         events.zoomanim = this._animateZoom;
6869                 }
6870                 return events;
6871         },
6873         // @method isOpen: Boolean
6874         // Returns `true` when the popup is visible on the map.
6875         isOpen: function () {
6876                 return !!this._map && this._map.hasLayer(this);
6877         },
6879         // @method bringToFront: this
6880         // Brings this popup in front of other popups (in the same map pane).
6881         bringToFront: function () {
6882                 if (this._map) {
6883                         L.DomUtil.toFront(this._container);
6884                 }
6885                 return this;
6886         },
6888         // @method bringToBack: this
6889         // Brings this popup to the back of other popups (in the same map pane).
6890         bringToBack: function () {
6891                 if (this._map) {
6892                         L.DomUtil.toBack(this._container);
6893                 }
6894                 return this;
6895         },
6897         _updateContent: function () {
6898                 if (!this._content) { return; }
6900                 var node = this._contentNode;
6901                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
6903                 if (typeof content === 'string') {
6904                         node.innerHTML = content;
6905                 } else {
6906                         while (node.hasChildNodes()) {
6907                                 node.removeChild(node.firstChild);
6908                         }
6909                         node.appendChild(content);
6910                 }
6911                 this.fire('contentupdate');
6912         },
6914         _updatePosition: function () {
6915                 if (!this._map) { return; }
6917                 var pos = this._map.latLngToLayerPoint(this._latlng),
6918                     offset = L.point(this.options.offset),
6919                     anchor = this._getAnchor();
6921                 if (this._zoomAnimated) {
6922                         L.DomUtil.setPosition(this._container, pos.add(anchor));
6923                 } else {
6924                         offset = offset.add(pos).add(anchor);
6925                 }
6927                 var bottom = this._containerBottom = -offset.y,
6928                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6930                 // bottom position the popup in case the height of the popup changes (images loading etc)
6931                 this._container.style.bottom = bottom + 'px';
6932                 this._container.style.left = left + 'px';
6933         },
6935         _getAnchor: function () {
6936                 return [0, 0];
6937         }
6944  * @class Popup
6945  * @inherits DivOverlay
6946  * @aka L.Popup
6947  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6948  * open popups while making sure that only one popup is open at one time
6949  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6951  * @example
6953  * If you want to just bind a popup to marker click and then open it, it's really easy:
6955  * ```js
6956  * marker.bindPopup(popupContent).openPopup();
6957  * ```
6958  * Path overlays like polylines also have a `bindPopup` method.
6959  * Here's a more complicated way to open a popup on a map:
6961  * ```js
6962  * var popup = L.popup()
6963  *      .setLatLng(latlng)
6964  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6965  *      .openOn(map);
6966  * ```
6967  */
6970 // @namespace Popup
6971 L.Popup = L.DivOverlay.extend({
6973         // @section
6974         // @aka Popup options
6975         options: {
6976                 // @option maxWidth: Number = 300
6977                 // Max width of the popup, in pixels.
6978                 maxWidth: 300,
6980                 // @option minWidth: Number = 50
6981                 // Min width of the popup, in pixels.
6982                 minWidth: 50,
6984                 // @option maxHeight: Number = null
6985                 // If set, creates a scrollable container of the given height
6986                 // inside a popup if its content exceeds it.
6987                 maxHeight: null,
6989                 // @option autoPan: Boolean = true
6990                 // Set it to `false` if you don't want the map to do panning animation
6991                 // to fit the opened popup.
6992                 autoPan: true,
6994                 // @option autoPanPaddingTopLeft: Point = null
6995                 // The margin between the popup and the top left corner of the map
6996                 // view after autopanning was performed.
6997                 autoPanPaddingTopLeft: null,
6999                 // @option autoPanPaddingBottomRight: Point = null
7000                 // The margin between the popup and the bottom right corner of the map
7001                 // view after autopanning was performed.
7002                 autoPanPaddingBottomRight: null,
7004                 // @option autoPanPadding: Point = Point(5, 5)
7005                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
7006                 autoPanPadding: [5, 5],
7008                 // @option keepInView: Boolean = false
7009                 // Set it to `true` if you want to prevent users from panning the popup
7010                 // off of the screen while it is open.
7011                 keepInView: false,
7013                 // @option closeButton: Boolean = true
7014                 // Controls the presence of a close button in the popup.
7015                 closeButton: true,
7017                 // @option autoClose: Boolean = true
7018                 // Set it to `false` if you want to override the default behavior of
7019                 // the popup closing when user clicks the map (set globally by
7020                 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
7021                 autoClose: true,
7023                 // @option className: String = ''
7024                 // A custom CSS class name to assign to the popup.
7025                 className: ''
7026         },
7028         // @namespace Popup
7029         // @method openOn(map: Map): this
7030         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
7031         openOn: function (map) {
7032                 map.openPopup(this);
7033                 return this;
7034         },
7036         onAdd: function (map) {
7037                 L.DivOverlay.prototype.onAdd.call(this, map);
7039                 // @namespace Map
7040                 // @section Popup events
7041                 // @event popupopen: PopupEvent
7042                 // Fired when a popup is opened in the map
7043                 map.fire('popupopen', {popup: this});
7045                 if (this._source) {
7046                         // @namespace Layer
7047                         // @section Popup events
7048                         // @event popupopen: PopupEvent
7049                         // Fired when a popup bound to this layer is opened
7050                         this._source.fire('popupopen', {popup: this}, true);
7051                         // For non-path layers, we toggle the popup when clicking
7052                         // again the layer, so prevent the map to reopen it.
7053                         if (!(this._source instanceof L.Path)) {
7054                                 this._source.on('preclick', L.DomEvent.stopPropagation);
7055                         }
7056                 }
7057         },
7059         onRemove: function (map) {
7060                 L.DivOverlay.prototype.onRemove.call(this, map);
7062                 // @namespace Map
7063                 // @section Popup events
7064                 // @event popupclose: PopupEvent
7065                 // Fired when a popup in the map is closed
7066                 map.fire('popupclose', {popup: this});
7068                 if (this._source) {
7069                         // @namespace Layer
7070                         // @section Popup events
7071                         // @event popupclose: PopupEvent
7072                         // Fired when a popup bound to this layer is closed
7073                         this._source.fire('popupclose', {popup: this}, true);
7074                         if (!(this._source instanceof L.Path)) {
7075                                 this._source.off('preclick', L.DomEvent.stopPropagation);
7076                         }
7077                 }
7078         },
7080         getEvents: function () {
7081                 var events = L.DivOverlay.prototype.getEvents.call(this);
7083                 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
7084                         events.preclick = this._close;
7085                 }
7087                 if (this.options.keepInView) {
7088                         events.moveend = this._adjustPan;
7089                 }
7091                 return events;
7092         },
7094         _close: function () {
7095                 if (this._map) {
7096                         this._map.closePopup(this);
7097                 }
7098         },
7100         _initLayout: function () {
7101                 var prefix = 'leaflet-popup',
7102                     container = this._container = L.DomUtil.create('div',
7103                         prefix + ' ' + (this.options.className || '') +
7104                         ' leaflet-zoom-animated');
7106                 if (this.options.closeButton) {
7107                         var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
7108                         closeButton.href = '#close';
7109                         closeButton.innerHTML = '&#215;';
7111                         L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
7112                 }
7114                 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
7115                 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
7117                 L.DomEvent
7118                         .disableClickPropagation(wrapper)
7119                         .disableScrollPropagation(this._contentNode)
7120                         .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
7122                 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
7123                 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
7124         },
7126         _updateLayout: function () {
7127                 var container = this._contentNode,
7128                     style = container.style;
7130                 style.width = '';
7131                 style.whiteSpace = 'nowrap';
7133                 var width = container.offsetWidth;
7134                 width = Math.min(width, this.options.maxWidth);
7135                 width = Math.max(width, this.options.minWidth);
7137                 style.width = (width + 1) + 'px';
7138                 style.whiteSpace = '';
7140                 style.height = '';
7142                 var height = container.offsetHeight,
7143                     maxHeight = this.options.maxHeight,
7144                     scrolledClass = 'leaflet-popup-scrolled';
7146                 if (maxHeight && height > maxHeight) {
7147                         style.height = maxHeight + 'px';
7148                         L.DomUtil.addClass(container, scrolledClass);
7149                 } else {
7150                         L.DomUtil.removeClass(container, scrolledClass);
7151                 }
7153                 this._containerWidth = this._container.offsetWidth;
7154         },
7156         _animateZoom: function (e) {
7157                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
7158                     anchor = this._getAnchor();
7159                 L.DomUtil.setPosition(this._container, pos.add(anchor));
7160         },
7162         _adjustPan: function () {
7163                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
7165                 var map = this._map,
7166                     marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
7167                     containerHeight = this._container.offsetHeight + marginBottom,
7168                     containerWidth = this._containerWidth,
7169                     layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
7171                 layerPos._add(L.DomUtil.getPosition(this._container));
7173                 var containerPos = map.layerPointToContainerPoint(layerPos),
7174                     padding = L.point(this.options.autoPanPadding),
7175                     paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
7176                     paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
7177                     size = map.getSize(),
7178                     dx = 0,
7179                     dy = 0;
7181                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
7182                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
7183                 }
7184                 if (containerPos.x - dx - paddingTL.x < 0) { // left
7185                         dx = containerPos.x - paddingTL.x;
7186                 }
7187                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
7188                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
7189                 }
7190                 if (containerPos.y - dy - paddingTL.y < 0) { // top
7191                         dy = containerPos.y - paddingTL.y;
7192                 }
7194                 // @namespace Map
7195                 // @section Popup events
7196                 // @event autopanstart: Event
7197                 // Fired when the map starts autopanning when opening a popup.
7198                 if (dx || dy) {
7199                         map
7200                             .fire('autopanstart')
7201                             .panBy([dx, dy]);
7202                 }
7203         },
7205         _onCloseButtonClick: function (e) {
7206                 this._close();
7207                 L.DomEvent.stop(e);
7208         },
7210         _getAnchor: function () {
7211                 // Where should we anchor the popup on the source layer?
7212                 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
7213         }
7217 // @namespace Popup
7218 // @factory L.popup(options?: Popup options, source?: Layer)
7219 // 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.
7220 L.popup = function (options, source) {
7221         return new L.Popup(options, source);
7225 /* @namespace Map
7226  * @section Interaction Options
7227  * @option closePopupOnClick: Boolean = true
7228  * Set it to `false` if you don't want popups to close when user clicks the map.
7229  */
7230 L.Map.mergeOptions({
7231         closePopupOnClick: true
7235 // @namespace Map
7236 // @section Methods for Layers and Controls
7237 L.Map.include({
7238         // @method openPopup(popup: Popup): this
7239         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
7240         // @alternative
7241         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
7242         // Creates a popup with the specified content and options and opens it in the given point on a map.
7243         openPopup: function (popup, latlng, options) {
7244                 if (!(popup instanceof L.Popup)) {
7245                         popup = new L.Popup(options).setContent(popup);
7246                 }
7248                 if (latlng) {
7249                         popup.setLatLng(latlng);
7250                 }
7252                 if (this.hasLayer(popup)) {
7253                         return this;
7254                 }
7256                 if (this._popup && this._popup.options.autoClose) {
7257                         this.closePopup();
7258                 }
7260                 this._popup = popup;
7261                 return this.addLayer(popup);
7262         },
7264         // @method closePopup(popup?: Popup): this
7265         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
7266         closePopup: function (popup) {
7267                 if (!popup || popup === this._popup) {
7268                         popup = this._popup;
7269                         this._popup = null;
7270                 }
7271                 if (popup) {
7272                         this.removeLayer(popup);
7273                 }
7274                 return this;
7275         }
7279  * @namespace Layer
7280  * @section Popup methods example
7282  * All layers share a set of methods convenient for binding popups to it.
7284  * ```js
7285  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
7286  * layer.openPopup();
7287  * layer.closePopup();
7288  * ```
7290  * 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.
7291  */
7293 // @section Popup methods
7294 L.Layer.include({
7296         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
7297         // Binds a popup to the layer with the passed `content` and sets up the
7298         // neccessary event listeners. If a `Function` is passed it will receive
7299         // the layer as the first argument and should return a `String` or `HTMLElement`.
7300         bindPopup: function (content, options) {
7302                 if (content instanceof L.Popup) {
7303                         L.setOptions(content, options);
7304                         this._popup = content;
7305                         content._source = this;
7306                 } else {
7307                         if (!this._popup || options) {
7308                                 this._popup = new L.Popup(options, this);
7309                         }
7310                         this._popup.setContent(content);
7311                 }
7313                 if (!this._popupHandlersAdded) {
7314                         this.on({
7315                                 click: this._openPopup,
7316                                 remove: this.closePopup,
7317                                 move: this._movePopup
7318                         });
7319                         this._popupHandlersAdded = true;
7320                 }
7322                 return this;
7323         },
7325         // @method unbindPopup(): this
7326         // Removes the popup previously bound with `bindPopup`.
7327         unbindPopup: function () {
7328                 if (this._popup) {
7329                         this.off({
7330                                 click: this._openPopup,
7331                                 remove: this.closePopup,
7332                                 move: this._movePopup
7333                         });
7334                         this._popupHandlersAdded = false;
7335                         this._popup = null;
7336                 }
7337                 return this;
7338         },
7340         // @method openPopup(latlng?: LatLng): this
7341         // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
7342         openPopup: function (layer, latlng) {
7343                 if (!(layer instanceof L.Layer)) {
7344                         latlng = layer;
7345                         layer = this;
7346                 }
7348                 if (layer instanceof L.FeatureGroup) {
7349                         for (var id in this._layers) {
7350                                 layer = this._layers[id];
7351                                 break;
7352                         }
7353                 }
7355                 if (!latlng) {
7356                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7357                 }
7359                 if (this._popup && this._map) {
7360                         // set popup source to this layer
7361                         this._popup._source = layer;
7363                         // update the popup (content, layout, ect...)
7364                         this._popup.update();
7366                         // open the popup on the map
7367                         this._map.openPopup(this._popup, latlng);
7368                 }
7370                 return this;
7371         },
7373         // @method closePopup(): this
7374         // Closes the popup bound to this layer if it is open.
7375         closePopup: function () {
7376                 if (this._popup) {
7377                         this._popup._close();
7378                 }
7379                 return this;
7380         },
7382         // @method togglePopup(): this
7383         // Opens or closes the popup bound to this layer depending on its current state.
7384         togglePopup: function (target) {
7385                 if (this._popup) {
7386                         if (this._popup._map) {
7387                                 this.closePopup();
7388                         } else {
7389                                 this.openPopup(target);
7390                         }
7391                 }
7392                 return this;
7393         },
7395         // @method isPopupOpen(): boolean
7396         // Returns `true` if the popup bound to this layer is currently open.
7397         isPopupOpen: function () {
7398                 return (this._popup ? this._popup.isOpen() : false);
7399         },
7401         // @method setPopupContent(content: String|HTMLElement|Popup): this
7402         // Sets the content of the popup bound to this layer.
7403         setPopupContent: function (content) {
7404                 if (this._popup) {
7405                         this._popup.setContent(content);
7406                 }
7407                 return this;
7408         },
7410         // @method getPopup(): Popup
7411         // Returns the popup bound to this layer.
7412         getPopup: function () {
7413                 return this._popup;
7414         },
7416         _openPopup: function (e) {
7417                 var layer = e.layer || e.target;
7419                 if (!this._popup) {
7420                         return;
7421                 }
7423                 if (!this._map) {
7424                         return;
7425                 }
7427                 // prevent map click
7428                 L.DomEvent.stop(e);
7430                 // if this inherits from Path its a vector and we can just
7431                 // open the popup at the new location
7432                 if (layer instanceof L.Path) {
7433                         this.openPopup(e.layer || e.target, e.latlng);
7434                         return;
7435                 }
7437                 // otherwise treat it like a marker and figure out
7438                 // if we should toggle it open/closed
7439                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
7440                         this.closePopup();
7441                 } else {
7442                         this.openPopup(layer, e.latlng);
7443                 }
7444         },
7446         _movePopup: function (e) {
7447                 this._popup.setLatLng(e.latlng);
7448         }
7454  * @class Tooltip
7455  * @inherits DivOverlay
7456  * @aka L.Tooltip
7457  * Used to display small texts on top of map layers.
7459  * @example
7461  * ```js
7462  * marker.bindTooltip("my tooltip text").openTooltip();
7463  * ```
7464  * Note about tooltip offset. Leaflet takes two options in consideration
7465  * for computing tooltip offseting:
7466  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
7467  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
7468  *   move it to the bottom. Negatives will move to the left and top.
7469  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
7470  *   should adapt this value if you use a custom icon.
7471  */
7474 // @namespace Tooltip
7475 L.Tooltip = L.DivOverlay.extend({
7477         // @section
7478         // @aka Tooltip options
7479         options: {
7480                 // @option pane: String = 'tooltipPane'
7481                 // `Map pane` where the tooltip will be added.
7482                 pane: 'tooltipPane',
7484                 // @option offset: Point = Point(0, 0)
7485                 // Optional offset of the tooltip position.
7486                 offset: [0, 0],
7488                 // @option direction: String = 'auto'
7489                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
7490                 // `top`, `bottom`, `center`, `auto`.
7491                 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
7492                 // position on the map.
7493                 direction: 'auto',
7495                 // @option permanent: Boolean = false
7496                 // Whether to open the tooltip permanently or only on mouseover.
7497                 permanent: false,
7499                 // @option sticky: Boolean = false
7500                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
7501                 sticky: false,
7503                 // @option interactive: Boolean = false
7504                 // If true, the tooltip will listen to the feature events.
7505                 interactive: false,
7507                 // @option opacity: Number = 0.9
7508                 // Tooltip container opacity.
7509                 opacity: 0.9
7510         },
7512         onAdd: function (map) {
7513                 L.DivOverlay.prototype.onAdd.call(this, map);
7514                 this.setOpacity(this.options.opacity);
7516                 // @namespace Map
7517                 // @section Tooltip events
7518                 // @event tooltipopen: TooltipEvent
7519                 // Fired when a tooltip is opened in the map.
7520                 map.fire('tooltipopen', {tooltip: this});
7522                 if (this._source) {
7523                         // @namespace Layer
7524                         // @section Tooltip events
7525                         // @event tooltipopen: TooltipEvent
7526                         // Fired when a tooltip bound to this layer is opened.
7527                         this._source.fire('tooltipopen', {tooltip: this}, true);
7528                 }
7529         },
7531         onRemove: function (map) {
7532                 L.DivOverlay.prototype.onRemove.call(this, map);
7534                 // @namespace Map
7535                 // @section Tooltip events
7536                 // @event tooltipclose: TooltipEvent
7537                 // Fired when a tooltip in the map is closed.
7538                 map.fire('tooltipclose', {tooltip: this});
7540                 if (this._source) {
7541                         // @namespace Layer
7542                         // @section Tooltip events
7543                         // @event tooltipclose: TooltipEvent
7544                         // Fired when a tooltip bound to this layer is closed.
7545                         this._source.fire('tooltipclose', {tooltip: this}, true);
7546                 }
7547         },
7549         getEvents: function () {
7550                 var events = L.DivOverlay.prototype.getEvents.call(this);
7552                 if (L.Browser.touch && !this.options.permanent) {
7553                         events.preclick = this._close;
7554                 }
7556                 return events;
7557         },
7559         _close: function () {
7560                 if (this._map) {
7561                         this._map.closeTooltip(this);
7562                 }
7563         },
7565         _initLayout: function () {
7566                 var prefix = 'leaflet-tooltip',
7567                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7569                 this._contentNode = this._container = L.DomUtil.create('div', className);
7570         },
7572         _updateLayout: function () {},
7574         _adjustPan: function () {},
7576         _setPosition: function (pos) {
7577                 var map = this._map,
7578                     container = this._container,
7579                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
7580                     tooltipPoint = map.layerPointToContainerPoint(pos),
7581                     direction = this.options.direction,
7582                     tooltipWidth = container.offsetWidth,
7583                     tooltipHeight = container.offsetHeight,
7584                     offset = L.point(this.options.offset),
7585                     anchor = this._getAnchor();
7587                 if (direction === 'top') {
7588                         pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
7589                 } else if (direction === 'bottom') {
7590                         pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true));
7591                 } else if (direction === 'center') {
7592                         pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
7593                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
7594                         direction = 'right';
7595                         pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
7596                 } else {
7597                         direction = 'left';
7598                         pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
7599                 }
7601                 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
7602                 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
7603                 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
7604                 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
7605                 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
7606                 L.DomUtil.setPosition(container, pos);
7607         },
7609         _updatePosition: function () {
7610                 var pos = this._map.latLngToLayerPoint(this._latlng);
7611                 this._setPosition(pos);
7612         },
7614         setOpacity: function (opacity) {
7615                 this.options.opacity = opacity;
7617                 if (this._container) {
7618                         L.DomUtil.setOpacity(this._container, opacity);
7619                 }
7620         },
7622         _animateZoom: function (e) {
7623                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
7624                 this._setPosition(pos);
7625         },
7627         _getAnchor: function () {
7628                 // Where should we anchor the tooltip on the source layer?
7629                 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
7630         }
7634 // @namespace Tooltip
7635 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
7636 // 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.
7637 L.tooltip = function (options, source) {
7638         return new L.Tooltip(options, source);
7641 // @namespace Map
7642 // @section Methods for Layers and Controls
7643 L.Map.include({
7645         // @method openTooltip(tooltip: Tooltip): this
7646         // Opens the specified tooltip.
7647         // @alternative
7648         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
7649         // Creates a tooltip with the specified content and options and open it.
7650         openTooltip: function (tooltip, latlng, options) {
7651                 if (!(tooltip instanceof L.Tooltip)) {
7652                         tooltip = new L.Tooltip(options).setContent(tooltip);
7653                 }
7655                 if (latlng) {
7656                         tooltip.setLatLng(latlng);
7657                 }
7659                 if (this.hasLayer(tooltip)) {
7660                         return this;
7661                 }
7663                 return this.addLayer(tooltip);
7664         },
7666         // @method closeTooltip(tooltip?: Tooltip): this
7667         // Closes the tooltip given as parameter.
7668         closeTooltip: function (tooltip) {
7669                 if (tooltip) {
7670                         this.removeLayer(tooltip);
7671                 }
7672                 return this;
7673         }
7678  * @namespace Layer
7679  * @section Tooltip methods example
7681  * All layers share a set of methods convenient for binding tooltips to it.
7683  * ```js
7684  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
7685  * layer.openTooltip();
7686  * layer.closeTooltip();
7687  * ```
7688  */
7690 // @section Tooltip methods
7691 L.Layer.include({
7693         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
7694         // Binds a tooltip to the layer with the passed `content` and sets up the
7695         // neccessary event listeners. If a `Function` is passed it will receive
7696         // the layer as the first argument and should return a `String` or `HTMLElement`.
7697         bindTooltip: function (content, options) {
7699                 if (content instanceof L.Tooltip) {
7700                         L.setOptions(content, options);
7701                         this._tooltip = content;
7702                         content._source = this;
7703                 } else {
7704                         if (!this._tooltip || options) {
7705                                 this._tooltip = L.tooltip(options, this);
7706                         }
7707                         this._tooltip.setContent(content);
7709                 }
7711                 this._initTooltipInteractions();
7713                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
7714                         this.openTooltip();
7715                 }
7717                 return this;
7718         },
7720         // @method unbindTooltip(): this
7721         // Removes the tooltip previously bound with `bindTooltip`.
7722         unbindTooltip: function () {
7723                 if (this._tooltip) {
7724                         this._initTooltipInteractions(true);
7725                         this.closeTooltip();
7726                         this._tooltip = null;
7727                 }
7728                 return this;
7729         },
7731         _initTooltipInteractions: function (remove) {
7732                 if (!remove && this._tooltipHandlersAdded) { return; }
7733                 var onOff = remove ? 'off' : 'on',
7734                     events = {
7735                         remove: this.closeTooltip,
7736                         move: this._moveTooltip
7737                     };
7738                 if (!this._tooltip.options.permanent) {
7739                         events.mouseover = this._openTooltip;
7740                         events.mouseout = this.closeTooltip;
7741                         if (this._tooltip.options.sticky) {
7742                                 events.mousemove = this._moveTooltip;
7743                         }
7744                         if (L.Browser.touch) {
7745                                 events.click = this._openTooltip;
7746                         }
7747                 } else {
7748                         events.add = this._openTooltip;
7749                 }
7750                 this[onOff](events);
7751                 this._tooltipHandlersAdded = !remove;
7752         },
7754         // @method openTooltip(latlng?: LatLng): this
7755         // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
7756         openTooltip: function (layer, latlng) {
7757                 if (!(layer instanceof L.Layer)) {
7758                         latlng = layer;
7759                         layer = this;
7760                 }
7762                 if (layer instanceof L.FeatureGroup) {
7763                         for (var id in this._layers) {
7764                                 layer = this._layers[id];
7765                                 break;
7766                         }
7767                 }
7769                 if (!latlng) {
7770                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7771                 }
7773                 if (this._tooltip && this._map) {
7775                         // set tooltip source to this layer
7776                         this._tooltip._source = layer;
7778                         // update the tooltip (content, layout, ect...)
7779                         this._tooltip.update();
7781                         // open the tooltip on the map
7782                         this._map.openTooltip(this._tooltip, latlng);
7784                         // Tooltip container may not be defined if not permanent and never
7785                         // opened.
7786                         if (this._tooltip.options.interactive && this._tooltip._container) {
7787                                 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
7788                                 this.addInteractiveTarget(this._tooltip._container);
7789                         }
7790                 }
7792                 return this;
7793         },
7795         // @method closeTooltip(): this
7796         // Closes the tooltip bound to this layer if it is open.
7797         closeTooltip: function () {
7798                 if (this._tooltip) {
7799                         this._tooltip._close();
7800                         if (this._tooltip.options.interactive && this._tooltip._container) {
7801                                 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
7802                                 this.removeInteractiveTarget(this._tooltip._container);
7803                         }
7804                 }
7805                 return this;
7806         },
7808         // @method toggleTooltip(): this
7809         // Opens or closes the tooltip bound to this layer depending on its current state.
7810         toggleTooltip: function (target) {
7811                 if (this._tooltip) {
7812                         if (this._tooltip._map) {
7813                                 this.closeTooltip();
7814                         } else {
7815                                 this.openTooltip(target);
7816                         }
7817                 }
7818                 return this;
7819         },
7821         // @method isTooltipOpen(): boolean
7822         // Returns `true` if the tooltip bound to this layer is currently open.
7823         isTooltipOpen: function () {
7824                 return this._tooltip.isOpen();
7825         },
7827         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
7828         // Sets the content of the tooltip bound to this layer.
7829         setTooltipContent: function (content) {
7830                 if (this._tooltip) {
7831                         this._tooltip.setContent(content);
7832                 }
7833                 return this;
7834         },
7836         // @method getTooltip(): Tooltip
7837         // Returns the tooltip bound to this layer.
7838         getTooltip: function () {
7839                 return this._tooltip;
7840         },
7842         _openTooltip: function (e) {
7843                 var layer = e.layer || e.target;
7845                 if (!this._tooltip || !this._map) {
7846                         return;
7847                 }
7848                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
7849         },
7851         _moveTooltip: function (e) {
7852                 var latlng = e.latlng, containerPoint, layerPoint;
7853                 if (this._tooltip.options.sticky && e.originalEvent) {
7854                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
7855                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
7856                         latlng = this._map.layerPointToLatLng(layerPoint);
7857                 }
7858                 this._tooltip.setLatLng(latlng);
7859         }
7865  * @class LayerGroup
7866  * @aka L.LayerGroup
7867  * @inherits Layer
7869  * Used to group several layers and handle them as one. If you add it to the map,
7870  * any layers added or removed from the group will be added/removed on the map as
7871  * well. Extends `Layer`.
7873  * @example
7875  * ```js
7876  * L.layerGroup([marker1, marker2])
7877  *      .addLayer(polyline)
7878  *      .addTo(map);
7879  * ```
7880  */
7882 L.LayerGroup = L.Layer.extend({
7884         initialize: function (layers) {
7885                 this._layers = {};
7887                 var i, len;
7889                 if (layers) {
7890                         for (i = 0, len = layers.length; i < len; i++) {
7891                                 this.addLayer(layers[i]);
7892                         }
7893                 }
7894         },
7896         // @method addLayer(layer: Layer): this
7897         // Adds the given layer to the group.
7898         addLayer: function (layer) {
7899                 var id = this.getLayerId(layer);
7901                 this._layers[id] = layer;
7903                 if (this._map) {
7904                         this._map.addLayer(layer);
7905                 }
7907                 return this;
7908         },
7910         // @method removeLayer(layer: Layer): this
7911         // Removes the given layer from the group.
7912         // @alternative
7913         // @method removeLayer(id: Number): this
7914         // Removes the layer with the given internal ID from the group.
7915         removeLayer: function (layer) {
7916                 var id = layer in this._layers ? layer : this.getLayerId(layer);
7918                 if (this._map && this._layers[id]) {
7919                         this._map.removeLayer(this._layers[id]);
7920                 }
7922                 delete this._layers[id];
7924                 return this;
7925         },
7927         // @method hasLayer(layer: Layer): Boolean
7928         // Returns `true` if the given layer is currently added to the group.
7929         hasLayer: function (layer) {
7930                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7931         },
7933         // @method clearLayers(): this
7934         // Removes all the layers from the group.
7935         clearLayers: function () {
7936                 for (var i in this._layers) {
7937                         this.removeLayer(this._layers[i]);
7938                 }
7939                 return this;
7940         },
7942         // @method invoke(methodName: String, …): this
7943         // Calls `methodName` on every layer contained in this group, passing any
7944         // additional parameters. Has no effect if the layers contained do not
7945         // implement `methodName`.
7946         invoke: function (methodName) {
7947                 var args = Array.prototype.slice.call(arguments, 1),
7948                     i, layer;
7950                 for (i in this._layers) {
7951                         layer = this._layers[i];
7953                         if (layer[methodName]) {
7954                                 layer[methodName].apply(layer, args);
7955                         }
7956                 }
7958                 return this;
7959         },
7961         onAdd: function (map) {
7962                 for (var i in this._layers) {
7963                         map.addLayer(this._layers[i]);
7964                 }
7965         },
7967         onRemove: function (map) {
7968                 for (var i in this._layers) {
7969                         map.removeLayer(this._layers[i]);
7970                 }
7971         },
7973         // @method eachLayer(fn: Function, context?: Object): this
7974         // Iterates over the layers of the group, optionally specifying context of the iterator function.
7975         // ```js
7976         // group.eachLayer(function (layer) {
7977         //      layer.bindPopup('Hello');
7978         // });
7979         // ```
7980         eachLayer: function (method, context) {
7981                 for (var i in this._layers) {
7982                         method.call(context, this._layers[i]);
7983                 }
7984                 return this;
7985         },
7987         // @method getLayer(id: Number): Layer
7988         // Returns the layer with the given internal ID.
7989         getLayer: function (id) {
7990                 return this._layers[id];
7991         },
7993         // @method getLayers(): Layer[]
7994         // Returns an array of all the layers added to the group.
7995         getLayers: function () {
7996                 var layers = [];
7998                 for (var i in this._layers) {
7999                         layers.push(this._layers[i]);
8000                 }
8001                 return layers;
8002         },
8004         // @method setZIndex(zIndex: Number): this
8005         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
8006         setZIndex: function (zIndex) {
8007                 return this.invoke('setZIndex', zIndex);
8008         },
8010         // @method getLayerId(layer: Layer): Number
8011         // Returns the internal ID for a layer
8012         getLayerId: function (layer) {
8013                 return L.stamp(layer);
8014         }
8018 // @factory L.layerGroup(layers: Layer[])
8019 // Create a layer group, optionally given an initial set of layers.
8020 L.layerGroup = function (layers) {
8021         return new L.LayerGroup(layers);
8027  * @class FeatureGroup
8028  * @aka L.FeatureGroup
8029  * @inherits LayerGroup
8031  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
8032  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
8033  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
8034  * handler, it will handle events from any of the layers. This includes mouse events
8035  * and custom events.
8036  *  * Has `layeradd` and `layerremove` events
8038  * @example
8040  * ```js
8041  * L.featureGroup([marker1, marker2, polyline])
8042  *      .bindPopup('Hello world!')
8043  *      .on('click', function() { alert('Clicked on a member of the group!'); })
8044  *      .addTo(map);
8045  * ```
8046  */
8048 L.FeatureGroup = L.LayerGroup.extend({
8050         addLayer: function (layer) {
8051                 if (this.hasLayer(layer)) {
8052                         return this;
8053                 }
8055                 layer.addEventParent(this);
8057                 L.LayerGroup.prototype.addLayer.call(this, layer);
8059                 // @event layeradd: LayerEvent
8060                 // Fired when a layer is added to this `FeatureGroup`
8061                 return this.fire('layeradd', {layer: layer});
8062         },
8064         removeLayer: function (layer) {
8065                 if (!this.hasLayer(layer)) {
8066                         return this;
8067                 }
8068                 if (layer in this._layers) {
8069                         layer = this._layers[layer];
8070                 }
8072                 layer.removeEventParent(this);
8074                 L.LayerGroup.prototype.removeLayer.call(this, layer);
8076                 // @event layerremove: LayerEvent
8077                 // Fired when a layer is removed from this `FeatureGroup`
8078                 return this.fire('layerremove', {layer: layer});
8079         },
8081         // @method setStyle(style: Path options): this
8082         // Sets the given path options to each layer of the group that has a `setStyle` method.
8083         setStyle: function (style) {
8084                 return this.invoke('setStyle', style);
8085         },
8087         // @method bringToFront(): this
8088         // Brings the layer group to the top of all other layers
8089         bringToFront: function () {
8090                 return this.invoke('bringToFront');
8091         },
8093         // @method bringToBack(): this
8094         // Brings the layer group to the top of all other layers
8095         bringToBack: function () {
8096                 return this.invoke('bringToBack');
8097         },
8099         // @method getBounds(): LatLngBounds
8100         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
8101         getBounds: function () {
8102                 var bounds = new L.LatLngBounds();
8104                 for (var id in this._layers) {
8105                         var layer = this._layers[id];
8106                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
8107                 }
8108                 return bounds;
8109         }
8112 // @factory L.featureGroup(layers: Layer[])
8113 // Create a feature group, optionally given an initial set of layers.
8114 L.featureGroup = function (layers) {
8115         return new L.FeatureGroup(layers);
8121  * @class Renderer
8122  * @inherits Layer
8123  * @aka L.Renderer
8125  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
8126  * DOM container of the renderer, its bounds, and its zoom animation.
8128  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
8129  * itself can be added or removed to the map. All paths use a renderer, which can
8130  * be implicit (the map will decide the type of renderer and use it automatically)
8131  * or explicit (using the [`renderer`](#path-renderer) option of the path).
8133  * Do not use this class directly, use `SVG` and `Canvas` instead.
8135  * @event update: Event
8136  * Fired when the renderer updates its bounds, center and zoom, for example when
8137  * its map has moved
8138  */
8140 L.Renderer = L.Layer.extend({
8142         // @section
8143         // @aka Renderer options
8144         options: {
8145                 // @option padding: Number = 0.1
8146                 // How much to extend the clip area around the map view (relative to its size)
8147                 // e.g. 0.1 would be 10% of map view in each direction
8148                 padding: 0.1
8149         },
8151         initialize: function (options) {
8152                 L.setOptions(this, options);
8153                 L.stamp(this);
8154                 this._layers = this._layers || {};
8155         },
8157         onAdd: function () {
8158                 if (!this._container) {
8159                         this._initContainer(); // defined by renderer implementations
8161                         if (this._zoomAnimated) {
8162                                 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
8163                         }
8164                 }
8166                 this.getPane().appendChild(this._container);
8167                 this._update();
8168                 this.on('update', this._updatePaths, this);
8169         },
8171         onRemove: function () {
8172                 L.DomUtil.remove(this._container);
8173                 this.off('update', this._updatePaths, this);
8174         },
8176         getEvents: function () {
8177                 var events = {
8178                         viewreset: this._reset,
8179                         zoom: this._onZoom,
8180                         moveend: this._update,
8181                         zoomend: this._onZoomEnd
8182                 };
8183                 if (this._zoomAnimated) {
8184                         events.zoomanim = this._onAnimZoom;
8185                 }
8186                 return events;
8187         },
8189         _onAnimZoom: function (ev) {
8190                 this._updateTransform(ev.center, ev.zoom);
8191         },
8193         _onZoom: function () {
8194                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
8195         },
8197         _updateTransform: function (center, zoom) {
8198                 var scale = this._map.getZoomScale(zoom, this._zoom),
8199                     position = L.DomUtil.getPosition(this._container),
8200                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
8201                     currentCenterPoint = this._map.project(this._center, zoom),
8202                     destCenterPoint = this._map.project(center, zoom),
8203                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
8205                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
8207                 if (L.Browser.any3d) {
8208                         L.DomUtil.setTransform(this._container, topLeftOffset, scale);
8209                 } else {
8210                         L.DomUtil.setPosition(this._container, topLeftOffset);
8211                 }
8212         },
8214         _reset: function () {
8215                 this._update();
8216                 this._updateTransform(this._center, this._zoom);
8218                 for (var id in this._layers) {
8219                         this._layers[id]._reset();
8220                 }
8221         },
8223         _onZoomEnd: function () {
8224                 for (var id in this._layers) {
8225                         this._layers[id]._project();
8226                 }
8227         },
8229         _updatePaths: function () {
8230                 for (var id in this._layers) {
8231                         this._layers[id]._update();
8232                 }
8233         },
8235         _update: function () {
8236                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
8237                 // Subclasses are responsible of firing the 'update' event.
8238                 var p = this.options.padding,
8239                     size = this._map.getSize(),
8240                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
8242                 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
8244                 this._center = this._map.getCenter();
8245                 this._zoom = this._map.getZoom();
8246         }
8250 L.Map.include({
8251         // @namespace Map; @method getRenderer(layer: Path): Renderer
8252         // Returns the instance of `Renderer` that should be used to render the given
8253         // `Path`. It will ensure that the `renderer` options of the map and paths
8254         // are respected, and that the renderers do exist on the map.
8255         getRenderer: function (layer) {
8256                 // @namespace Path; @option renderer: Renderer
8257                 // Use this specific instance of `Renderer` for this path. Takes
8258                 // precedence over the map's [default renderer](#map-renderer).
8259                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
8261                 if (!renderer) {
8262                         // @namespace Map; @option preferCanvas: Boolean = false
8263                         // Whether `Path`s should be rendered on a `Canvas` renderer.
8264                         // By default, all `Path`s are rendered in a `SVG` renderer.
8265                         renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
8266                 }
8268                 if (!this.hasLayer(renderer)) {
8269                         this.addLayer(renderer);
8270                 }
8271                 return renderer;
8272         },
8274         _getPaneRenderer: function (name) {
8275                 if (name === 'overlayPane' || name === undefined) {
8276                         return false;
8277                 }
8279                 var renderer = this._paneRenderers[name];
8280                 if (renderer === undefined) {
8281                         renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
8282                         this._paneRenderers[name] = renderer;
8283                 }
8284                 return renderer;
8285         }
8291  * @class Path
8292  * @aka L.Path
8293  * @inherits Interactive layer
8295  * An abstract class that contains options and constants shared between vector
8296  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8297  */
8299 L.Path = L.Layer.extend({
8301         // @section
8302         // @aka Path options
8303         options: {
8304                 // @option stroke: Boolean = true
8305                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8306                 stroke: true,
8308                 // @option color: String = '#3388ff'
8309                 // Stroke color
8310                 color: '#3388ff',
8312                 // @option weight: Number = 3
8313                 // Stroke width in pixels
8314                 weight: 3,
8316                 // @option opacity: Number = 1.0
8317                 // Stroke opacity
8318                 opacity: 1,
8320                 // @option lineCap: String= 'round'
8321                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8322                 lineCap: 'round',
8324                 // @option lineJoin: String = 'round'
8325                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8326                 lineJoin: 'round',
8328                 // @option dashArray: String = null
8329                 // 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).
8330                 dashArray: null,
8332                 // @option dashOffset: String = null
8333                 // 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).
8334                 dashOffset: null,
8336                 // @option fill: Boolean = depends
8337                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8338                 fill: false,
8340                 // @option fillColor: String = *
8341                 // Fill color. Defaults to the value of the [`color`](#path-color) option
8342                 fillColor: null,
8344                 // @option fillOpacity: Number = 0.2
8345                 // Fill opacity.
8346                 fillOpacity: 0.2,
8348                 // @option fillRule: String = 'evenodd'
8349                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8350                 fillRule: 'evenodd',
8352                 // className: '',
8354                 // Option inherited from "Interactive layer" abstract class
8355                 interactive: true
8356         },
8358         beforeAdd: function (map) {
8359                 // Renderer is set here because we need to call renderer.getEvents
8360                 // before this.getEvents.
8361                 this._renderer = map.getRenderer(this);
8362         },
8364         onAdd: function () {
8365                 this._renderer._initPath(this);
8366                 this._reset();
8367                 this._renderer._addPath(this);
8368         },
8370         onRemove: function () {
8371                 this._renderer._removePath(this);
8372         },
8374         // @method redraw(): this
8375         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8376         redraw: function () {
8377                 if (this._map) {
8378                         this._renderer._updatePath(this);
8379                 }
8380                 return this;
8381         },
8383         // @method setStyle(style: Path options): this
8384         // Changes the appearance of a Path based on the options in the `Path options` object.
8385         setStyle: function (style) {
8386                 L.setOptions(this, style);
8387                 if (this._renderer) {
8388                         this._renderer._updateStyle(this);
8389                 }
8390                 return this;
8391         },
8393         // @method bringToFront(): this
8394         // Brings the layer to the top of all path layers.
8395         bringToFront: function () {
8396                 if (this._renderer) {
8397                         this._renderer._bringToFront(this);
8398                 }
8399                 return this;
8400         },
8402         // @method bringToBack(): this
8403         // Brings the layer to the bottom of all path layers.
8404         bringToBack: function () {
8405                 if (this._renderer) {
8406                         this._renderer._bringToBack(this);
8407                 }
8408                 return this;
8409         },
8411         getElement: function () {
8412                 return this._path;
8413         },
8415         _reset: function () {
8416                 // defined in children classes
8417                 this._project();
8418                 this._update();
8419         },
8421         _clickTolerance: function () {
8422                 // used when doing hit detection for Canvas layers
8423                 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
8424         }
8430  * @namespace LineUtil
8432  * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
8433  */
8435 L.LineUtil = {
8437         // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
8438         // Improves rendering performance dramatically by lessening the number of points to draw.
8440         // @function simplify(points: Point[], tolerance: Number): Point[]
8441         // Dramatically reduces the number of points in a polyline while retaining
8442         // its shape and returns a new array of simplified points, using the
8443         // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
8444         // Used for a huge performance boost when processing/displaying Leaflet polylines for
8445         // each zoom level and also reducing visual noise. tolerance affects the amount of
8446         // simplification (lesser value means higher quality but slower and with more points).
8447         // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
8448         simplify: function (points, tolerance) {
8449                 if (!tolerance || !points.length) {
8450                         return points.slice();
8451                 }
8453                 var sqTolerance = tolerance * tolerance;
8455                 // stage 1: vertex reduction
8456                 points = this._reducePoints(points, sqTolerance);
8458                 // stage 2: Douglas-Peucker simplification
8459                 points = this._simplifyDP(points, sqTolerance);
8461                 return points;
8462         },
8464         // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
8465         // Returns the distance between point `p` and segment `p1` to `p2`.
8466         pointToSegmentDistance:  function (p, p1, p2) {
8467                 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
8468         },
8470         // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
8471         // Returns the closest point from a point `p` on a segment `p1` to `p2`.
8472         closestPointOnSegment: function (p, p1, p2) {
8473                 return this._sqClosestPointOnSegment(p, p1, p2);
8474         },
8476         // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
8477         _simplifyDP: function (points, sqTolerance) {
8479                 var len = points.length,
8480                     ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
8481                     markers = new ArrayConstructor(len);
8483                 markers[0] = markers[len - 1] = 1;
8485                 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
8487                 var i,
8488                     newPoints = [];
8490                 for (i = 0; i < len; i++) {
8491                         if (markers[i]) {
8492                                 newPoints.push(points[i]);
8493                         }
8494                 }
8496                 return newPoints;
8497         },
8499         _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
8501                 var maxSqDist = 0,
8502                     index, i, sqDist;
8504                 for (i = first + 1; i <= last - 1; i++) {
8505                         sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
8507                         if (sqDist > maxSqDist) {
8508                                 index = i;
8509                                 maxSqDist = sqDist;
8510                         }
8511                 }
8513                 if (maxSqDist > sqTolerance) {
8514                         markers[index] = 1;
8516                         this._simplifyDPStep(points, markers, sqTolerance, first, index);
8517                         this._simplifyDPStep(points, markers, sqTolerance, index, last);
8518                 }
8519         },
8521         // reduce points that are too close to each other to a single point
8522         _reducePoints: function (points, sqTolerance) {
8523                 var reducedPoints = [points[0]];
8525                 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
8526                         if (this._sqDist(points[i], points[prev]) > sqTolerance) {
8527                                 reducedPoints.push(points[i]);
8528                                 prev = i;
8529                         }
8530                 }
8531                 if (prev < len - 1) {
8532                         reducedPoints.push(points[len - 1]);
8533                 }
8534                 return reducedPoints;
8535         },
8538         // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
8539         // Clips the segment a to b by rectangular bounds with the
8540         // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
8541         // (modifying the segment points directly!). Used by Leaflet to only show polyline
8542         // points that are on the screen or near, increasing performance.
8543         clipSegment: function (a, b, bounds, useLastCode, round) {
8544                 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
8545                     codeB = this._getBitCode(b, bounds),
8547                     codeOut, p, newCode;
8549                 // save 2nd code to avoid calculating it on the next segment
8550                 this._lastCode = codeB;
8552                 while (true) {
8553                         // if a,b is inside the clip window (trivial accept)
8554                         if (!(codeA | codeB)) {
8555                                 return [a, b];
8556                         }
8558                         // if a,b is outside the clip window (trivial reject)
8559                         if (codeA & codeB) {
8560                                 return false;
8561                         }
8563                         // other cases
8564                         codeOut = codeA || codeB;
8565                         p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
8566                         newCode = this._getBitCode(p, bounds);
8568                         if (codeOut === codeA) {
8569                                 a = p;
8570                                 codeA = newCode;
8571                         } else {
8572                                 b = p;
8573                                 codeB = newCode;
8574                         }
8575                 }
8576         },
8578         _getEdgeIntersection: function (a, b, code, bounds, round) {
8579                 var dx = b.x - a.x,
8580                     dy = b.y - a.y,
8581                     min = bounds.min,
8582                     max = bounds.max,
8583                     x, y;
8585                 if (code & 8) { // top
8586                         x = a.x + dx * (max.y - a.y) / dy;
8587                         y = max.y;
8589                 } else if (code & 4) { // bottom
8590                         x = a.x + dx * (min.y - a.y) / dy;
8591                         y = min.y;
8593                 } else if (code & 2) { // right
8594                         x = max.x;
8595                         y = a.y + dy * (max.x - a.x) / dx;
8597                 } else if (code & 1) { // left
8598                         x = min.x;
8599                         y = a.y + dy * (min.x - a.x) / dx;
8600                 }
8602                 return new L.Point(x, y, round);
8603         },
8605         _getBitCode: function (p, bounds) {
8606                 var code = 0;
8608                 if (p.x < bounds.min.x) { // left
8609                         code |= 1;
8610                 } else if (p.x > bounds.max.x) { // right
8611                         code |= 2;
8612                 }
8614                 if (p.y < bounds.min.y) { // bottom
8615                         code |= 4;
8616                 } else if (p.y > bounds.max.y) { // top
8617                         code |= 8;
8618                 }
8620                 return code;
8621         },
8623         // square distance (to avoid unnecessary Math.sqrt calls)
8624         _sqDist: function (p1, p2) {
8625                 var dx = p2.x - p1.x,
8626                     dy = p2.y - p1.y;
8627                 return dx * dx + dy * dy;
8628         },
8630         // return closest point on segment or distance to that point
8631         _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
8632                 var x = p1.x,
8633                     y = p1.y,
8634                     dx = p2.x - x,
8635                     dy = p2.y - y,
8636                     dot = dx * dx + dy * dy,
8637                     t;
8639                 if (dot > 0) {
8640                         t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
8642                         if (t > 1) {
8643                                 x = p2.x;
8644                                 y = p2.y;
8645                         } else if (t > 0) {
8646                                 x += dx * t;
8647                                 y += dy * t;
8648                         }
8649                 }
8651                 dx = p.x - x;
8652                 dy = p.y - y;
8654                 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
8655         }
8661  * @class Polyline
8662  * @aka L.Polyline
8663  * @inherits Path
8665  * A class for drawing polyline overlays on a map. Extends `Path`.
8667  * @example
8669  * ```js
8670  * // create a red polyline from an array of LatLng points
8671  * var latlngs = [
8672  *      [45.51, -122.68],
8673  *      [37.77, -122.43],
8674  *      [34.04, -118.2]
8675  * ];
8677  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8679  * // zoom the map to the polyline
8680  * map.fitBounds(polyline.getBounds());
8681  * ```
8683  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8685  * ```js
8686  * // create a red polyline from an array of arrays of LatLng points
8687  * var latlngs = [
8688  *      [[45.51, -122.68],
8689  *       [37.77, -122.43],
8690  *       [34.04, -118.2]],
8691  *      [[40.78, -73.91],
8692  *       [41.83, -87.62],
8693  *       [32.76, -96.72]]
8694  * ];
8695  * ```
8696  */
8698 L.Polyline = L.Path.extend({
8700         // @section
8701         // @aka Polyline options
8702         options: {
8703                 // @option smoothFactor: Number = 1.0
8704                 // How much to simplify the polyline on each zoom level. More means
8705                 // better performance and smoother look, and less means more accurate representation.
8706                 smoothFactor: 1.0,
8708                 // @option noClip: Boolean = false
8709                 // Disable polyline clipping.
8710                 noClip: false
8711         },
8713         initialize: function (latlngs, options) {
8714                 L.setOptions(this, options);
8715                 this._setLatLngs(latlngs);
8716         },
8718         // @method getLatLngs(): LatLng[]
8719         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8720         getLatLngs: function () {
8721                 return this._latlngs;
8722         },
8724         // @method setLatLngs(latlngs: LatLng[]): this
8725         // Replaces all the points in the polyline with the given array of geographical points.
8726         setLatLngs: function (latlngs) {
8727                 this._setLatLngs(latlngs);
8728                 return this.redraw();
8729         },
8731         // @method isEmpty(): Boolean
8732         // Returns `true` if the Polyline has no LatLngs.
8733         isEmpty: function () {
8734                 return !this._latlngs.length;
8735         },
8737         closestLayerPoint: function (p) {
8738                 var minDistance = Infinity,
8739                     minPoint = null,
8740                     closest = L.LineUtil._sqClosestPointOnSegment,
8741                     p1, p2;
8743                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8744                         var points = this._parts[j];
8746                         for (var i = 1, len = points.length; i < len; i++) {
8747                                 p1 = points[i - 1];
8748                                 p2 = points[i];
8750                                 var sqDist = closest(p, p1, p2, true);
8752                                 if (sqDist < minDistance) {
8753                                         minDistance = sqDist;
8754                                         minPoint = closest(p, p1, p2);
8755                                 }
8756                         }
8757                 }
8758                 if (minPoint) {
8759                         minPoint.distance = Math.sqrt(minDistance);
8760                 }
8761                 return minPoint;
8762         },
8764         // @method getCenter(): LatLng
8765         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8766         getCenter: function () {
8767                 // throws error when not yet added to map as this center calculation requires projected coordinates
8768                 if (!this._map) {
8769                         throw new Error('Must add layer to map before using getCenter()');
8770                 }
8772                 var i, halfDist, segDist, dist, p1, p2, ratio,
8773                     points = this._rings[0],
8774                     len = points.length;
8776                 if (!len) { return null; }
8778                 // polyline centroid algorithm; only uses the first ring if there are multiple
8780                 for (i = 0, halfDist = 0; i < len - 1; i++) {
8781                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
8782                 }
8784                 // The line is so small in the current view that all points are on the same pixel.
8785                 if (halfDist === 0) {
8786                         return this._map.layerPointToLatLng(points[0]);
8787                 }
8789                 for (i = 0, dist = 0; i < len - 1; i++) {
8790                         p1 = points[i];
8791                         p2 = points[i + 1];
8792                         segDist = p1.distanceTo(p2);
8793                         dist += segDist;
8795                         if (dist > halfDist) {
8796                                 ratio = (dist - halfDist) / segDist;
8797                                 return this._map.layerPointToLatLng([
8798                                         p2.x - ratio * (p2.x - p1.x),
8799                                         p2.y - ratio * (p2.y - p1.y)
8800                                 ]);
8801                         }
8802                 }
8803         },
8805         // @method getBounds(): LatLngBounds
8806         // Returns the `LatLngBounds` of the path.
8807         getBounds: function () {
8808                 return this._bounds;
8809         },
8811         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8812         // Adds a given point to the polyline. By default, adds to the first ring of
8813         // the polyline in case of a multi-polyline, but can be overridden by passing
8814         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8815         addLatLng: function (latlng, latlngs) {
8816                 latlngs = latlngs || this._defaultShape();
8817                 latlng = L.latLng(latlng);
8818                 latlngs.push(latlng);
8819                 this._bounds.extend(latlng);
8820                 return this.redraw();
8821         },
8823         _setLatLngs: function (latlngs) {
8824                 this._bounds = new L.LatLngBounds();
8825                 this._latlngs = this._convertLatLngs(latlngs);
8826         },
8828         _defaultShape: function () {
8829                 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
8830         },
8832         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8833         _convertLatLngs: function (latlngs) {
8834                 var result = [],
8835                     flat = L.Polyline._flat(latlngs);
8837                 for (var i = 0, len = latlngs.length; i < len; i++) {
8838                         if (flat) {
8839                                 result[i] = L.latLng(latlngs[i]);
8840                                 this._bounds.extend(result[i]);
8841                         } else {
8842                                 result[i] = this._convertLatLngs(latlngs[i]);
8843                         }
8844                 }
8846                 return result;
8847         },
8849         _project: function () {
8850                 var pxBounds = new L.Bounds();
8851                 this._rings = [];
8852                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8854                 var w = this._clickTolerance(),
8855                     p = new L.Point(w, w);
8857                 if (this._bounds.isValid() && pxBounds.isValid()) {
8858                         pxBounds.min._subtract(p);
8859                         pxBounds.max._add(p);
8860                         this._pxBounds = pxBounds;
8861                 }
8862         },
8864         // recursively turns latlngs into a set of rings with projected coordinates
8865         _projectLatlngs: function (latlngs, result, projectedBounds) {
8866                 var flat = latlngs[0] instanceof L.LatLng,
8867                     len = latlngs.length,
8868                     i, ring;
8870                 if (flat) {
8871                         ring = [];
8872                         for (i = 0; i < len; i++) {
8873                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8874                                 projectedBounds.extend(ring[i]);
8875                         }
8876                         result.push(ring);
8877                 } else {
8878                         for (i = 0; i < len; i++) {
8879                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8880                         }
8881                 }
8882         },
8884         // clip polyline by renderer bounds so that we have less to render for performance
8885         _clipPoints: function () {
8886                 var bounds = this._renderer._bounds;
8888                 this._parts = [];
8889                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8890                         return;
8891                 }
8893                 if (this.options.noClip) {
8894                         this._parts = this._rings;
8895                         return;
8896                 }
8898                 var parts = this._parts,
8899                     i, j, k, len, len2, segment, points;
8901                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8902                         points = this._rings[i];
8904                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8905                                 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8907                                 if (!segment) { continue; }
8909                                 parts[k] = parts[k] || [];
8910                                 parts[k].push(segment[0]);
8912                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8913                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8914                                         parts[k].push(segment[1]);
8915                                         k++;
8916                                 }
8917                         }
8918                 }
8919         },
8921         // simplify each clipped part of the polyline for performance
8922         _simplifyPoints: function () {
8923                 var parts = this._parts,
8924                     tolerance = this.options.smoothFactor;
8926                 for (var i = 0, len = parts.length; i < len; i++) {
8927                         parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8928                 }
8929         },
8931         _update: function () {
8932                 if (!this._map) { return; }
8934                 this._clipPoints();
8935                 this._simplifyPoints();
8936                 this._updatePath();
8937         },
8939         _updatePath: function () {
8940                 this._renderer._updatePoly(this);
8941         }
8944 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8945 // Instantiates a polyline object given an array of geographical points and
8946 // optionally an options object. You can create a `Polyline` object with
8947 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8948 // of geographic points.
8949 L.polyline = function (latlngs, options) {
8950         return new L.Polyline(latlngs, options);
8953 L.Polyline._flat = function (latlngs) {
8954         // true if it's a flat array of latlngs; false if nested
8955         return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8961  * @namespace PolyUtil
8962  * Various utility functions for polygon geometries.
8963  */
8965 L.PolyUtil = {};
8967 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8968  * 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)).
8969  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8970  * performance. Note that polygon points needs different algorithm for clipping
8971  * than polyline, so there's a seperate method for it.
8972  */
8973 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8974         var clippedPoints,
8975             edges = [1, 4, 2, 8],
8976             i, j, k,
8977             a, b,
8978             len, edge, p,
8979             lu = L.LineUtil;
8981         for (i = 0, len = points.length; i < len; i++) {
8982                 points[i]._code = lu._getBitCode(points[i], bounds);
8983         }
8985         // for each edge (left, bottom, right, top)
8986         for (k = 0; k < 4; k++) {
8987                 edge = edges[k];
8988                 clippedPoints = [];
8990                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8991                         a = points[i];
8992                         b = points[j];
8994                         // if a is inside the clip window
8995                         if (!(a._code & edge)) {
8996                                 // if b is outside the clip window (a->b goes out of screen)
8997                                 if (b._code & edge) {
8998                                         p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8999                                         p._code = lu._getBitCode(p, bounds);
9000                                         clippedPoints.push(p);
9001                                 }
9002                                 clippedPoints.push(a);
9004                         // else if b is inside the clip window (a->b enters the screen)
9005                         } else if (!(b._code & edge)) {
9006                                 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
9007                                 p._code = lu._getBitCode(p, bounds);
9008                                 clippedPoints.push(p);
9009                         }
9010                 }
9011                 points = clippedPoints;
9012         }
9014         return points;
9020  * @class Polygon
9021  * @aka L.Polygon
9022  * @inherits Polyline
9024  * A class for drawing polygon overlays on a map. Extends `Polyline`.
9026  * 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.
9029  * @example
9031  * ```js
9032  * // create a red polygon from an array of LatLng points
9033  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
9035  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
9037  * // zoom the map to the polygon
9038  * map.fitBounds(polygon.getBounds());
9039  * ```
9041  * 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:
9043  * ```js
9044  * var latlngs = [
9045  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
9046  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
9047  * ];
9048  * ```
9050  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
9052  * ```js
9053  * var latlngs = [
9054  *   [ // first polygon
9055  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
9056  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
9057  *   ],
9058  *   [ // second polygon
9059  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
9060  *   ]
9061  * ];
9062  * ```
9063  */
9065 L.Polygon = L.Polyline.extend({
9067         options: {
9068                 fill: true
9069         },
9071         isEmpty: function () {
9072                 return !this._latlngs.length || !this._latlngs[0].length;
9073         },
9075         getCenter: function () {
9076                 // throws error when not yet added to map as this center calculation requires projected coordinates
9077                 if (!this._map) {
9078                         throw new Error('Must add layer to map before using getCenter()');
9079                 }
9081                 var i, j, p1, p2, f, area, x, y, center,
9082                     points = this._rings[0],
9083                     len = points.length;
9085                 if (!len) { return null; }
9087                 // polygon centroid algorithm; only uses the first ring if there are multiple
9089                 area = x = y = 0;
9091                 for (i = 0, j = len - 1; i < len; j = i++) {
9092                         p1 = points[i];
9093                         p2 = points[j];
9095                         f = p1.y * p2.x - p2.y * p1.x;
9096                         x += (p1.x + p2.x) * f;
9097                         y += (p1.y + p2.y) * f;
9098                         area += f * 3;
9099                 }
9101                 if (area === 0) {
9102                         // Polygon is so small that all points are on same pixel.
9103                         center = points[0];
9104                 } else {
9105                         center = [x / area, y / area];
9106                 }
9107                 return this._map.layerPointToLatLng(center);
9108         },
9110         _convertLatLngs: function (latlngs) {
9111                 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
9112                     len = result.length;
9114                 // remove last point if it equals first one
9115                 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
9116                         result.pop();
9117                 }
9118                 return result;
9119         },
9121         _setLatLngs: function (latlngs) {
9122                 L.Polyline.prototype._setLatLngs.call(this, latlngs);
9123                 if (L.Polyline._flat(this._latlngs)) {
9124                         this._latlngs = [this._latlngs];
9125                 }
9126         },
9128         _defaultShape: function () {
9129                 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
9130         },
9132         _clipPoints: function () {
9133                 // polygons need a different clipping algorithm so we redefine that
9135                 var bounds = this._renderer._bounds,
9136                     w = this.options.weight,
9137                     p = new L.Point(w, w);
9139                 // increase clip padding by stroke width to avoid stroke on clip edges
9140                 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
9142                 this._parts = [];
9143                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
9144                         return;
9145                 }
9147                 if (this.options.noClip) {
9148                         this._parts = this._rings;
9149                         return;
9150                 }
9152                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
9153                         clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
9154                         if (clipped.length) {
9155                                 this._parts.push(clipped);
9156                         }
9157                 }
9158         },
9160         _updatePath: function () {
9161                 this._renderer._updatePoly(this, true);
9162         }
9166 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
9167 L.polygon = function (latlngs, options) {
9168         return new L.Polygon(latlngs, options);
9174  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
9175  */
9178  * @class Rectangle
9179  * @aka L.Retangle
9180  * @inherits Polygon
9182  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
9184  * @example
9186  * ```js
9187  * // define rectangle geographical bounds
9188  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
9190  * // create an orange rectangle
9191  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
9193  * // zoom the map to the rectangle bounds
9194  * map.fitBounds(bounds);
9195  * ```
9197  */
9200 L.Rectangle = L.Polygon.extend({
9201         initialize: function (latLngBounds, options) {
9202                 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
9203         },
9205         // @method setBounds(latLngBounds: LatLngBounds): this
9206         // Redraws the rectangle with the passed bounds.
9207         setBounds: function (latLngBounds) {
9208                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
9209         },
9211         _boundsToLatLngs: function (latLngBounds) {
9212                 latLngBounds = L.latLngBounds(latLngBounds);
9213                 return [
9214                         latLngBounds.getSouthWest(),
9215                         latLngBounds.getNorthWest(),
9216                         latLngBounds.getNorthEast(),
9217                         latLngBounds.getSouthEast()
9218                 ];
9219         }
9223 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
9224 L.rectangle = function (latLngBounds, options) {
9225         return new L.Rectangle(latLngBounds, options);
9231  * @class CircleMarker
9232  * @aka L.CircleMarker
9233  * @inherits Path
9235  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
9236  */
9238 L.CircleMarker = L.Path.extend({
9240         // @section
9241         // @aka CircleMarker options
9242         options: {
9243                 fill: true,
9245                 // @option radius: Number = 10
9246                 // Radius of the circle marker, in pixels
9247                 radius: 10
9248         },
9250         initialize: function (latlng, options) {
9251                 L.setOptions(this, options);
9252                 this._latlng = L.latLng(latlng);
9253                 this._radius = this.options.radius;
9254         },
9256         // @method setLatLng(latLng: LatLng): this
9257         // Sets the position of a circle marker to a new location.
9258         setLatLng: function (latlng) {
9259                 this._latlng = L.latLng(latlng);
9260                 this.redraw();
9261                 return this.fire('move', {latlng: this._latlng});
9262         },
9264         // @method getLatLng(): LatLng
9265         // Returns the current geographical position of the circle marker
9266         getLatLng: function () {
9267                 return this._latlng;
9268         },
9270         // @method setRadius(radius: Number): this
9271         // Sets the radius of a circle marker. Units are in pixels.
9272         setRadius: function (radius) {
9273                 this.options.radius = this._radius = radius;
9274                 return this.redraw();
9275         },
9277         // @method getRadius(): Number
9278         // Returns the current radius of the circle
9279         getRadius: function () {
9280                 return this._radius;
9281         },
9283         setStyle : function (options) {
9284                 var radius = options && options.radius || this._radius;
9285                 L.Path.prototype.setStyle.call(this, options);
9286                 this.setRadius(radius);
9287                 return this;
9288         },
9290         _project: function () {
9291                 this._point = this._map.latLngToLayerPoint(this._latlng);
9292                 this._updateBounds();
9293         },
9295         _updateBounds: function () {
9296                 var r = this._radius,
9297                     r2 = this._radiusY || r,
9298                     w = this._clickTolerance(),
9299                     p = [r + w, r2 + w];
9300                 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
9301         },
9303         _update: function () {
9304                 if (this._map) {
9305                         this._updatePath();
9306                 }
9307         },
9309         _updatePath: function () {
9310                 this._renderer._updateCircle(this);
9311         },
9313         _empty: function () {
9314                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
9315         }
9319 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
9320 // Instantiates a circle marker object given a geographical point, and an optional options object.
9321 L.circleMarker = function (latlng, options) {
9322         return new L.CircleMarker(latlng, options);
9328  * @class Circle
9329  * @aka L.Circle
9330  * @inherits CircleMarker
9332  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
9334  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
9336  * @example
9338  * ```js
9339  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
9340  * ```
9341  */
9343 L.Circle = L.CircleMarker.extend({
9345         initialize: function (latlng, options, legacyOptions) {
9346                 if (typeof options === 'number') {
9347                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
9348                         options = L.extend({}, legacyOptions, {radius: options});
9349                 }
9350                 L.setOptions(this, options);
9351                 this._latlng = L.latLng(latlng);
9353                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
9355                 // @section
9356                 // @aka Circle options
9357                 // @option radius: Number; Radius of the circle, in meters.
9358                 this._mRadius = this.options.radius;
9359         },
9361         // @method setRadius(radius: Number): this
9362         // Sets the radius of a circle. Units are in meters.
9363         setRadius: function (radius) {
9364                 this._mRadius = radius;
9365                 return this.redraw();
9366         },
9368         // @method getRadius(): Number
9369         // Returns the current radius of a circle. Units are in meters.
9370         getRadius: function () {
9371                 return this._mRadius;
9372         },
9374         // @method getBounds(): LatLngBounds
9375         // Returns the `LatLngBounds` of the path.
9376         getBounds: function () {
9377                 var half = [this._radius, this._radiusY || this._radius];
9379                 return new L.LatLngBounds(
9380                         this._map.layerPointToLatLng(this._point.subtract(half)),
9381                         this._map.layerPointToLatLng(this._point.add(half)));
9382         },
9384         setStyle: L.Path.prototype.setStyle,
9386         _project: function () {
9388                 var lng = this._latlng.lng,
9389                     lat = this._latlng.lat,
9390                     map = this._map,
9391                     crs = map.options.crs;
9393                 if (crs.distance === L.CRS.Earth.distance) {
9394                         var d = Math.PI / 180,
9395                             latR = (this._mRadius / L.CRS.Earth.R) / d,
9396                             top = map.project([lat + latR, lng]),
9397                             bottom = map.project([lat - latR, lng]),
9398                             p = top.add(bottom).divideBy(2),
9399                             lat2 = map.unproject(p).lat,
9400                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
9401                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
9403                         if (isNaN(lngR) || lngR === 0) {
9404                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
9405                         }
9407                         this._point = p.subtract(map.getPixelOrigin());
9408                         this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
9409                         this._radiusY = Math.max(Math.round(p.y - top.y), 1);
9411                 } else {
9412                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
9414                         this._point = map.latLngToLayerPoint(this._latlng);
9415                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
9416                 }
9418                 this._updateBounds();
9419         }
9422 // @factory L.circle(latlng: LatLng, options?: Circle options)
9423 // Instantiates a circle object given a geographical point, and an options object
9424 // which contains the circle radius.
9425 // @alternative
9426 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
9427 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
9428 // Do not use in new applications or plugins.
9429 L.circle = function (latlng, options, legacyOptions) {
9430         return new L.Circle(latlng, options, legacyOptions);
9436  * @class SVG
9437  * @inherits Renderer
9438  * @aka L.SVG
9440  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
9441  * Inherits `Renderer`.
9443  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
9444  * available in all web browsers, notably Android 2.x and 3.x.
9446  * Although SVG is not available on IE7 and IE8, these browsers support
9447  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
9448  * (a now deprecated technology), and the SVG renderer will fall back to VML in
9449  * this case.
9451  * @example
9453  * Use SVG by default for all paths in the map:
9455  * ```js
9456  * var map = L.map('map', {
9457  *      renderer: L.svg()
9458  * });
9459  * ```
9461  * Use a SVG renderer with extra padding for specific vector geometries:
9463  * ```js
9464  * var map = L.map('map');
9465  * var myRenderer = L.svg({ padding: 0.5 });
9466  * var line = L.polyline( coordinates, { renderer: myRenderer } );
9467  * var circle = L.circle( center, { renderer: myRenderer } );
9468  * ```
9469  */
9471 L.SVG = L.Renderer.extend({
9473         getEvents: function () {
9474                 var events = L.Renderer.prototype.getEvents.call(this);
9475                 events.zoomstart = this._onZoomStart;
9476                 return events;
9477         },
9479         _initContainer: function () {
9480                 this._container = L.SVG.create('svg');
9482                 // makes it possible to click through svg root; we'll reset it back in individual paths
9483                 this._container.setAttribute('pointer-events', 'none');
9485                 this._rootGroup = L.SVG.create('g');
9486                 this._container.appendChild(this._rootGroup);
9487         },
9489         _onZoomStart: function () {
9490                 // Drag-then-pinch interactions might mess up the center and zoom.
9491                 // In this case, the easiest way to prevent this is re-do the renderer
9492                 //   bounds and padding when the zooming starts.
9493                 this._update();
9494         },
9496         _update: function () {
9497                 if (this._map._animatingZoom && this._bounds) { return; }
9499                 L.Renderer.prototype._update.call(this);
9501                 var b = this._bounds,
9502                     size = b.getSize(),
9503                     container = this._container;
9505                 // set size of svg-container if changed
9506                 if (!this._svgSize || !this._svgSize.equals(size)) {
9507                         this._svgSize = size;
9508                         container.setAttribute('width', size.x);
9509                         container.setAttribute('height', size.y);
9510                 }
9512                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
9513                 L.DomUtil.setPosition(container, b.min);
9514                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
9516                 this.fire('update');
9517         },
9519         // methods below are called by vector layers implementations
9521         _initPath: function (layer) {
9522                 var path = layer._path = L.SVG.create('path');
9524                 // @namespace Path
9525                 // @option className: String = null
9526                 // Custom class name set on an element. Only for SVG renderer.
9527                 if (layer.options.className) {
9528                         L.DomUtil.addClass(path, layer.options.className);
9529                 }
9531                 if (layer.options.interactive) {
9532                         L.DomUtil.addClass(path, 'leaflet-interactive');
9533                 }
9535                 this._updateStyle(layer);
9536                 this._layers[L.stamp(layer)] = layer;
9537         },
9539         _addPath: function (layer) {
9540                 this._rootGroup.appendChild(layer._path);
9541                 layer.addInteractiveTarget(layer._path);
9542         },
9544         _removePath: function (layer) {
9545                 L.DomUtil.remove(layer._path);
9546                 layer.removeInteractiveTarget(layer._path);
9547                 delete this._layers[L.stamp(layer)];
9548         },
9550         _updatePath: function (layer) {
9551                 layer._project();
9552                 layer._update();
9553         },
9555         _updateStyle: function (layer) {
9556                 var path = layer._path,
9557                     options = layer.options;
9559                 if (!path) { return; }
9561                 if (options.stroke) {
9562                         path.setAttribute('stroke', options.color);
9563                         path.setAttribute('stroke-opacity', options.opacity);
9564                         path.setAttribute('stroke-width', options.weight);
9565                         path.setAttribute('stroke-linecap', options.lineCap);
9566                         path.setAttribute('stroke-linejoin', options.lineJoin);
9568                         if (options.dashArray) {
9569                                 path.setAttribute('stroke-dasharray', options.dashArray);
9570                         } else {
9571                                 path.removeAttribute('stroke-dasharray');
9572                         }
9574                         if (options.dashOffset) {
9575                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
9576                         } else {
9577                                 path.removeAttribute('stroke-dashoffset');
9578                         }
9579                 } else {
9580                         path.setAttribute('stroke', 'none');
9581                 }
9583                 if (options.fill) {
9584                         path.setAttribute('fill', options.fillColor || options.color);
9585                         path.setAttribute('fill-opacity', options.fillOpacity);
9586                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
9587                 } else {
9588                         path.setAttribute('fill', 'none');
9589                 }
9590         },
9592         _updatePoly: function (layer, closed) {
9593                 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
9594         },
9596         _updateCircle: function (layer) {
9597                 var p = layer._point,
9598                     r = layer._radius,
9599                     r2 = layer._radiusY || r,
9600                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
9602                 // drawing a circle with two half-arcs
9603                 var d = layer._empty() ? 'M0 0' :
9604                                 'M' + (p.x - r) + ',' + p.y +
9605                                 arc + (r * 2) + ',0 ' +
9606                                 arc + (-r * 2) + ',0 ';
9608                 this._setPath(layer, d);
9609         },
9611         _setPath: function (layer, path) {
9612                 layer._path.setAttribute('d', path);
9613         },
9615         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
9616         _bringToFront: function (layer) {
9617                 L.DomUtil.toFront(layer._path);
9618         },
9620         _bringToBack: function (layer) {
9621                 L.DomUtil.toBack(layer._path);
9622         }
9626 // @namespace SVG; @section
9627 // There are several static functions which can be called without instantiating L.SVG:
9628 L.extend(L.SVG, {
9629         // @function create(name: String): SVGElement
9630         // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
9631         // corresponding to the class name passed. For example, using 'line' will return
9632         // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
9633         create: function (name) {
9634                 return document.createElementNS('http://www.w3.org/2000/svg', name);
9635         },
9637         // @function pointsToPath(rings: Point[], closed: Boolean): String
9638         // Generates a SVG path string for multiple rings, with each ring turning
9639         // into "M..L..L.." instructions
9640         pointsToPath: function (rings, closed) {
9641                 var str = '',
9642                     i, j, len, len2, points, p;
9644                 for (i = 0, len = rings.length; i < len; i++) {
9645                         points = rings[i];
9647                         for (j = 0, len2 = points.length; j < len2; j++) {
9648                                 p = points[j];
9649                                 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
9650                         }
9652                         // closes the ring for polygons; "x" is VML syntax
9653                         str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
9654                 }
9656                 // SVG complains about empty path strings
9657                 return str || 'M0 0';
9658         }
9661 // @namespace Browser; @property svg: Boolean
9662 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
9663 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
9666 // @namespace SVG
9667 // @factory L.svg(options?: Renderer options)
9668 // Creates a SVG renderer with the given options.
9669 L.svg = function (options) {
9670         return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
9676  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
9677  */
9680  * @class SVG
9682  * 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.
9684  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
9685  * with old versions of Internet Explorer.
9686  */
9688 // @namespace Browser; @property vml: Boolean
9689 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
9690 L.Browser.vml = !L.Browser.svg && (function () {
9691         try {
9692                 var div = document.createElement('div');
9693                 div.innerHTML = '<v:shape adj="1"/>';
9695                 var shape = div.firstChild;
9696                 shape.style.behavior = 'url(#default#VML)';
9698                 return shape && (typeof shape.adj === 'object');
9700         } catch (e) {
9701                 return false;
9702         }
9703 }());
9705 // redefine some SVG methods to handle VML syntax which is similar but with some differences
9706 L.SVG.include(!L.Browser.vml ? {} : {
9708         _initContainer: function () {
9709                 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
9710         },
9712         _update: function () {
9713                 if (this._map._animatingZoom) { return; }
9714                 L.Renderer.prototype._update.call(this);
9715                 this.fire('update');
9716         },
9718         _initPath: function (layer) {
9719                 var container = layer._container = L.SVG.create('shape');
9721                 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
9723                 container.coordsize = '1 1';
9725                 layer._path = L.SVG.create('path');
9726                 container.appendChild(layer._path);
9728                 this._updateStyle(layer);
9729                 this._layers[L.stamp(layer)] = layer;
9730         },
9732         _addPath: function (layer) {
9733                 var container = layer._container;
9734                 this._container.appendChild(container);
9736                 if (layer.options.interactive) {
9737                         layer.addInteractiveTarget(container);
9738                 }
9739         },
9741         _removePath: function (layer) {
9742                 var container = layer._container;
9743                 L.DomUtil.remove(container);
9744                 layer.removeInteractiveTarget(container);
9745                 delete this._layers[L.stamp(layer)];
9746         },
9748         _updateStyle: function (layer) {
9749                 var stroke = layer._stroke,
9750                     fill = layer._fill,
9751                     options = layer.options,
9752                     container = layer._container;
9754                 container.stroked = !!options.stroke;
9755                 container.filled = !!options.fill;
9757                 if (options.stroke) {
9758                         if (!stroke) {
9759                                 stroke = layer._stroke = L.SVG.create('stroke');
9760                         }
9761                         container.appendChild(stroke);
9762                         stroke.weight = options.weight + 'px';
9763                         stroke.color = options.color;
9764                         stroke.opacity = options.opacity;
9766                         if (options.dashArray) {
9767                                 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
9768                                     options.dashArray.join(' ') :
9769                                     options.dashArray.replace(/( *, *)/g, ' ');
9770                         } else {
9771                                 stroke.dashStyle = '';
9772                         }
9773                         stroke.endcap = options.lineCap.replace('butt', 'flat');
9774                         stroke.joinstyle = options.lineJoin;
9776                 } else if (stroke) {
9777                         container.removeChild(stroke);
9778                         layer._stroke = null;
9779                 }
9781                 if (options.fill) {
9782                         if (!fill) {
9783                                 fill = layer._fill = L.SVG.create('fill');
9784                         }
9785                         container.appendChild(fill);
9786                         fill.color = options.fillColor || options.color;
9787                         fill.opacity = options.fillOpacity;
9789                 } else if (fill) {
9790                         container.removeChild(fill);
9791                         layer._fill = null;
9792                 }
9793         },
9795         _updateCircle: function (layer) {
9796                 var p = layer._point.round(),
9797                     r = Math.round(layer._radius),
9798                     r2 = Math.round(layer._radiusY || r);
9800                 this._setPath(layer, layer._empty() ? 'M0 0' :
9801                                 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
9802         },
9804         _setPath: function (layer, path) {
9805                 layer._path.v = path;
9806         },
9808         _bringToFront: function (layer) {
9809                 L.DomUtil.toFront(layer._container);
9810         },
9812         _bringToBack: function (layer) {
9813                 L.DomUtil.toBack(layer._container);
9814         }
9817 if (L.Browser.vml) {
9818         L.SVG.create = (function () {
9819                 try {
9820                         document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
9821                         return function (name) {
9822                                 return document.createElement('<lvml:' + name + ' class="lvml">');
9823                         };
9824                 } catch (e) {
9825                         return function (name) {
9826                                 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
9827                         };
9828                 }
9829         })();
9835  * @class Canvas
9836  * @inherits Renderer
9837  * @aka L.Canvas
9839  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9840  * Inherits `Renderer`.
9842  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
9843  * available in all web browsers, notably IE8, and overlapping geometries might
9844  * not display properly in some edge cases.
9846  * @example
9848  * Use Canvas by default for all paths in the map:
9850  * ```js
9851  * var map = L.map('map', {
9852  *      renderer: L.canvas()
9853  * });
9854  * ```
9856  * Use a Canvas renderer with extra padding for specific vector geometries:
9858  * ```js
9859  * var map = L.map('map');
9860  * var myRenderer = L.canvas({ padding: 0.5 });
9861  * var line = L.polyline( coordinates, { renderer: myRenderer } );
9862  * var circle = L.circle( center, { renderer: myRenderer } );
9863  * ```
9864  */
9866 L.Canvas = L.Renderer.extend({
9867         getEvents: function () {
9868                 var events = L.Renderer.prototype.getEvents.call(this);
9869                 events.viewprereset = this._onViewPreReset;
9870                 return events;
9871         },
9873         _onViewPreReset: function () {
9874                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
9875                 this._postponeUpdatePaths = true;
9876         },
9878         onAdd: function () {
9879                 L.Renderer.prototype.onAdd.call(this);
9881                 // Redraw vectors since canvas is cleared upon removal,
9882                 // in case of removing the renderer itself from the map.
9883                 this._draw();
9884         },
9886         _initContainer: function () {
9887                 var container = this._container = document.createElement('canvas');
9889                 L.DomEvent
9890                         .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
9891                         .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
9892                         .on(container, 'mouseout', this._handleMouseOut, this);
9894                 this._ctx = container.getContext('2d');
9895         },
9897         _updatePaths: function () {
9898                 if (this._postponeUpdatePaths) { return; }
9900                 var layer;
9901                 this._redrawBounds = null;
9902                 for (var id in this._layers) {
9903                         layer = this._layers[id];
9904                         layer._update();
9905                 }
9906                 this._redraw();
9907         },
9909         _update: function () {
9910                 if (this._map._animatingZoom && this._bounds) { return; }
9912                 this._drawnLayers = {};
9914                 L.Renderer.prototype._update.call(this);
9916                 var b = this._bounds,
9917                     container = this._container,
9918                     size = b.getSize(),
9919                     m = L.Browser.retina ? 2 : 1;
9921                 L.DomUtil.setPosition(container, b.min);
9923                 // set canvas size (also clearing it); use double size on retina
9924                 container.width = m * size.x;
9925                 container.height = m * size.y;
9926                 container.style.width = size.x + 'px';
9927                 container.style.height = size.y + 'px';
9929                 if (L.Browser.retina) {
9930                         this._ctx.scale(2, 2);
9931                 }
9933                 // translate so we use the same path coordinates after canvas element moves
9934                 this._ctx.translate(-b.min.x, -b.min.y);
9936                 // Tell paths to redraw themselves
9937                 this.fire('update');
9938         },
9940         _reset: function () {
9941                 L.Renderer.prototype._reset.call(this);
9943                 if (this._postponeUpdatePaths) {
9944                         this._postponeUpdatePaths = false;
9945                         this._updatePaths();
9946                 }
9947         },
9949         _initPath: function (layer) {
9950                 this._updateDashArray(layer);
9951                 this._layers[L.stamp(layer)] = layer;
9953                 var order = layer._order = {
9954                         layer: layer,
9955                         prev: this._drawLast,
9956                         next: null
9957                 };
9958                 if (this._drawLast) { this._drawLast.next = order; }
9959                 this._drawLast = order;
9960                 this._drawFirst = this._drawFirst || this._drawLast;
9961         },
9963         _addPath: function (layer) {
9964                 this._requestRedraw(layer);
9965         },
9967         _removePath: function (layer) {
9968                 var order = layer._order;
9969                 var next = order.next;
9970                 var prev = order.prev;
9972                 if (next) {
9973                         next.prev = prev;
9974                 } else {
9975                         this._drawLast = prev;
9976                 }
9977                 if (prev) {
9978                         prev.next = next;
9979                 } else {
9980                         this._drawFirst = next;
9981                 }
9983                 delete layer._order;
9985                 delete this._layers[L.stamp(layer)];
9987                 this._requestRedraw(layer);
9988         },
9990         _updatePath: function (layer) {
9991                 // Redraw the union of the layer's old pixel
9992                 // bounds and the new pixel bounds.
9993                 this._extendRedrawBounds(layer);
9994                 layer._project();
9995                 layer._update();
9996                 // The redraw will extend the redraw bounds
9997                 // with the new pixel bounds.
9998                 this._requestRedraw(layer);
9999         },
10001         _updateStyle: function (layer) {
10002                 this._updateDashArray(layer);
10003                 this._requestRedraw(layer);
10004         },
10006         _updateDashArray: function (layer) {
10007                 if (layer.options.dashArray) {
10008                         var parts = layer.options.dashArray.split(','),
10009                             dashArray = [],
10010                             i;
10011                         for (i = 0; i < parts.length; i++) {
10012                                 dashArray.push(Number(parts[i]));
10013                         }
10014                         layer.options._dashArray = dashArray;
10015                 }
10016         },
10018         _requestRedraw: function (layer) {
10019                 if (!this._map) { return; }
10021                 this._extendRedrawBounds(layer);
10022                 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
10023         },
10025         _extendRedrawBounds: function (layer) {
10026                 var padding = (layer.options.weight || 0) + 1;
10027                 this._redrawBounds = this._redrawBounds || new L.Bounds();
10028                 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
10029                 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
10030         },
10032         _redraw: function () {
10033                 this._redrawRequest = null;
10035                 if (this._redrawBounds) {
10036                         this._redrawBounds.min._floor();
10037                         this._redrawBounds.max._ceil();
10038                 }
10040                 this._clear(); // clear layers in redraw bounds
10041                 this._draw(); // draw layers
10043                 this._redrawBounds = null;
10044         },
10046         _clear: function () {
10047                 var bounds = this._redrawBounds;
10048                 if (bounds) {
10049                         var size = bounds.getSize();
10050                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
10051                 } else {
10052                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
10053                 }
10054         },
10056         _draw: function () {
10057                 var layer, bounds = this._redrawBounds;
10058                 this._ctx.save();
10059                 if (bounds) {
10060                         var size = bounds.getSize();
10061                         this._ctx.beginPath();
10062                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
10063                         this._ctx.clip();
10064                 }
10066                 this._drawing = true;
10068                 for (var order = this._drawFirst; order; order = order.next) {
10069                         layer = order.layer;
10070                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
10071                                 layer._updatePath();
10072                         }
10073                 }
10075                 this._drawing = false;
10077                 this._ctx.restore();  // Restore state before clipping.
10078         },
10080         _updatePoly: function (layer, closed) {
10081                 if (!this._drawing) { return; }
10083                 var i, j, len2, p,
10084                     parts = layer._parts,
10085                     len = parts.length,
10086                     ctx = this._ctx;
10088                 if (!len) { return; }
10090                 this._drawnLayers[layer._leaflet_id] = layer;
10092                 ctx.beginPath();
10094                 if (ctx.setLineDash) {
10095                         ctx.setLineDash(layer.options && layer.options._dashArray || []);
10096                 }
10098                 for (i = 0; i < len; i++) {
10099                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
10100                                 p = parts[i][j];
10101                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
10102                         }
10103                         if (closed) {
10104                                 ctx.closePath();
10105                         }
10106                 }
10108                 this._fillStroke(ctx, layer);
10110                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
10111         },
10113         _updateCircle: function (layer) {
10115                 if (!this._drawing || layer._empty()) { return; }
10117                 var p = layer._point,
10118                     ctx = this._ctx,
10119                     r = layer._radius,
10120                     s = (layer._radiusY || r) / r;
10122                 this._drawnLayers[layer._leaflet_id] = layer;
10124                 if (s !== 1) {
10125                         ctx.save();
10126                         ctx.scale(1, s);
10127                 }
10129                 ctx.beginPath();
10130                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
10132                 if (s !== 1) {
10133                         ctx.restore();
10134                 }
10136                 this._fillStroke(ctx, layer);
10137         },
10139         _fillStroke: function (ctx, layer) {
10140                 var options = layer.options;
10142                 if (options.fill) {
10143                         ctx.globalAlpha = options.fillOpacity;
10144                         ctx.fillStyle = options.fillColor || options.color;
10145                         ctx.fill(options.fillRule || 'evenodd');
10146                 }
10148                 if (options.stroke && options.weight !== 0) {
10149                         ctx.globalAlpha = options.opacity;
10150                         ctx.lineWidth = options.weight;
10151                         ctx.strokeStyle = options.color;
10152                         ctx.lineCap = options.lineCap;
10153                         ctx.lineJoin = options.lineJoin;
10154                         ctx.stroke();
10155                 }
10156         },
10158         // Canvas obviously doesn't have mouse events for individual drawn objects,
10159         // so we emulate that by calculating what's under the mouse on mousemove/click manually
10161         _onClick: function (e) {
10162                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
10164                 for (var order = this._drawFirst; order; order = order.next) {
10165                         layer = order.layer;
10166                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
10167                                 clickedLayer = layer;
10168                         }
10169                 }
10170                 if (clickedLayer)  {
10171                         L.DomEvent._fakeStop(e);
10172                         this._fireEvent([clickedLayer], e);
10173                 }
10174         },
10176         _onMouseMove: function (e) {
10177                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
10179                 var point = this._map.mouseEventToLayerPoint(e);
10180                 this._handleMouseHover(e, point);
10181         },
10184         _handleMouseOut: function (e) {
10185                 var layer = this._hoveredLayer;
10186                 if (layer) {
10187                         // if we're leaving the layer, fire mouseout
10188                         L.DomUtil.removeClass(this._container, 'leaflet-interactive');
10189                         this._fireEvent([layer], e, 'mouseout');
10190                         this._hoveredLayer = null;
10191                 }
10192         },
10194         _handleMouseHover: function (e, point) {
10195                 var layer, candidateHoveredLayer;
10197                 for (var order = this._drawFirst; order; order = order.next) {
10198                         layer = order.layer;
10199                         if (layer.options.interactive && layer._containsPoint(point)) {
10200                                 candidateHoveredLayer = layer;
10201                         }
10202                 }
10204                 if (candidateHoveredLayer !== this._hoveredLayer) {
10205                         this._handleMouseOut(e);
10207                         if (candidateHoveredLayer) {
10208                                 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
10209                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
10210                                 this._hoveredLayer = candidateHoveredLayer;
10211                         }
10212                 }
10214                 if (this._hoveredLayer) {
10215                         this._fireEvent([this._hoveredLayer], e);
10216                 }
10217         },
10219         _fireEvent: function (layers, e, type) {
10220                 this._map._fireDOMEvent(e, type || e.type, layers);
10221         },
10223         _bringToFront: function (layer) {
10224                 var order = layer._order;
10225                 var next = order.next;
10226                 var prev = order.prev;
10228                 if (next) {
10229                         next.prev = prev;
10230                 } else {
10231                         // Already last
10232                         return;
10233                 }
10234                 if (prev) {
10235                         prev.next = next;
10236                 } else if (next) {
10237                         // Update first entry unless this is the
10238                         // signle entry
10239                         this._drawFirst = next;
10240                 }
10242                 order.prev = this._drawLast;
10243                 this._drawLast.next = order;
10245                 order.next = null;
10246                 this._drawLast = order;
10248                 this._requestRedraw(layer);
10249         },
10251         _bringToBack: function (layer) {
10252                 var order = layer._order;
10253                 var next = order.next;
10254                 var prev = order.prev;
10256                 if (prev) {
10257                         prev.next = next;
10258                 } else {
10259                         // Already first
10260                         return;
10261                 }
10262                 if (next) {
10263                         next.prev = prev;
10264                 } else if (prev) {
10265                         // Update last entry unless this is the
10266                         // signle entry
10267                         this._drawLast = prev;
10268                 }
10270                 order.prev = null;
10272                 order.next = this._drawFirst;
10273                 this._drawFirst.prev = order;
10274                 this._drawFirst = order;
10276                 this._requestRedraw(layer);
10277         }
10280 // @namespace Browser; @property canvas: Boolean
10281 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
10282 L.Browser.canvas = (function () {
10283         return !!document.createElement('canvas').getContext;
10284 }());
10286 // @namespace Canvas
10287 // @factory L.canvas(options?: Renderer options)
10288 // Creates a Canvas renderer with the given options.
10289 L.canvas = function (options) {
10290         return L.Browser.canvas ? new L.Canvas(options) : null;
10293 L.Polyline.prototype._containsPoint = function (p, closed) {
10294         var i, j, k, len, len2, part,
10295             w = this._clickTolerance();
10297         if (!this._pxBounds.contains(p)) { return false; }
10299         // hit detection for polylines
10300         for (i = 0, len = this._parts.length; i < len; i++) {
10301                 part = this._parts[i];
10303                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10304                         if (!closed && (j === 0)) { continue; }
10306                         if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
10307                                 return true;
10308                         }
10309                 }
10310         }
10311         return false;
10314 L.Polygon.prototype._containsPoint = function (p) {
10315         var inside = false,
10316             part, p1, p2, i, j, k, len, len2;
10318         if (!this._pxBounds.contains(p)) { return false; }
10320         // ray casting algorithm for detecting if point is in polygon
10321         for (i = 0, len = this._parts.length; i < len; i++) {
10322                 part = this._parts[i];
10324                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10325                         p1 = part[j];
10326                         p2 = part[k];
10328                         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)) {
10329                                 inside = !inside;
10330                         }
10331                 }
10332         }
10334         // also check if it's on polygon stroke
10335         return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
10338 L.CircleMarker.prototype._containsPoint = function (p) {
10339         return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
10345  * @class GeoJSON
10346  * @aka L.GeoJSON
10347  * @inherits FeatureGroup
10349  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
10350  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
10352  * @example
10354  * ```js
10355  * L.geoJSON(data, {
10356  *      style: function (feature) {
10357  *              return {color: feature.properties.color};
10358  *      }
10359  * }).bindPopup(function (layer) {
10360  *      return layer.feature.properties.description;
10361  * }).addTo(map);
10362  * ```
10363  */
10365 L.GeoJSON = L.FeatureGroup.extend({
10367         /* @section
10368          * @aka GeoJSON options
10369          *
10370          * @option pointToLayer: Function = *
10371          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
10372          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
10373          * The default is to spawn a default `Marker`:
10374          * ```js
10375          * function(geoJsonPoint, latlng) {
10376          *      return L.marker(latlng);
10377          * }
10378          * ```
10379          *
10380          * @option style: Function = *
10381          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
10382          * called internally when data is added.
10383          * The default value is to not override any defaults:
10384          * ```js
10385          * function (geoJsonFeature) {
10386          *      return {}
10387          * }
10388          * ```
10389          *
10390          * @option onEachFeature: Function = *
10391          * A `Function` that will be called once for each created `Feature`, after it has
10392          * been created and styled. Useful for attaching events and popups to features.
10393          * The default is to do nothing with the newly created layers:
10394          * ```js
10395          * function (feature, layer) {}
10396          * ```
10397          *
10398          * @option filter: Function = *
10399          * A `Function` that will be used to decide whether to include a feature or not.
10400          * The default is to include all features:
10401          * ```js
10402          * function (geoJsonFeature) {
10403          *      return true;
10404          * }
10405          * ```
10406          * Note: dynamically changing the `filter` option will have effect only on newly
10407          * added data. It will _not_ re-evaluate already included features.
10408          *
10409          * @option coordsToLatLng: Function = *
10410          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
10411          * The default is the `coordsToLatLng` static method.
10412          */
10414         initialize: function (geojson, options) {
10415                 L.setOptions(this, options);
10417                 this._layers = {};
10419                 if (geojson) {
10420                         this.addData(geojson);
10421                 }
10422         },
10424         // @method addData( <GeoJSON> data ): this
10425         // Adds a GeoJSON object to the layer.
10426         addData: function (geojson) {
10427                 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
10428                     i, len, feature;
10430                 if (features) {
10431                         for (i = 0, len = features.length; i < len; i++) {
10432                                 // only add this if geometry or geometries are set and not null
10433                                 feature = features[i];
10434                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
10435                                         this.addData(feature);
10436                                 }
10437                         }
10438                         return this;
10439                 }
10441                 var options = this.options;
10443                 if (options.filter && !options.filter(geojson)) { return this; }
10445                 var layer = L.GeoJSON.geometryToLayer(geojson, options);
10446                 if (!layer) {
10447                         return this;
10448                 }
10449                 layer.feature = L.GeoJSON.asFeature(geojson);
10451                 layer.defaultOptions = layer.options;
10452                 this.resetStyle(layer);
10454                 if (options.onEachFeature) {
10455                         options.onEachFeature(geojson, layer);
10456                 }
10458                 return this.addLayer(layer);
10459         },
10461         // @method resetStyle( <Path> layer ): this
10462         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
10463         resetStyle: function (layer) {
10464                 // reset any custom styles
10465                 layer.options = L.Util.extend({}, layer.defaultOptions);
10466                 this._setLayerStyle(layer, this.options.style);
10467                 return this;
10468         },
10470         // @method setStyle( <Function> style ): this
10471         // Changes styles of GeoJSON vector layers with the given style function.
10472         setStyle: function (style) {
10473                 return this.eachLayer(function (layer) {
10474                         this._setLayerStyle(layer, style);
10475                 }, this);
10476         },
10478         _setLayerStyle: function (layer, style) {
10479                 if (typeof style === 'function') {
10480                         style = style(layer.feature);
10481                 }
10482                 if (layer.setStyle) {
10483                         layer.setStyle(style);
10484                 }
10485         }
10488 // @section
10489 // There are several static functions which can be called without instantiating L.GeoJSON:
10490 L.extend(L.GeoJSON, {
10491         // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
10492         // Creates a `Layer` from a given GeoJSON feature. Can use a custom
10493         // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
10494         // functions if provided as options.
10495         geometryToLayer: function (geojson, options) {
10497                 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
10498                     coords = geometry ? geometry.coordinates : null,
10499                     layers = [],
10500                     pointToLayer = options && options.pointToLayer,
10501                     coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
10502                     latlng, latlngs, i, len;
10504                 if (!coords && !geometry) {
10505                         return null;
10506                 }
10508                 switch (geometry.type) {
10509                 case 'Point':
10510                         latlng = coordsToLatLng(coords);
10511                         return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
10513                 case 'MultiPoint':
10514                         for (i = 0, len = coords.length; i < len; i++) {
10515                                 latlng = coordsToLatLng(coords[i]);
10516                                 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
10517                         }
10518                         return new L.FeatureGroup(layers);
10520                 case 'LineString':
10521                 case 'MultiLineString':
10522                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
10523                         return new L.Polyline(latlngs, options);
10525                 case 'Polygon':
10526                 case 'MultiPolygon':
10527                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
10528                         return new L.Polygon(latlngs, options);
10530                 case 'GeometryCollection':
10531                         for (i = 0, len = geometry.geometries.length; i < len; i++) {
10532                                 var layer = this.geometryToLayer({
10533                                         geometry: geometry.geometries[i],
10534                                         type: 'Feature',
10535                                         properties: geojson.properties
10536                                 }, options);
10538                                 if (layer) {
10539                                         layers.push(layer);
10540                                 }
10541                         }
10542                         return new L.FeatureGroup(layers);
10544                 default:
10545                         throw new Error('Invalid GeoJSON object.');
10546                 }
10547         },
10549         // @function coordsToLatLng(coords: Array): LatLng
10550         // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
10551         // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
10552         coordsToLatLng: function (coords) {
10553                 return new L.LatLng(coords[1], coords[0], coords[2]);
10554         },
10556         // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
10557         // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
10558         // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
10559         // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
10560         coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
10561                 var latlngs = [];
10563                 for (var i = 0, len = coords.length, latlng; i < len; i++) {
10564                         latlng = levelsDeep ?
10565                                 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
10566                                 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
10568                         latlngs.push(latlng);
10569                 }
10571                 return latlngs;
10572         },
10574         // @function latLngToCoords(latlng: LatLng): Array
10575         // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
10576         latLngToCoords: function (latlng) {
10577                 return latlng.alt !== undefined ?
10578                                 [latlng.lng, latlng.lat, latlng.alt] :
10579                                 [latlng.lng, latlng.lat];
10580         },
10582         // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
10583         // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
10584         // `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.
10585         latLngsToCoords: function (latlngs, levelsDeep, closed) {
10586                 var coords = [];
10588                 for (var i = 0, len = latlngs.length; i < len; i++) {
10589                         coords.push(levelsDeep ?
10590                                 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
10591                                 L.GeoJSON.latLngToCoords(latlngs[i]));
10592                 }
10594                 if (!levelsDeep && closed) {
10595                         coords.push(coords[0]);
10596                 }
10598                 return coords;
10599         },
10601         getFeature: function (layer, newGeometry) {
10602                 return layer.feature ?
10603                                 L.extend({}, layer.feature, {geometry: newGeometry}) :
10604                                 L.GeoJSON.asFeature(newGeometry);
10605         },
10607         // @function asFeature(geojson: Object): Object
10608         // Normalize GeoJSON geometries/features into GeoJSON features.
10609         asFeature: function (geojson) {
10610                 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
10611                         return geojson;
10612                 }
10614                 return {
10615                         type: 'Feature',
10616                         properties: {},
10617                         geometry: geojson
10618                 };
10619         }
10622 var PointToGeoJSON = {
10623         toGeoJSON: function () {
10624                 return L.GeoJSON.getFeature(this, {
10625                         type: 'Point',
10626                         coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
10627                 });
10628         }
10631 // @namespace Marker
10632 // @method toGeoJSON(): Object
10633 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
10634 L.Marker.include(PointToGeoJSON);
10636 // @namespace CircleMarker
10637 // @method toGeoJSON(): Object
10638 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
10639 L.Circle.include(PointToGeoJSON);
10640 L.CircleMarker.include(PointToGeoJSON);
10643 // @namespace Polyline
10644 // @method toGeoJSON(): Object
10645 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
10646 L.Polyline.prototype.toGeoJSON = function () {
10647         var multi = !L.Polyline._flat(this._latlngs);
10649         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
10651         return L.GeoJSON.getFeature(this, {
10652                 type: (multi ? 'Multi' : '') + 'LineString',
10653                 coordinates: coords
10654         });
10657 // @namespace Polygon
10658 // @method toGeoJSON(): Object
10659 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
10660 L.Polygon.prototype.toGeoJSON = function () {
10661         var holes = !L.Polyline._flat(this._latlngs),
10662             multi = holes && !L.Polyline._flat(this._latlngs[0]);
10664         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
10666         if (!holes) {
10667                 coords = [coords];
10668         }
10670         return L.GeoJSON.getFeature(this, {
10671                 type: (multi ? 'Multi' : '') + 'Polygon',
10672                 coordinates: coords
10673         });
10677 // @namespace LayerGroup
10678 L.LayerGroup.include({
10679         toMultiPoint: function () {
10680                 var coords = [];
10682                 this.eachLayer(function (layer) {
10683                         coords.push(layer.toGeoJSON().geometry.coordinates);
10684                 });
10686                 return L.GeoJSON.getFeature(this, {
10687                         type: 'MultiPoint',
10688                         coordinates: coords
10689                 });
10690         },
10692         // @method toGeoJSON(): Object
10693         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
10694         toGeoJSON: function () {
10696                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
10698                 if (type === 'MultiPoint') {
10699                         return this.toMultiPoint();
10700                 }
10702                 var isGeometryCollection = type === 'GeometryCollection',
10703                     jsons = [];
10705                 this.eachLayer(function (layer) {
10706                         if (layer.toGeoJSON) {
10707                                 var json = layer.toGeoJSON();
10708                                 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
10709                         }
10710                 });
10712                 if (isGeometryCollection) {
10713                         return L.GeoJSON.getFeature(this, {
10714                                 geometries: jsons,
10715                                 type: 'GeometryCollection'
10716                         });
10717                 }
10719                 return {
10720                         type: 'FeatureCollection',
10721                         features: jsons
10722                 };
10723         }
10726 // @namespace GeoJSON
10727 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
10728 // Creates a GeoJSON layer. Optionally accepts an object in
10729 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
10730 // (you can alternatively add it later with `addData` method) and an `options` object.
10731 L.geoJSON = function (geojson, options) {
10732         return new L.GeoJSON(geojson, options);
10734 // Backward compatibility.
10735 L.geoJson = L.geoJSON;
10740  * @class Draggable
10741  * @aka L.Draggable
10742  * @inherits Evented
10744  * A class for making DOM elements draggable (including touch support).
10745  * Used internally for map and marker dragging. Only works for elements
10746  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10748  * @example
10749  * ```js
10750  * var draggable = new L.Draggable(elementToDrag);
10751  * draggable.enable();
10752  * ```
10753  */
10755 L.Draggable = L.Evented.extend({
10757         options: {
10758                 // @option clickTolerance: Number = 3
10759                 // The max number of pixels a user can shift the mouse pointer during a click
10760                 // for it to be considered a valid click (as opposed to a mouse drag).
10761                 clickTolerance: 3
10762         },
10764         statics: {
10765                 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10766                 END: {
10767                         mousedown: 'mouseup',
10768                         touchstart: 'touchend',
10769                         pointerdown: 'touchend',
10770                         MSPointerDown: 'touchend'
10771                 },
10772                 MOVE: {
10773                         mousedown: 'mousemove',
10774                         touchstart: 'touchmove',
10775                         pointerdown: 'touchmove',
10776                         MSPointerDown: 'touchmove'
10777                 }
10778         },
10780         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10781         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10782         initialize: function (element, dragStartTarget, preventOutline) {
10783                 this._element = element;
10784                 this._dragStartTarget = dragStartTarget || element;
10785                 this._preventOutline = preventOutline;
10786         },
10788         // @method enable()
10789         // Enables the dragging ability
10790         enable: function () {
10791                 if (this._enabled) { return; }
10793                 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10795                 this._enabled = true;
10796         },
10798         // @method disable()
10799         // Disables the dragging ability
10800         disable: function () {
10801                 if (!this._enabled) { return; }
10803                 // If we're currently dragging this draggable,
10804                 // disabling it counts as first ending the drag.
10805                 if (L.Draggable._dragging === this) {
10806                         this.finishDrag();
10807                 }
10809                 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10811                 this._enabled = false;
10812                 this._moved = false;
10813         },
10815         _onDown: function (e) {
10816                 // Ignore simulated events, since we handle both touch and
10817                 // mouse explicitly; otherwise we risk getting duplicates of
10818                 // touch events, see #4315.
10819                 // Also ignore the event if disabled; this happens in IE11
10820                 // under some circumstances, see #3666.
10821                 if (e._simulated || !this._enabled) { return; }
10823                 this._moved = false;
10825                 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10827                 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
10828                 L.Draggable._dragging = this;  // Prevent dragging multiple objects at once.
10830                 if (this._preventOutline) {
10831                         L.DomUtil.preventOutline(this._element);
10832                 }
10834                 L.DomUtil.disableImageDrag();
10835                 L.DomUtil.disableTextSelection();
10837                 if (this._moving) { return; }
10839                 // @event down: Event
10840                 // Fired when a drag is about to start.
10841                 this.fire('down');
10843                 var first = e.touches ? e.touches[0] : e;
10845                 this._startPoint = new L.Point(first.clientX, first.clientY);
10847                 L.DomEvent
10848                         .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10849                         .on(document, L.Draggable.END[e.type], this._onUp, this);
10850         },
10852         _onMove: function (e) {
10853                 // Ignore simulated events, since we handle both touch and
10854                 // mouse explicitly; otherwise we risk getting duplicates of
10855                 // touch events, see #4315.
10856                 // Also ignore the event if disabled; this happens in IE11
10857                 // under some circumstances, see #3666.
10858                 if (e._simulated || !this._enabled) { return; }
10860                 if (e.touches && e.touches.length > 1) {
10861                         this._moved = true;
10862                         return;
10863                 }
10865                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10866                     newPoint = new L.Point(first.clientX, first.clientY),
10867                     offset = newPoint.subtract(this._startPoint);
10869                 if (!offset.x && !offset.y) { return; }
10870                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10872                 L.DomEvent.preventDefault(e);
10874                 if (!this._moved) {
10875                         // @event dragstart: Event
10876                         // Fired when a drag starts
10877                         this.fire('dragstart');
10879                         this._moved = true;
10880                         this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10882                         L.DomUtil.addClass(document.body, 'leaflet-dragging');
10884                         this._lastTarget = e.target || e.srcElement;
10885                         // IE and Edge do not give the <use> element, so fetch it
10886                         // if necessary
10887                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10888                                 this._lastTarget = this._lastTarget.correspondingUseElement;
10889                         }
10890                         L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10891                 }
10893                 this._newPos = this._startPos.add(offset);
10894                 this._moving = true;
10896                 L.Util.cancelAnimFrame(this._animRequest);
10897                 this._lastEvent = e;
10898                 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10899         },
10901         _updatePosition: function () {
10902                 var e = {originalEvent: this._lastEvent};
10904                 // @event predrag: Event
10905                 // Fired continuously during dragging *before* each corresponding
10906                 // update of the element's position.
10907                 this.fire('predrag', e);
10908                 L.DomUtil.setPosition(this._element, this._newPos);
10910                 // @event drag: Event
10911                 // Fired continuously during dragging.
10912                 this.fire('drag', e);
10913         },
10915         _onUp: function (e) {
10916                 // Ignore simulated events, since we handle both touch and
10917                 // mouse explicitly; otherwise we risk getting duplicates of
10918                 // touch events, see #4315.
10919                 // Also ignore the event if disabled; this happens in IE11
10920                 // under some circumstances, see #3666.
10921                 if (e._simulated || !this._enabled) { return; }
10922                 this.finishDrag();
10923         },
10925         finishDrag: function () {
10926                 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10928                 if (this._lastTarget) {
10929                         L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10930                         this._lastTarget = null;
10931                 }
10933                 for (var i in L.Draggable.MOVE) {
10934                         L.DomEvent
10935                                 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10936                                 .off(document, L.Draggable.END[i], this._onUp, this);
10937                 }
10939                 L.DomUtil.enableImageDrag();
10940                 L.DomUtil.enableTextSelection();
10942                 if (this._moved && this._moving) {
10943                         // ensure drag is not fired after dragend
10944                         L.Util.cancelAnimFrame(this._animRequest);
10946                         // @event dragend: DragEndEvent
10947                         // Fired when the drag ends.
10948                         this.fire('dragend', {
10949                                 distance: this._newPos.distanceTo(this._startPos)
10950                         });
10951                 }
10953                 this._moving = false;
10954                 L.Draggable._dragging = false;
10955         }
10962         L.Handler is a base class for handler classes that are used internally to inject
10963         interaction features like dragging to classes like Map and Marker.
10966 // @class Handler
10967 // @aka L.Handler
10968 // Abstract class for map interaction handlers
10970 L.Handler = L.Class.extend({
10971         initialize: function (map) {
10972                 this._map = map;
10973         },
10975         // @method enable(): this
10976         // Enables the handler
10977         enable: function () {
10978                 if (this._enabled) { return this; }
10980                 this._enabled = true;
10981                 this.addHooks();
10982                 return this;
10983         },
10985         // @method disable(): this
10986         // Disables the handler
10987         disable: function () {
10988                 if (!this._enabled) { return this; }
10990                 this._enabled = false;
10991                 this.removeHooks();
10992                 return this;
10993         },
10995         // @method enabled(): Boolean
10996         // Returns `true` if the handler is enabled
10997         enabled: function () {
10998                 return !!this._enabled;
10999         }
11001         // @section Extension methods
11002         // Classes inheriting from `Handler` must implement the two following methods:
11003         // @method addHooks()
11004         // Called when the handler is enabled, should add event hooks.
11005         // @method removeHooks()
11006         // Called when the handler is disabled, should remove the event hooks added previously.
11012  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
11013  */
11015 // @namespace Map
11016 // @section Interaction Options
11017 L.Map.mergeOptions({
11018         // @option dragging: Boolean = true
11019         // Whether the map be draggable with mouse/touch or not.
11020         dragging: true,
11022         // @section Panning Inertia Options
11023         // @option inertia: Boolean = *
11024         // If enabled, panning of the map will have an inertia effect where
11025         // the map builds momentum while dragging and continues moving in
11026         // the same direction for some time. Feels especially nice on touch
11027         // devices. Enabled by default unless running on old Android devices.
11028         inertia: !L.Browser.android23,
11030         // @option inertiaDeceleration: Number = 3000
11031         // The rate with which the inertial movement slows down, in pixels/second².
11032         inertiaDeceleration: 3400, // px/s^2
11034         // @option inertiaMaxSpeed: Number = Infinity
11035         // Max speed of the inertial movement, in pixels/second.
11036         inertiaMaxSpeed: Infinity, // px/s
11038         // @option easeLinearity: Number = 0.2
11039         easeLinearity: 0.2,
11041         // TODO refactor, move to CRS
11042         // @option worldCopyJump: Boolean = false
11043         // With this option enabled, the map tracks when you pan to another "copy"
11044         // of the world and seamlessly jumps to the original one so that all overlays
11045         // like markers and vector layers are still visible.
11046         worldCopyJump: false,
11048         // @option maxBoundsViscosity: Number = 0.0
11049         // If `maxBounds` is set, this option will control how solid the bounds
11050         // are when dragging the map around. The default value of `0.0` allows the
11051         // user to drag outside the bounds at normal speed, higher values will
11052         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
11053         // solid, preventing the user from dragging outside the bounds.
11054         maxBoundsViscosity: 0.0
11057 L.Map.Drag = L.Handler.extend({
11058         addHooks: function () {
11059                 if (!this._draggable) {
11060                         var map = this._map;
11062                         this._draggable = new L.Draggable(map._mapPane, map._container);
11064                         this._draggable.on({
11065                                 down: this._onDown,
11066                                 dragstart: this._onDragStart,
11067                                 drag: this._onDrag,
11068                                 dragend: this._onDragEnd
11069                         }, this);
11071                         this._draggable.on('predrag', this._onPreDragLimit, this);
11072                         if (map.options.worldCopyJump) {
11073                                 this._draggable.on('predrag', this._onPreDragWrap, this);
11074                                 map.on('zoomend', this._onZoomEnd, this);
11076                                 map.whenReady(this._onZoomEnd, this);
11077                         }
11078                 }
11079                 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
11080                 this._draggable.enable();
11081                 this._positions = [];
11082                 this._times = [];
11083         },
11085         removeHooks: function () {
11086                 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
11087                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
11088                 this._draggable.disable();
11089         },
11091         moved: function () {
11092                 return this._draggable && this._draggable._moved;
11093         },
11095         moving: function () {
11096                 return this._draggable && this._draggable._moving;
11097         },
11099         _onDown: function () {
11100                 this._map._stop();
11101         },
11103         _onDragStart: function () {
11104                 var map = this._map;
11106                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
11107                         var bounds = L.latLngBounds(this._map.options.maxBounds);
11109                         this._offsetLimit = L.bounds(
11110                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
11111                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
11112                                         .add(this._map.getSize()));
11114                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
11115                 } else {
11116                         this._offsetLimit = null;
11117                 }
11119                 map
11120                     .fire('movestart')
11121                     .fire('dragstart');
11123                 if (map.options.inertia) {
11124                         this._positions = [];
11125                         this._times = [];
11126                 }
11127         },
11129         _onDrag: function (e) {
11130                 if (this._map.options.inertia) {
11131                         var time = this._lastTime = +new Date(),
11132                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
11134                         this._positions.push(pos);
11135                         this._times.push(time);
11137                         if (time - this._times[0] > 50) {
11138                                 this._positions.shift();
11139                                 this._times.shift();
11140                         }
11141                 }
11143                 this._map
11144                     .fire('move', e)
11145                     .fire('drag', e);
11146         },
11148         _onZoomEnd: function () {
11149                 var pxCenter = this._map.getSize().divideBy(2),
11150                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
11152                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
11153                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
11154         },
11156         _viscousLimit: function (value, threshold) {
11157                 return value - (value - threshold) * this._viscosity;
11158         },
11160         _onPreDragLimit: function () {
11161                 if (!this._viscosity || !this._offsetLimit) { return; }
11163                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
11165                 var limit = this._offsetLimit;
11166                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
11167                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
11168                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
11169                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
11171                 this._draggable._newPos = this._draggable._startPos.add(offset);
11172         },
11174         _onPreDragWrap: function () {
11175                 // TODO refactor to be able to adjust map pane position after zoom
11176                 var worldWidth = this._worldWidth,
11177                     halfWidth = Math.round(worldWidth / 2),
11178                     dx = this._initialWorldOffset,
11179                     x = this._draggable._newPos.x,
11180                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
11181                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
11182                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
11184                 this._draggable._absPos = this._draggable._newPos.clone();
11185                 this._draggable._newPos.x = newX;
11186         },
11188         _onDragEnd: function (e) {
11189                 var map = this._map,
11190                     options = map.options,
11192                     noInertia = !options.inertia || this._times.length < 2;
11194                 map.fire('dragend', e);
11196                 if (noInertia) {
11197                         map.fire('moveend');
11199                 } else {
11201                         var direction = this._lastPos.subtract(this._positions[0]),
11202                             duration = (this._lastTime - this._times[0]) / 1000,
11203                             ease = options.easeLinearity,
11205                             speedVector = direction.multiplyBy(ease / duration),
11206                             speed = speedVector.distanceTo([0, 0]),
11208                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
11209                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
11211                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
11212                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
11214                         if (!offset.x && !offset.y) {
11215                                 map.fire('moveend');
11217                         } else {
11218                                 offset = map._limitOffset(offset, map.options.maxBounds);
11220                                 L.Util.requestAnimFrame(function () {
11221                                         map.panBy(offset, {
11222                                                 duration: decelerationDuration,
11223                                                 easeLinearity: ease,
11224                                                 noMoveStart: true,
11225                                                 animate: true
11226                                         });
11227                                 });
11228                         }
11229                 }
11230         }
11233 // @section Handlers
11234 // @property dragging: Handler
11235 // Map dragging handler (by both mouse and touch).
11236 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
11241  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
11242  */
11244 // @namespace Map
11245 // @section Interaction Options
11247 L.Map.mergeOptions({
11248         // @option doubleClickZoom: Boolean|String = true
11249         // Whether the map can be zoomed in by double clicking on it and
11250         // zoomed out by double clicking while holding shift. If passed
11251         // `'center'`, double-click zoom will zoom to the center of the
11252         //  view regardless of where the mouse was.
11253         doubleClickZoom: true
11256 L.Map.DoubleClickZoom = L.Handler.extend({
11257         addHooks: function () {
11258                 this._map.on('dblclick', this._onDoubleClick, this);
11259         },
11261         removeHooks: function () {
11262                 this._map.off('dblclick', this._onDoubleClick, this);
11263         },
11265         _onDoubleClick: function (e) {
11266                 var map = this._map,
11267                     oldZoom = map.getZoom(),
11268                     delta = map.options.zoomDelta,
11269                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
11271                 if (map.options.doubleClickZoom === 'center') {
11272                         map.setZoom(zoom);
11273                 } else {
11274                         map.setZoomAround(e.containerPoint, zoom);
11275                 }
11276         }
11279 // @section Handlers
11281 // Map properties include interaction handlers that allow you to control
11282 // interaction behavior in runtime, enabling or disabling certain features such
11283 // as dragging or touch zoom (see `Handler` methods). For example:
11285 // ```js
11286 // map.doubleClickZoom.disable();
11287 // ```
11289 // @property doubleClickZoom: Handler
11290 // Double click zoom handler.
11291 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
11296  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
11297  */
11299 // @namespace Map
11300 // @section Interaction Options
11301 L.Map.mergeOptions({
11302         // @section Mousewheel options
11303         // @option scrollWheelZoom: Boolean|String = true
11304         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
11305         // it will zoom to the center of the view regardless of where the mouse was.
11306         scrollWheelZoom: true,
11308         // @option wheelDebounceTime: Number = 40
11309         // Limits the rate at which a wheel can fire (in milliseconds). By default
11310         // user can't zoom via wheel more often than once per 40 ms.
11311         wheelDebounceTime: 40,
11313         // @option wheelPxPerZoomLevel: Number = 60
11314         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
11315         // mean a change of one full zoom level. Smaller values will make wheel-zooming
11316         // faster (and vice versa).
11317         wheelPxPerZoomLevel: 60
11320 L.Map.ScrollWheelZoom = L.Handler.extend({
11321         addHooks: function () {
11322                 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
11324                 this._delta = 0;
11325         },
11327         removeHooks: function () {
11328                 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
11329         },
11331         _onWheelScroll: function (e) {
11332                 var delta = L.DomEvent.getWheelDelta(e);
11334                 var debounce = this._map.options.wheelDebounceTime;
11336                 this._delta += delta;
11337                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
11339                 if (!this._startTime) {
11340                         this._startTime = +new Date();
11341                 }
11343                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
11345                 clearTimeout(this._timer);
11346                 this._timer = setTimeout(L.bind(this._performZoom, this), left);
11348                 L.DomEvent.stop(e);
11349         },
11351         _performZoom: function () {
11352                 var map = this._map,
11353                     zoom = map.getZoom(),
11354                     snap = this._map.options.zoomSnap || 0;
11356                 map._stop(); // stop panning and fly animations if any
11358                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
11359                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
11360                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
11361                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
11362                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
11364                 this._delta = 0;
11365                 this._startTime = null;
11367                 if (!delta) { return; }
11369                 if (map.options.scrollWheelZoom === 'center') {
11370                         map.setZoom(zoom + delta);
11371                 } else {
11372                         map.setZoomAround(this._lastMousePos, zoom + delta);
11373                 }
11374         }
11377 // @section Handlers
11378 // @property scrollWheelZoom: Handler
11379 // Scroll wheel zoom handler.
11380 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
11385  * Extends the event handling code with double tap support for mobile browsers.
11386  */
11388 L.extend(L.DomEvent, {
11390         _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
11391         _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
11393         // inspired by Zepto touch code by Thomas Fuchs
11394         addDoubleTapListener: function (obj, handler, id) {
11395                 var last, touch,
11396                     doubleTap = false,
11397                     delay = 250;
11399                 function onTouchStart(e) {
11400                         var count;
11402                         if (L.Browser.pointer) {
11403                                 if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
11404                                 count = L.DomEvent._pointersCount;
11405                         } else {
11406                                 count = e.touches.length;
11407                         }
11409                         if (count > 1) { return; }
11411                         var now = Date.now(),
11412                             delta = now - (last || now);
11414                         touch = e.touches ? e.touches[0] : e;
11415                         doubleTap = (delta > 0 && delta <= delay);
11416                         last = now;
11417                 }
11419                 function onTouchEnd(e) {
11420                         if (doubleTap && !touch.cancelBubble) {
11421                                 if (L.Browser.pointer) {
11422                                         if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
11424                                         // work around .type being readonly with MSPointer* events
11425                                         var newTouch = {},
11426                                             prop, i;
11428                                         for (i in touch) {
11429                                                 prop = touch[i];
11430                                                 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
11431                                         }
11432                                         touch = newTouch;
11433                                 }
11434                                 touch.type = 'dblclick';
11435                                 handler(touch);
11436                                 last = null;
11437                         }
11438                 }
11440                 var pre = '_leaflet_',
11441                     touchstart = this._touchstart,
11442                     touchend = this._touchend;
11444                 obj[pre + touchstart + id] = onTouchStart;
11445                 obj[pre + touchend + id] = onTouchEnd;
11446                 obj[pre + 'dblclick' + id] = handler;
11448                 obj.addEventListener(touchstart, onTouchStart, false);
11449                 obj.addEventListener(touchend, onTouchEnd, false);
11451                 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
11452                 // the browser doesn't fire touchend/pointerup events but does fire
11453                 // native dblclicks. See #4127.
11454                 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
11455                 obj.addEventListener('dblclick', handler, false);
11457                 return this;
11458         },
11460         removeDoubleTapListener: function (obj, id) {
11461                 var pre = '_leaflet_',
11462                     touchstart = obj[pre + this._touchstart + id],
11463                     touchend = obj[pre + this._touchend + id],
11464                     dblclick = obj[pre + 'dblclick' + id];
11466                 obj.removeEventListener(this._touchstart, touchstart, false);
11467                 obj.removeEventListener(this._touchend, touchend, false);
11468                 if (!L.Browser.edge) {
11469                         obj.removeEventListener('dblclick', dblclick, false);
11470                 }
11472                 return this;
11473         }
11479  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
11480  */
11482 L.extend(L.DomEvent, {
11484         POINTER_DOWN:   L.Browser.msPointer ? 'MSPointerDown'   : 'pointerdown',
11485         POINTER_MOVE:   L.Browser.msPointer ? 'MSPointerMove'   : 'pointermove',
11486         POINTER_UP:     L.Browser.msPointer ? 'MSPointerUp'     : 'pointerup',
11487         POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
11488         TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
11490         _pointers: {},
11491         _pointersCount: 0,
11493         // Provides a touch events wrapper for (ms)pointer events.
11494         // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
11496         addPointerListener: function (obj, type, handler, id) {
11498                 if (type === 'touchstart') {
11499                         this._addPointerStart(obj, handler, id);
11501                 } else if (type === 'touchmove') {
11502                         this._addPointerMove(obj, handler, id);
11504                 } else if (type === 'touchend') {
11505                         this._addPointerEnd(obj, handler, id);
11506                 }
11508                 return this;
11509         },
11511         removePointerListener: function (obj, type, id) {
11512                 var handler = obj['_leaflet_' + type + id];
11514                 if (type === 'touchstart') {
11515                         obj.removeEventListener(this.POINTER_DOWN, handler, false);
11517                 } else if (type === 'touchmove') {
11518                         obj.removeEventListener(this.POINTER_MOVE, handler, false);
11520                 } else if (type === 'touchend') {
11521                         obj.removeEventListener(this.POINTER_UP, handler, false);
11522                         obj.removeEventListener(this.POINTER_CANCEL, handler, false);
11523                 }
11525                 return this;
11526         },
11528         _addPointerStart: function (obj, handler, id) {
11529                 var onDown = L.bind(function (e) {
11530                         if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
11531                                 // In IE11, some touch events needs to fire for form controls, or
11532                                 // the controls will stop working. We keep a whitelist of tag names that
11533                                 // need these events. For other target tags, we prevent default on the event.
11534                                 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
11535                                         L.DomEvent.preventDefault(e);
11536                                 } else {
11537                                         return;
11538                                 }
11539                         }
11541                         this._handlePointer(e, handler);
11542                 }, this);
11544                 obj['_leaflet_touchstart' + id] = onDown;
11545                 obj.addEventListener(this.POINTER_DOWN, onDown, false);
11547                 // need to keep track of what pointers and how many are active to provide e.touches emulation
11548                 if (!this._pointerDocListener) {
11549                         var pointerUp = L.bind(this._globalPointerUp, this);
11551                         // we listen documentElement as any drags that end by moving the touch off the screen get fired there
11552                         document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
11553                         document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
11554                         document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
11555                         document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
11557                         this._pointerDocListener = true;
11558                 }
11559         },
11561         _globalPointerDown: function (e) {
11562                 this._pointers[e.pointerId] = e;
11563                 this._pointersCount++;
11564         },
11566         _globalPointerMove: function (e) {
11567                 if (this._pointers[e.pointerId]) {
11568                         this._pointers[e.pointerId] = e;
11569                 }
11570         },
11572         _globalPointerUp: function (e) {
11573                 delete this._pointers[e.pointerId];
11574                 this._pointersCount--;
11575         },
11577         _handlePointer: function (e, handler) {
11578                 e.touches = [];
11579                 for (var i in this._pointers) {
11580                         e.touches.push(this._pointers[i]);
11581                 }
11582                 e.changedTouches = [e];
11584                 handler(e);
11585         },
11587         _addPointerMove: function (obj, handler, id) {
11588                 var onMove = L.bind(function (e) {
11589                         // don't fire touch moves when mouse isn't down
11590                         if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
11592                         this._handlePointer(e, handler);
11593                 }, this);
11595                 obj['_leaflet_touchmove' + id] = onMove;
11596                 obj.addEventListener(this.POINTER_MOVE, onMove, false);
11597         },
11599         _addPointerEnd: function (obj, handler, id) {
11600                 var onUp = L.bind(function (e) {
11601                         this._handlePointer(e, handler);
11602                 }, this);
11604                 obj['_leaflet_touchend' + id] = onUp;
11605                 obj.addEventListener(this.POINTER_UP, onUp, false);
11606                 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
11607         }
11613  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
11614  */
11616 // @namespace Map
11617 // @section Interaction Options
11618 L.Map.mergeOptions({
11619         // @section Touch interaction options
11620         // @option touchZoom: Boolean|String = *
11621         // Whether the map can be zoomed by touch-dragging with two fingers. If
11622         // passed `'center'`, it will zoom to the center of the view regardless of
11623         // where the touch events (fingers) were. Enabled for touch-capable web
11624         // browsers except for old Androids.
11625         touchZoom: L.Browser.touch && !L.Browser.android23,
11627         // @option bounceAtZoomLimits: Boolean = true
11628         // Set it to false if you don't want the map to zoom beyond min/max zoom
11629         // and then bounce back when pinch-zooming.
11630         bounceAtZoomLimits: true
11633 L.Map.TouchZoom = L.Handler.extend({
11634         addHooks: function () {
11635                 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
11636                 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
11637         },
11639         removeHooks: function () {
11640                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
11641                 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
11642         },
11644         _onTouchStart: function (e) {
11645                 var map = this._map;
11646                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
11648                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
11649                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
11651                 this._centerPoint = map.getSize()._divideBy(2);
11652                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
11653                 if (map.options.touchZoom !== 'center') {
11654                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
11655                 }
11657                 this._startDist = p1.distanceTo(p2);
11658                 this._startZoom = map.getZoom();
11660                 this._moved = false;
11661                 this._zooming = true;
11663                 map._stop();
11665                 L.DomEvent
11666                     .on(document, 'touchmove', this._onTouchMove, this)
11667                     .on(document, 'touchend', this._onTouchEnd, this);
11669                 L.DomEvent.preventDefault(e);
11670         },
11672         _onTouchMove: function (e) {
11673                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
11675                 var map = this._map,
11676                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
11677                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
11678                     scale = p1.distanceTo(p2) / this._startDist;
11681                 this._zoom = map.getScaleZoom(scale, this._startZoom);
11683                 if (!map.options.bounceAtZoomLimits && (
11684                         (this._zoom < map.getMinZoom() && scale < 1) ||
11685                         (this._zoom > map.getMaxZoom() && scale > 1))) {
11686                         this._zoom = map._limitZoom(this._zoom);
11687                 }
11689                 if (map.options.touchZoom === 'center') {
11690                         this._center = this._startLatLng;
11691                         if (scale === 1) { return; }
11692                 } else {
11693                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
11694                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
11695                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
11696                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
11697                 }
11699                 if (!this._moved) {
11700                         map._moveStart(true);
11701                         this._moved = true;
11702                 }
11704                 L.Util.cancelAnimFrame(this._animRequest);
11706                 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
11707                 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
11709                 L.DomEvent.preventDefault(e);
11710         },
11712         _onTouchEnd: function () {
11713                 if (!this._moved || !this._zooming) {
11714                         this._zooming = false;
11715                         return;
11716                 }
11718                 this._zooming = false;
11719                 L.Util.cancelAnimFrame(this._animRequest);
11721                 L.DomEvent
11722                     .off(document, 'touchmove', this._onTouchMove)
11723                     .off(document, 'touchend', this._onTouchEnd);
11725                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
11726                 if (this._map.options.zoomAnimation) {
11727                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
11728                 } else {
11729                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11730                 }
11731         }
11734 // @section Handlers
11735 // @property touchZoom: Handler
11736 // Touch zoom handler.
11737 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11742  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11743  */
11745 // @namespace Map
11746 // @section Interaction Options
11747 L.Map.mergeOptions({
11748         // @section Touch interaction options
11749         // @option tap: Boolean = true
11750         // Enables mobile hacks for supporting instant taps (fixing 200ms click
11751         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11752         tap: true,
11754         // @option tapTolerance: Number = 15
11755         // The max number of pixels a user can shift his finger during touch
11756         // for it to be considered a valid tap.
11757         tapTolerance: 15
11760 L.Map.Tap = L.Handler.extend({
11761         addHooks: function () {
11762                 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11763         },
11765         removeHooks: function () {
11766                 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11767         },
11769         _onDown: function (e) {
11770                 if (!e.touches) { return; }
11772                 L.DomEvent.preventDefault(e);
11774                 this._fireClick = true;
11776                 // don't simulate click or track longpress if more than 1 touch
11777                 if (e.touches.length > 1) {
11778                         this._fireClick = false;
11779                         clearTimeout(this._holdTimeout);
11780                         return;
11781                 }
11783                 var first = e.touches[0],
11784                     el = first.target;
11786                 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11788                 // if touching a link, highlight it
11789                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11790                         L.DomUtil.addClass(el, 'leaflet-active');
11791                 }
11793                 // simulate long hold but setting a timeout
11794                 this._holdTimeout = setTimeout(L.bind(function () {
11795                         if (this._isTapValid()) {
11796                                 this._fireClick = false;
11797                                 this._onUp();
11798                                 this._simulateEvent('contextmenu', first);
11799                         }
11800                 }, this), 1000);
11802                 this._simulateEvent('mousedown', first);
11804                 L.DomEvent.on(document, {
11805                         touchmove: this._onMove,
11806                         touchend: this._onUp
11807                 }, this);
11808         },
11810         _onUp: function (e) {
11811                 clearTimeout(this._holdTimeout);
11813                 L.DomEvent.off(document, {
11814                         touchmove: this._onMove,
11815                         touchend: this._onUp
11816                 }, this);
11818                 if (this._fireClick && e && e.changedTouches) {
11820                         var first = e.changedTouches[0],
11821                             el = first.target;
11823                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11824                                 L.DomUtil.removeClass(el, 'leaflet-active');
11825                         }
11827                         this._simulateEvent('mouseup', first);
11829                         // simulate click if the touch didn't move too much
11830                         if (this._isTapValid()) {
11831                                 this._simulateEvent('click', first);
11832                         }
11833                 }
11834         },
11836         _isTapValid: function () {
11837                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11838         },
11840         _onMove: function (e) {
11841                 var first = e.touches[0];
11842                 this._newPos = new L.Point(first.clientX, first.clientY);
11843                 this._simulateEvent('mousemove', first);
11844         },
11846         _simulateEvent: function (type, e) {
11847                 var simulatedEvent = document.createEvent('MouseEvents');
11849                 simulatedEvent._simulated = true;
11850                 e.target._simulatedClick = true;
11852                 simulatedEvent.initMouseEvent(
11853                         type, true, true, window, 1,
11854                         e.screenX, e.screenY,
11855                         e.clientX, e.clientY,
11856                         false, false, false, false, 0, null);
11858                 e.target.dispatchEvent(simulatedEvent);
11859         }
11862 // @section Handlers
11863 // @property tap: Handler
11864 // Mobile touch hacks (quick tap and touch hold) handler.
11865 if (L.Browser.touch && !L.Browser.pointer) {
11866         L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11872  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11873  * (zoom to a selected bounding box), enabled by default.
11874  */
11876 // @namespace Map
11877 // @section Interaction Options
11878 L.Map.mergeOptions({
11879         // @option boxZoom: Boolean = true
11880         // Whether the map can be zoomed to a rectangular area specified by
11881         // dragging the mouse while pressing the shift key.
11882         boxZoom: true
11885 L.Map.BoxZoom = L.Handler.extend({
11886         initialize: function (map) {
11887                 this._map = map;
11888                 this._container = map._container;
11889                 this._pane = map._panes.overlayPane;
11890         },
11892         addHooks: function () {
11893                 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11894         },
11896         removeHooks: function () {
11897                 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11898         },
11900         moved: function () {
11901                 return this._moved;
11902         },
11904         _resetState: function () {
11905                 this._moved = false;
11906         },
11908         _onMouseDown: function (e) {
11909                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11911                 this._resetState();
11913                 L.DomUtil.disableTextSelection();
11914                 L.DomUtil.disableImageDrag();
11916                 this._startPoint = this._map.mouseEventToContainerPoint(e);
11918                 L.DomEvent.on(document, {
11919                         contextmenu: L.DomEvent.stop,
11920                         mousemove: this._onMouseMove,
11921                         mouseup: this._onMouseUp,
11922                         keydown: this._onKeyDown
11923                 }, this);
11924         },
11926         _onMouseMove: function (e) {
11927                 if (!this._moved) {
11928                         this._moved = true;
11930                         this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11931                         L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11933                         this._map.fire('boxzoomstart');
11934                 }
11936                 this._point = this._map.mouseEventToContainerPoint(e);
11938                 var bounds = new L.Bounds(this._point, this._startPoint),
11939                     size = bounds.getSize();
11941                 L.DomUtil.setPosition(this._box, bounds.min);
11943                 this._box.style.width  = size.x + 'px';
11944                 this._box.style.height = size.y + 'px';
11945         },
11947         _finish: function () {
11948                 if (this._moved) {
11949                         L.DomUtil.remove(this._box);
11950                         L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11951                 }
11953                 L.DomUtil.enableTextSelection();
11954                 L.DomUtil.enableImageDrag();
11956                 L.DomEvent.off(document, {
11957                         contextmenu: L.DomEvent.stop,
11958                         mousemove: this._onMouseMove,
11959                         mouseup: this._onMouseUp,
11960                         keydown: this._onKeyDown
11961                 }, this);
11962         },
11964         _onMouseUp: function (e) {
11965                 if ((e.which !== 1) && (e.button !== 1)) { return; }
11967                 this._finish();
11969                 if (!this._moved) { return; }
11970                 // Postpone to next JS tick so internal click event handling
11971                 // still see it as "moved".
11972                 setTimeout(L.bind(this._resetState, this), 0);
11974                 var bounds = new L.LatLngBounds(
11975                         this._map.containerPointToLatLng(this._startPoint),
11976                         this._map.containerPointToLatLng(this._point));
11978                 this._map
11979                         .fitBounds(bounds)
11980                         .fire('boxzoomend', {boxZoomBounds: bounds});
11981         },
11983         _onKeyDown: function (e) {
11984                 if (e.keyCode === 27) {
11985                         this._finish();
11986                 }
11987         }
11990 // @section Handlers
11991 // @property boxZoom: Handler
11992 // Box (shift-drag with mouse) zoom handler.
11993 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11998  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11999  */
12001 // @namespace Map
12002 // @section Keyboard Navigation Options
12003 L.Map.mergeOptions({
12004         // @option keyboard: Boolean = true
12005         // Makes the map focusable and allows users to navigate the map with keyboard
12006         // arrows and `+`/`-` keys.
12007         keyboard: true,
12009         // @option keyboardPanDelta: Number = 80
12010         // Amount of pixels to pan when pressing an arrow key.
12011         keyboardPanDelta: 80
12014 L.Map.Keyboard = L.Handler.extend({
12016         keyCodes: {
12017                 left:    [37],
12018                 right:   [39],
12019                 down:    [40],
12020                 up:      [38],
12021                 zoomIn:  [187, 107, 61, 171],
12022                 zoomOut: [189, 109, 54, 173]
12023         },
12025         initialize: function (map) {
12026                 this._map = map;
12028                 this._setPanDelta(map.options.keyboardPanDelta);
12029                 this._setZoomDelta(map.options.zoomDelta);
12030         },
12032         addHooks: function () {
12033                 var container = this._map._container;
12035                 // make the container focusable by tabbing
12036                 if (container.tabIndex <= 0) {
12037                         container.tabIndex = '0';
12038                 }
12040                 L.DomEvent.on(container, {
12041                         focus: this._onFocus,
12042                         blur: this._onBlur,
12043                         mousedown: this._onMouseDown
12044                 }, this);
12046                 this._map.on({
12047                         focus: this._addHooks,
12048                         blur: this._removeHooks
12049                 }, this);
12050         },
12052         removeHooks: function () {
12053                 this._removeHooks();
12055                 L.DomEvent.off(this._map._container, {
12056                         focus: this._onFocus,
12057                         blur: this._onBlur,
12058                         mousedown: this._onMouseDown
12059                 }, this);
12061                 this._map.off({
12062                         focus: this._addHooks,
12063                         blur: this._removeHooks
12064                 }, this);
12065         },
12067         _onMouseDown: function () {
12068                 if (this._focused) { return; }
12070                 var body = document.body,
12071                     docEl = document.documentElement,
12072                     top = body.scrollTop || docEl.scrollTop,
12073                     left = body.scrollLeft || docEl.scrollLeft;
12075                 this._map._container.focus();
12077                 window.scrollTo(left, top);
12078         },
12080         _onFocus: function () {
12081                 this._focused = true;
12082                 this._map.fire('focus');
12083         },
12085         _onBlur: function () {
12086                 this._focused = false;
12087                 this._map.fire('blur');
12088         },
12090         _setPanDelta: function (panDelta) {
12091                 var keys = this._panKeys = {},
12092                     codes = this.keyCodes,
12093                     i, len;
12095                 for (i = 0, len = codes.left.length; i < len; i++) {
12096                         keys[codes.left[i]] = [-1 * panDelta, 0];
12097                 }
12098                 for (i = 0, len = codes.right.length; i < len; i++) {
12099                         keys[codes.right[i]] = [panDelta, 0];
12100                 }
12101                 for (i = 0, len = codes.down.length; i < len; i++) {
12102                         keys[codes.down[i]] = [0, panDelta];
12103                 }
12104                 for (i = 0, len = codes.up.length; i < len; i++) {
12105                         keys[codes.up[i]] = [0, -1 * panDelta];
12106                 }
12107         },
12109         _setZoomDelta: function (zoomDelta) {
12110                 var keys = this._zoomKeys = {},
12111                     codes = this.keyCodes,
12112                     i, len;
12114                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
12115                         keys[codes.zoomIn[i]] = zoomDelta;
12116                 }
12117                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
12118                         keys[codes.zoomOut[i]] = -zoomDelta;
12119                 }
12120         },
12122         _addHooks: function () {
12123                 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
12124         },
12126         _removeHooks: function () {
12127                 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
12128         },
12130         _onKeyDown: function (e) {
12131                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
12133                 var key = e.keyCode,
12134                     map = this._map,
12135                     offset;
12137                 if (key in this._panKeys) {
12139                         if (map._panAnim && map._panAnim._inProgress) { return; }
12141                         offset = this._panKeys[key];
12142                         if (e.shiftKey) {
12143                                 offset = L.point(offset).multiplyBy(3);
12144                         }
12146                         map.panBy(offset);
12148                         if (map.options.maxBounds) {
12149                                 map.panInsideBounds(map.options.maxBounds);
12150                         }
12152                 } else if (key in this._zoomKeys) {
12153                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
12155                 } else if (key === 27) {
12156                         map.closePopup();
12158                 } else {
12159                         return;
12160                 }
12162                 L.DomEvent.stop(e);
12163         }
12166 // @section Handlers
12167 // @section Handlers
12168 // @property keyboard: Handler
12169 // Keyboard navigation handler.
12170 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
12175  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
12176  */
12179 /* @namespace Marker
12180  * @section Interaction handlers
12182  * 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:
12184  * ```js
12185  * marker.dragging.disable();
12186  * ```
12188  * @property dragging: Handler
12189  * Marker dragging handler (by both mouse and touch).
12190  */
12192 L.Handler.MarkerDrag = L.Handler.extend({
12193         initialize: function (marker) {
12194                 this._marker = marker;
12195         },
12197         addHooks: function () {
12198                 var icon = this._marker._icon;
12200                 if (!this._draggable) {
12201                         this._draggable = new L.Draggable(icon, icon, true);
12202                 }
12204                 this._draggable.on({
12205                         dragstart: this._onDragStart,
12206                         drag: this._onDrag,
12207                         dragend: this._onDragEnd
12208                 }, this).enable();
12210                 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
12211         },
12213         removeHooks: function () {
12214                 this._draggable.off({
12215                         dragstart: this._onDragStart,
12216                         drag: this._onDrag,
12217                         dragend: this._onDragEnd
12218                 }, this).disable();
12220                 if (this._marker._icon) {
12221                         L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
12222                 }
12223         },
12225         moved: function () {
12226                 return this._draggable && this._draggable._moved;
12227         },
12229         _onDragStart: function () {
12230                 // @section Dragging events
12231                 // @event dragstart: Event
12232                 // Fired when the user starts dragging the marker.
12234                 // @event movestart: Event
12235                 // Fired when the marker starts moving (because of dragging).
12237                 this._oldLatLng = this._marker.getLatLng();
12238                 this._marker
12239                     .closePopup()
12240                     .fire('movestart')
12241                     .fire('dragstart');
12242         },
12244         _onDrag: function (e) {
12245                 var marker = this._marker,
12246                     shadow = marker._shadow,
12247                     iconPos = L.DomUtil.getPosition(marker._icon),
12248                     latlng = marker._map.layerPointToLatLng(iconPos);
12250                 // update shadow position
12251                 if (shadow) {
12252                         L.DomUtil.setPosition(shadow, iconPos);
12253                 }
12255                 marker._latlng = latlng;
12256                 e.latlng = latlng;
12257                 e.oldLatLng = this._oldLatLng;
12259                 // @event drag: Event
12260                 // Fired repeatedly while the user drags the marker.
12261                 marker
12262                     .fire('move', e)
12263                     .fire('drag', e);
12264         },
12266         _onDragEnd: function (e) {
12267                 // @event dragend: DragEndEvent
12268                 // Fired when the user stops dragging the marker.
12270                 // @event moveend: Event
12271                 // Fired when the marker stops moving (because of dragging).
12272                 delete this._oldLatLng;
12273                 this._marker
12274                     .fire('moveend')
12275                     .fire('dragend', e);
12276         }
12282  * @class Control
12283  * @aka L.Control
12284  * @inherits Class
12286  * L.Control is a base class for implementing map controls. Handles positioning.
12287  * All other controls extend from this class.
12288  */
12290 L.Control = L.Class.extend({
12291         // @section
12292         // @aka Control options
12293         options: {
12294                 // @option position: String = 'topright'
12295                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
12296                 // `'topright'`, `'bottomleft'` or `'bottomright'`
12297                 position: 'topright'
12298         },
12300         initialize: function (options) {
12301                 L.setOptions(this, options);
12302         },
12304         /* @section
12305          * Classes extending L.Control will inherit the following methods:
12306          *
12307          * @method getPosition: string
12308          * Returns the position of the control.
12309          */
12310         getPosition: function () {
12311                 return this.options.position;
12312         },
12314         // @method setPosition(position: string): this
12315         // Sets the position of the control.
12316         setPosition: function (position) {
12317                 var map = this._map;
12319                 if (map) {
12320                         map.removeControl(this);
12321                 }
12323                 this.options.position = position;
12325                 if (map) {
12326                         map.addControl(this);
12327                 }
12329                 return this;
12330         },
12332         // @method getContainer: HTMLElement
12333         // Returns the HTMLElement that contains the control.
12334         getContainer: function () {
12335                 return this._container;
12336         },
12338         // @method addTo(map: Map): this
12339         // Adds the control to the given map.
12340         addTo: function (map) {
12341                 this.remove();
12342                 this._map = map;
12344                 var container = this._container = this.onAdd(map),
12345                     pos = this.getPosition(),
12346                     corner = map._controlCorners[pos];
12348                 L.DomUtil.addClass(container, 'leaflet-control');
12350                 if (pos.indexOf('bottom') !== -1) {
12351                         corner.insertBefore(container, corner.firstChild);
12352                 } else {
12353                         corner.appendChild(container);
12354                 }
12356                 return this;
12357         },
12359         // @method remove: this
12360         // Removes the control from the map it is currently active on.
12361         remove: function () {
12362                 if (!this._map) {
12363                         return this;
12364                 }
12366                 L.DomUtil.remove(this._container);
12368                 if (this.onRemove) {
12369                         this.onRemove(this._map);
12370                 }
12372                 this._map = null;
12374                 return this;
12375         },
12377         _refocusOnMap: function (e) {
12378                 // if map exists and event is not a keyboard event
12379                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
12380                         this._map.getContainer().focus();
12381                 }
12382         }
12385 L.control = function (options) {
12386         return new L.Control(options);
12389 /* @section Extension methods
12390  * @uninheritable
12392  * Every control should extend from `L.Control` and (re-)implement the following methods.
12394  * @method onAdd(map: Map): HTMLElement
12395  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
12397  * @method onRemove(map: Map)
12398  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
12399  */
12401 /* @namespace Map
12402  * @section Methods for Layers and Controls
12403  */
12404 L.Map.include({
12405         // @method addControl(control: Control): this
12406         // Adds the given control to the map
12407         addControl: function (control) {
12408                 control.addTo(this);
12409                 return this;
12410         },
12412         // @method removeControl(control: Control): this
12413         // Removes the given control from the map
12414         removeControl: function (control) {
12415                 control.remove();
12416                 return this;
12417         },
12419         _initControlPos: function () {
12420                 var corners = this._controlCorners = {},
12421                     l = 'leaflet-',
12422                     container = this._controlContainer =
12423                             L.DomUtil.create('div', l + 'control-container', this._container);
12425                 function createCorner(vSide, hSide) {
12426                         var className = l + vSide + ' ' + l + hSide;
12428                         corners[vSide + hSide] = L.DomUtil.create('div', className, container);
12429                 }
12431                 createCorner('top', 'left');
12432                 createCorner('top', 'right');
12433                 createCorner('bottom', 'left');
12434                 createCorner('bottom', 'right');
12435         },
12437         _clearControlPos: function () {
12438                 L.DomUtil.remove(this._controlContainer);
12439         }
12445  * @class Control.Zoom
12446  * @aka L.Control.Zoom
12447  * @inherits Control
12449  * 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`.
12450  */
12452 L.Control.Zoom = L.Control.extend({
12453         // @section
12454         // @aka Control.Zoom options
12455         options: {
12456                 position: 'topleft',
12458                 // @option zoomInText: String = '+'
12459                 // The text set on the 'zoom in' button.
12460                 zoomInText: '+',
12462                 // @option zoomInTitle: String = 'Zoom in'
12463                 // The title set on the 'zoom in' button.
12464                 zoomInTitle: 'Zoom in',
12466                 // @option zoomOutText: String = '-'
12467                 // The text set on the 'zoom out' button.
12468                 zoomOutText: '-',
12470                 // @option zoomOutTitle: String = 'Zoom out'
12471                 // The title set on the 'zoom out' button.
12472                 zoomOutTitle: 'Zoom out'
12473         },
12475         onAdd: function (map) {
12476                 var zoomName = 'leaflet-control-zoom',
12477                     container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
12478                     options = this.options;
12480                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
12481                         zoomName + '-in',  container, this._zoomIn);
12482                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
12483                         zoomName + '-out', container, this._zoomOut);
12485                 this._updateDisabled();
12486                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
12488                 return container;
12489         },
12491         onRemove: function (map) {
12492                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
12493         },
12495         disable: function () {
12496                 this._disabled = true;
12497                 this._updateDisabled();
12498                 return this;
12499         },
12501         enable: function () {
12502                 this._disabled = false;
12503                 this._updateDisabled();
12504                 return this;
12505         },
12507         _zoomIn: function (e) {
12508                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
12509                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12510                 }
12511         },
12513         _zoomOut: function (e) {
12514                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
12515                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12516                 }
12517         },
12519         _createButton: function (html, title, className, container, fn) {
12520                 var link = L.DomUtil.create('a', className, container);
12521                 link.innerHTML = html;
12522                 link.href = '#';
12523                 link.title = title;
12525                 /*
12526                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
12527                  */
12528                 link.setAttribute('role', 'button');
12529                 link.setAttribute('aria-label', title);
12531                 L.DomEvent
12532                     .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
12533                     .on(link, 'click', L.DomEvent.stop)
12534                     .on(link, 'click', fn, this)
12535                     .on(link, 'click', this._refocusOnMap, this);
12537                 return link;
12538         },
12540         _updateDisabled: function () {
12541                 var map = this._map,
12542                     className = 'leaflet-disabled';
12544                 L.DomUtil.removeClass(this._zoomInButton, className);
12545                 L.DomUtil.removeClass(this._zoomOutButton, className);
12547                 if (this._disabled || map._zoom === map.getMinZoom()) {
12548                         L.DomUtil.addClass(this._zoomOutButton, className);
12549                 }
12550                 if (this._disabled || map._zoom === map.getMaxZoom()) {
12551                         L.DomUtil.addClass(this._zoomInButton, className);
12552                 }
12553         }
12556 // @namespace Map
12557 // @section Control options
12558 // @option zoomControl: Boolean = true
12559 // Whether a [zoom control](#control-zoom) is added to the map by default.
12560 L.Map.mergeOptions({
12561         zoomControl: true
12564 L.Map.addInitHook(function () {
12565         if (this.options.zoomControl) {
12566                 this.zoomControl = new L.Control.Zoom();
12567                 this.addControl(this.zoomControl);
12568         }
12571 // @namespace Control.Zoom
12572 // @factory L.control.zoom(options: Control.Zoom options)
12573 // Creates a zoom control
12574 L.control.zoom = function (options) {
12575         return new L.Control.Zoom(options);
12581  * @class Control.Attribution
12582  * @aka L.Control.Attribution
12583  * @inherits Control
12585  * 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.
12586  */
12588 L.Control.Attribution = L.Control.extend({
12589         // @section
12590         // @aka Control.Attribution options
12591         options: {
12592                 position: 'bottomright',
12594                 // @option prefix: String = 'Leaflet'
12595                 // The HTML text shown before the attributions. Pass `false` to disable.
12596                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
12597         },
12599         initialize: function (options) {
12600                 L.setOptions(this, options);
12602                 this._attributions = {};
12603         },
12605         onAdd: function (map) {
12606                 map.attributionControl = this;
12607                 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
12608                 if (L.DomEvent) {
12609                         L.DomEvent.disableClickPropagation(this._container);
12610                 }
12612                 // TODO ugly, refactor
12613                 for (var i in map._layers) {
12614                         if (map._layers[i].getAttribution) {
12615                                 this.addAttribution(map._layers[i].getAttribution());
12616                         }
12617                 }
12619                 this._update();
12621                 return this._container;
12622         },
12624         // @method setPrefix(prefix: String): this
12625         // Sets the text before the attributions.
12626         setPrefix: function (prefix) {
12627                 this.options.prefix = prefix;
12628                 this._update();
12629                 return this;
12630         },
12632         // @method addAttribution(text: String): this
12633         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
12634         addAttribution: function (text) {
12635                 if (!text) { return this; }
12637                 if (!this._attributions[text]) {
12638                         this._attributions[text] = 0;
12639                 }
12640                 this._attributions[text]++;
12642                 this._update();
12644                 return this;
12645         },
12647         // @method removeAttribution(text: String): this
12648         // Removes an attribution text.
12649         removeAttribution: function (text) {
12650                 if (!text) { return this; }
12652                 if (this._attributions[text]) {
12653                         this._attributions[text]--;
12654                         this._update();
12655                 }
12657                 return this;
12658         },
12660         _update: function () {
12661                 if (!this._map) { return; }
12663                 var attribs = [];
12665                 for (var i in this._attributions) {
12666                         if (this._attributions[i]) {
12667                                 attribs.push(i);
12668                         }
12669                 }
12671                 var prefixAndAttribs = [];
12673                 if (this.options.prefix) {
12674                         prefixAndAttribs.push(this.options.prefix);
12675                 }
12676                 if (attribs.length) {
12677                         prefixAndAttribs.push(attribs.join(', '));
12678                 }
12680                 this._container.innerHTML = prefixAndAttribs.join(' | ');
12681         }
12684 // @namespace Map
12685 // @section Control options
12686 // @option attributionControl: Boolean = true
12687 // Whether a [attribution control](#control-attribution) is added to the map by default.
12688 L.Map.mergeOptions({
12689         attributionControl: true
12692 L.Map.addInitHook(function () {
12693         if (this.options.attributionControl) {
12694                 new L.Control.Attribution().addTo(this);
12695         }
12698 // @namespace Control.Attribution
12699 // @factory L.control.attribution(options: Control.Attribution options)
12700 // Creates an attribution control.
12701 L.control.attribution = function (options) {
12702         return new L.Control.Attribution(options);
12708  * @class Control.Scale
12709  * @aka L.Control.Scale
12710  * @inherits Control
12712  * 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`.
12714  * @example
12716  * ```js
12717  * L.control.scale().addTo(map);
12718  * ```
12719  */
12721 L.Control.Scale = L.Control.extend({
12722         // @section
12723         // @aka Control.Scale options
12724         options: {
12725                 position: 'bottomleft',
12727                 // @option maxWidth: Number = 100
12728                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
12729                 maxWidth: 100,
12731                 // @option metric: Boolean = True
12732                 // Whether to show the metric scale line (m/km).
12733                 metric: true,
12735                 // @option imperial: Boolean = True
12736                 // Whether to show the imperial scale line (mi/ft).
12737                 imperial: true
12739                 // @option updateWhenIdle: Boolean = false
12740                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12741         },
12743         onAdd: function (map) {
12744                 var className = 'leaflet-control-scale',
12745                     container = L.DomUtil.create('div', className),
12746                     options = this.options;
12748                 this._addScales(options, className + '-line', container);
12750                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12751                 map.whenReady(this._update, this);
12753                 return container;
12754         },
12756         onRemove: function (map) {
12757                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12758         },
12760         _addScales: function (options, className, container) {
12761                 if (options.metric) {
12762                         this._mScale = L.DomUtil.create('div', className, container);
12763                 }
12764                 if (options.imperial) {
12765                         this._iScale = L.DomUtil.create('div', className, container);
12766                 }
12767         },
12769         _update: function () {
12770                 var map = this._map,
12771                     y = map.getSize().y / 2;
12773                 var maxMeters = map.distance(
12774                                 map.containerPointToLatLng([0, y]),
12775                                 map.containerPointToLatLng([this.options.maxWidth, y]));
12777                 this._updateScales(maxMeters);
12778         },
12780         _updateScales: function (maxMeters) {
12781                 if (this.options.metric && maxMeters) {
12782                         this._updateMetric(maxMeters);
12783                 }
12784                 if (this.options.imperial && maxMeters) {
12785                         this._updateImperial(maxMeters);
12786                 }
12787         },
12789         _updateMetric: function (maxMeters) {
12790                 var meters = this._getRoundNum(maxMeters),
12791                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12793                 this._updateScale(this._mScale, label, meters / maxMeters);
12794         },
12796         _updateImperial: function (maxMeters) {
12797                 var maxFeet = maxMeters * 3.2808399,
12798                     maxMiles, miles, feet;
12800                 if (maxFeet > 5280) {
12801                         maxMiles = maxFeet / 5280;
12802                         miles = this._getRoundNum(maxMiles);
12803                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12805                 } else {
12806                         feet = this._getRoundNum(maxFeet);
12807                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12808                 }
12809         },
12811         _updateScale: function (scale, text, ratio) {
12812                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12813                 scale.innerHTML = text;
12814         },
12816         _getRoundNum: function (num) {
12817                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12818                     d = num / pow10;
12820                 d = d >= 10 ? 10 :
12821                     d >= 5 ? 5 :
12822                     d >= 3 ? 3 :
12823                     d >= 2 ? 2 : 1;
12825                 return pow10 * d;
12826         }
12830 // @factory L.control.scale(options?: Control.Scale options)
12831 // Creates an scale control with the given options.
12832 L.control.scale = function (options) {
12833         return new L.Control.Scale(options);
12839  * @class Control.Layers
12840  * @aka L.Control.Layers
12841  * @inherits Control
12843  * 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`.
12845  * @example
12847  * ```js
12848  * var baseLayers = {
12849  *      "Mapbox": mapbox,
12850  *      "OpenStreetMap": osm
12851  * };
12853  * var overlays = {
12854  *      "Marker": marker,
12855  *      "Roads": roadsLayer
12856  * };
12858  * L.control.layers(baseLayers, overlays).addTo(map);
12859  * ```
12861  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12863  * ```js
12864  * {
12865  *     "<someName1>": layer1,
12866  *     "<someName2>": layer2
12867  * }
12868  * ```
12870  * The layer names can contain HTML, which allows you to add additional styling to the items:
12872  * ```js
12873  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12874  * ```
12875  */
12878 L.Control.Layers = L.Control.extend({
12879         // @section
12880         // @aka Control.Layers options
12881         options: {
12882                 // @option collapsed: Boolean = true
12883                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12884                 collapsed: true,
12885                 position: 'topright',
12887                 // @option autoZIndex: Boolean = true
12888                 // 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.
12889                 autoZIndex: true,
12891                 // @option hideSingleBase: Boolean = false
12892                 // If `true`, the base layers in the control will be hidden when there is only one.
12893                 hideSingleBase: false,
12895                 // @option sortLayers: Boolean = false
12896                 // Whether to sort the layers. When `false`, layers will keep the order
12897                 // in which they were added to the control.
12898                 sortLayers: false,
12900                 // @option sortFunction: Function = *
12901                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
12902                 // that will be used for sorting the layers, when `sortLayers` is `true`.
12903                 // The function receives both the `L.Layer` instances and their names, as in
12904                 // `sortFunction(layerA, layerB, nameA, nameB)`.
12905                 // By default, it sorts layers alphabetically by their name.
12906                 sortFunction: function (layerA, layerB, nameA, nameB) {
12907                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
12908                 }
12909         },
12911         initialize: function (baseLayers, overlays, options) {
12912                 L.setOptions(this, options);
12914                 this._layers = [];
12915                 this._lastZIndex = 0;
12916                 this._handlingClick = false;
12918                 for (var i in baseLayers) {
12919                         this._addLayer(baseLayers[i], i);
12920                 }
12922                 for (i in overlays) {
12923                         this._addLayer(overlays[i], i, true);
12924                 }
12925         },
12927         onAdd: function (map) {
12928                 this._initLayout();
12929                 this._update();
12931                 this._map = map;
12932                 map.on('zoomend', this._checkDisabledLayers, this);
12934                 return this._container;
12935         },
12937         onRemove: function () {
12938                 this._map.off('zoomend', this._checkDisabledLayers, this);
12940                 for (var i = 0; i < this._layers.length; i++) {
12941                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
12942                 }
12943         },
12945         // @method addBaseLayer(layer: Layer, name: String): this
12946         // Adds a base layer (radio button entry) with the given name to the control.
12947         addBaseLayer: function (layer, name) {
12948                 this._addLayer(layer, name);
12949                 return (this._map) ? this._update() : this;
12950         },
12952         // @method addOverlay(layer: Layer, name: String): this
12953         // Adds an overlay (checkbox entry) with the given name to the control.
12954         addOverlay: function (layer, name) {
12955                 this._addLayer(layer, name, true);
12956                 return (this._map) ? this._update() : this;
12957         },
12959         // @method removeLayer(layer: Layer): this
12960         // Remove the given layer from the control.
12961         removeLayer: function (layer) {
12962                 layer.off('add remove', this._onLayerChange, this);
12964                 var obj = this._getLayer(L.stamp(layer));
12965                 if (obj) {
12966                         this._layers.splice(this._layers.indexOf(obj), 1);
12967                 }
12968                 return (this._map) ? this._update() : this;
12969         },
12971         // @method expand(): this
12972         // Expand the control container if collapsed.
12973         expand: function () {
12974                 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12975                 this._form.style.height = null;
12976                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12977                 if (acceptableHeight < this._form.clientHeight) {
12978                         L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12979                         this._form.style.height = acceptableHeight + 'px';
12980                 } else {
12981                         L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12982                 }
12983                 this._checkDisabledLayers();
12984                 return this;
12985         },
12987         // @method collapse(): this
12988         // Collapse the control container if expanded.
12989         collapse: function () {
12990                 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12991                 return this;
12992         },
12994         _initLayout: function () {
12995                 var className = 'leaflet-control-layers',
12996                     container = this._container = L.DomUtil.create('div', className),
12997                     collapsed = this.options.collapsed;
12999                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
13000                 container.setAttribute('aria-haspopup', true);
13002                 L.DomEvent.disableClickPropagation(container);
13003                 if (!L.Browser.touch) {
13004                         L.DomEvent.disableScrollPropagation(container);
13005                 }
13007                 var form = this._form = L.DomUtil.create('form', className + '-list');
13009                 if (collapsed) {
13010                         this._map.on('click', this.collapse, this);
13012                         if (!L.Browser.android) {
13013                                 L.DomEvent.on(container, {
13014                                         mouseenter: this.expand,
13015                                         mouseleave: this.collapse
13016                                 }, this);
13017                         }
13018                 }
13020                 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
13021                 link.href = '#';
13022                 link.title = 'Layers';
13024                 if (L.Browser.touch) {
13025                         L.DomEvent
13026                             .on(link, 'click', L.DomEvent.stop)
13027                             .on(link, 'click', this.expand, this);
13028                 } else {
13029                         L.DomEvent.on(link, 'focus', this.expand, this);
13030                 }
13032                 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
13033                 L.DomEvent.on(form, 'click', function () {
13034                         setTimeout(L.bind(this._onInputClick, this), 0);
13035                 }, this);
13037                 // TODO keyboard accessibility
13039                 if (!collapsed) {
13040                         this.expand();
13041                 }
13043                 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
13044                 this._separator = L.DomUtil.create('div', className + '-separator', form);
13045                 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
13047                 container.appendChild(form);
13048         },
13050         _getLayer: function (id) {
13051                 for (var i = 0; i < this._layers.length; i++) {
13053                         if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
13054                                 return this._layers[i];
13055                         }
13056                 }
13057         },
13059         _addLayer: function (layer, name, overlay) {
13060                 layer.on('add remove', this._onLayerChange, this);
13062                 this._layers.push({
13063                         layer: layer,
13064                         name: name,
13065                         overlay: overlay
13066                 });
13068                 if (this.options.sortLayers) {
13069                         this._layers.sort(L.bind(function (a, b) {
13070                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
13071                         }, this));
13072                 }
13074                 if (this.options.autoZIndex && layer.setZIndex) {
13075                         this._lastZIndex++;
13076                         layer.setZIndex(this._lastZIndex);
13077                 }
13078         },
13080         _update: function () {
13081                 if (!this._container) { return this; }
13083                 L.DomUtil.empty(this._baseLayersList);
13084                 L.DomUtil.empty(this._overlaysList);
13086                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
13088                 for (i = 0; i < this._layers.length; i++) {
13089                         obj = this._layers[i];
13090                         this._addItem(obj);
13091                         overlaysPresent = overlaysPresent || obj.overlay;
13092                         baseLayersPresent = baseLayersPresent || !obj.overlay;
13093                         baseLayersCount += !obj.overlay ? 1 : 0;
13094                 }
13096                 // Hide base layers section if there's only one layer.
13097                 if (this.options.hideSingleBase) {
13098                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
13099                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
13100                 }
13102                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
13104                 return this;
13105         },
13107         _onLayerChange: function (e) {
13108                 if (!this._handlingClick) {
13109                         this._update();
13110                 }
13112                 var obj = this._getLayer(L.stamp(e.target));
13114                 // @namespace Map
13115                 // @section Layer events
13116                 // @event baselayerchange: LayersControlEvent
13117                 // Fired when the base layer is changed through the [layer control](#control-layers).
13118                 // @event overlayadd: LayersControlEvent
13119                 // Fired when an overlay is selected through the [layer control](#control-layers).
13120                 // @event overlayremove: LayersControlEvent
13121                 // Fired when an overlay is deselected through the [layer control](#control-layers).
13122                 // @namespace Control.Layers
13123                 var type = obj.overlay ?
13124                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
13125                         (e.type === 'add' ? 'baselayerchange' : null);
13127                 if (type) {
13128                         this._map.fire(type, obj);
13129                 }
13130         },
13132         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
13133         _createRadioElement: function (name, checked) {
13135                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
13136                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
13138                 var radioFragment = document.createElement('div');
13139                 radioFragment.innerHTML = radioHtml;
13141                 return radioFragment.firstChild;
13142         },
13144         _addItem: function (obj) {
13145                 var label = document.createElement('label'),
13146                     checked = this._map.hasLayer(obj.layer),
13147                     input;
13149                 if (obj.overlay) {
13150                         input = document.createElement('input');
13151                         input.type = 'checkbox';
13152                         input.className = 'leaflet-control-layers-selector';
13153                         input.defaultChecked = checked;
13154                 } else {
13155                         input = this._createRadioElement('leaflet-base-layers', checked);
13156                 }
13158                 input.layerId = L.stamp(obj.layer);
13160                 L.DomEvent.on(input, 'click', this._onInputClick, this);
13162                 var name = document.createElement('span');
13163                 name.innerHTML = ' ' + obj.name;
13165                 // Helps from preventing layer control flicker when checkboxes are disabled
13166                 // https://github.com/Leaflet/Leaflet/issues/2771
13167                 var holder = document.createElement('div');
13169                 label.appendChild(holder);
13170                 holder.appendChild(input);
13171                 holder.appendChild(name);
13173                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
13174                 container.appendChild(label);
13176                 this._checkDisabledLayers();
13177                 return label;
13178         },
13180         _onInputClick: function () {
13181                 var inputs = this._form.getElementsByTagName('input'),
13182                     input, layer, hasLayer;
13183                 var addedLayers = [],
13184                     removedLayers = [];
13186                 this._handlingClick = true;
13188                 for (var i = inputs.length - 1; i >= 0; i--) {
13189                         input = inputs[i];
13190                         layer = this._getLayer(input.layerId).layer;
13191                         hasLayer = this._map.hasLayer(layer);
13193                         if (input.checked && !hasLayer) {
13194                                 addedLayers.push(layer);
13196                         } else if (!input.checked && hasLayer) {
13197                                 removedLayers.push(layer);
13198                         }
13199                 }
13201                 // Bugfix issue 2318: Should remove all old layers before readding new ones
13202                 for (i = 0; i < removedLayers.length; i++) {
13203                         this._map.removeLayer(removedLayers[i]);
13204                 }
13205                 for (i = 0; i < addedLayers.length; i++) {
13206                         this._map.addLayer(addedLayers[i]);
13207                 }
13209                 this._handlingClick = false;
13211                 this._refocusOnMap();
13212         },
13214         _checkDisabledLayers: function () {
13215                 var inputs = this._form.getElementsByTagName('input'),
13216                     input,
13217                     layer,
13218                     zoom = this._map.getZoom();
13220                 for (var i = inputs.length - 1; i >= 0; i--) {
13221                         input = inputs[i];
13222                         layer = this._getLayer(input.layerId).layer;
13223                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
13224                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
13226                 }
13227         },
13229         _expand: function () {
13230                 // Backward compatibility, remove me in 1.1.
13231                 return this.expand();
13232         },
13234         _collapse: function () {
13235                 // Backward compatibility, remove me in 1.1.
13236                 return this.collapse();
13237         }
13242 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
13243 // 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.
13244 L.control.layers = function (baseLayers, overlays, options) {
13245         return new L.Control.Layers(baseLayers, overlays, options);
13250 }(window, document));
13251 //# sourceMappingURL=leaflet-src.map