]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
045b3071906a94e5d67862b986ebebf17e48392c
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /*
2  Leaflet 1.0.0, 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.0"
8 };
9
10 function expose() {
11         var oldL = window.L;
12
13         L.noConflict = function () {
14                 window.L = oldL;
15                 return this;
16         };
17
18         window.L = L;
19 }
20
21 // define Leaflet for Node module pattern loaders, including Browserify
22 if (typeof module === 'object' && typeof module.exports === 'object') {
23         module.exports = L;
24
25 // define Leaflet as an AMD module
26 } else if (typeof define === 'function' && define.amd) {
27         define(L);
28 }
29
30 // define Leaflet as a global L variable, saving the original L to restore later if needed
31 if (typeof window !== 'undefined') {
32         expose();
33 }
34
35
36
37 /*
38  * @namespace Util
39  *
40  * Various utility functions, used by Leaflet internally.
41  */
42
43 L.Util = {
44
45         // @function extend(dest: Object, src?: Object): Object
46         // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
47         extend: function (dest) {
48                 var i, j, len, src;
49
50                 for (j = 1, len = arguments.length; j < len; j++) {
51                         src = arguments[j];
52                         for (i in src) {
53                                 dest[i] = src[i];
54                         }
55                 }
56                 return dest;
57         },
58
59         // @function create(proto: Object, properties?: Object): Object
60         // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
61         create: Object.create || (function () {
62                 function F() {}
63                 return function (proto) {
64                         F.prototype = proto;
65                         return new F();
66                 };
67         })(),
68
69         // @function bind(fn: Function, …): Function
70         // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
71         // Has a `L.bind()` shortcut.
72         bind: function (fn, obj) {
73                 var slice = Array.prototype.slice;
74
75                 if (fn.bind) {
76                         return fn.bind.apply(fn, slice.call(arguments, 1));
77                 }
78
79                 var args = slice.call(arguments, 2);
80
81                 return function () {
82                         return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
83                 };
84         },
85
86         // @function stamp(obj: Object): Number
87         // Returns the unique ID of an object, assiging it one if it doesn't have it.
88         stamp: function (obj) {
89                 /*eslint-disable */
90                 obj._leaflet_id = obj._leaflet_id || ++L.Util.lastId;
91                 return obj._leaflet_id;
92                 /*eslint-enable */
93         },
94
95         // @property lastId: Number
96         // Last unique ID used by [`stamp()`](#util-stamp)
97         lastId: 0,
98
99         // @function throttle(fn: Function, time: Number, context: Object): Function
100         // Returns a function which executes function `fn` with the given scope `context`
101         // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
102         // `fn` will be called no more than one time per given amount of `time`. The arguments
103         // received by the bound function will be any arguments passed when binding the
104         // function, followed by any arguments passed when invoking the bound function.
105         // Has an `L.bind` shortcut.
106         throttle: function (fn, time, context) {
107                 var lock, args, wrapperFn, later;
108
109                 later = function () {
110                         // reset lock and call if queued
111                         lock = false;
112                         if (args) {
113                                 wrapperFn.apply(context, args);
114                                 args = false;
115                         }
116                 };
117
118                 wrapperFn = function () {
119                         if (lock) {
120                                 // called too soon, queue to call later
121                                 args = arguments;
122
123                         } else {
124                                 // call and lock until later
125                                 fn.apply(context, arguments);
126                                 setTimeout(later, time);
127                                 lock = true;
128                         }
129                 };
130
131                 return wrapperFn;
132         },
133
134         // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
135         // Returns the number `num` modulo `range` in such a way so it lies within
136         // `range[0]` and `range[1]`. The returned value will be always smaller than
137         // `range[1]` unless `includeMax` is set to `true`.
138         wrapNum: function (x, range, includeMax) {
139                 var max = range[1],
140                     min = range[0],
141                     d = max - min;
142                 return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
143         },
144
145         // @function falseFn(): Function
146         // Returns a function which always returns `false`.
147         falseFn: function () { return false; },
148
149         // @function formatNum(num: Number, digits?: Number): Number
150         // Returns the number `num` rounded to `digits` decimals, or to 5 decimals by default.
151         formatNum: function (num, digits) {
152                 var pow = Math.pow(10, digits || 5);
153                 return Math.round(num * pow) / pow;
154         },
155
156         // @function trim(str: String): String
157         // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
158         trim: function (str) {
159                 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
160         },
161
162         // @function splitWords(str: String): String[]
163         // Trims and splits the string on whitespace and returns the array of parts.
164         splitWords: function (str) {
165                 return L.Util.trim(str).split(/\s+/);
166         },
167
168         // @function setOptions(obj: Object, options: Object): Object
169         // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
170         setOptions: function (obj, options) {
171                 if (!obj.hasOwnProperty('options')) {
172                         obj.options = obj.options ? L.Util.create(obj.options) : {};
173                 }
174                 for (var i in options) {
175                         obj.options[i] = options[i];
176                 }
177                 return obj.options;
178         },
179
180         // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
181         // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
182         // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
183         // be appended at the end. If `uppercase` is `true`, the parameter names will
184         // be uppercased (e.g. `'?A=foo&B=bar'`)
185         getParamString: function (obj, existingUrl, uppercase) {
186                 var params = [];
187                 for (var i in obj) {
188                         params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
189                 }
190                 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
191         },
192
193         // @function template(str: String, data: Object): String
194         // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
195         // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
196         // `('Hello foo, bar')`. You can also specify functions instead of strings for
197         // data values — they will be evaluated passing `data` as an argument.
198         template: function (str, data) {
199                 return str.replace(L.Util.templateRe, function (str, key) {
200                         var value = data[key];
201
202                         if (value === undefined) {
203                                 throw new Error('No value provided for variable ' + str);
204
205                         } else if (typeof value === 'function') {
206                                 value = value(data);
207                         }
208                         return value;
209                 });
210         },
211
212         templateRe: /\{ *([\w_\-]+) *\}/g,
213
214         // @function isArray(obj): Boolean
215         // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
216         isArray: Array.isArray || function (obj) {
217                 return (Object.prototype.toString.call(obj) === '[object Array]');
218         },
219
220         // @function indexOf(array: Array, el: Object): Number
221         // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
222         indexOf: function (array, el) {
223                 for (var i = 0; i < array.length; i++) {
224                         if (array[i] === el) { return i; }
225                 }
226                 return -1;
227         },
228
229         // @property emptyImageUrl: String
230         // Data URI string containing a base64-encoded empty GIF image.
231         // Used as a hack to free memory from unused images on WebKit-powered
232         // mobile devices (by setting image `src` to this string).
233         emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
234 };
235
236 (function () {
237         // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
238
239         function getPrefixed(name) {
240                 return window['webkit' + name] || window['moz' + name] || window['ms' + name];
241         }
242
243         var lastTime = 0;
244
245         // fallback for IE 7-8
246         function timeoutDefer(fn) {
247                 var time = +new Date(),
248                     timeToCall = Math.max(0, 16 - (time - lastTime));
249
250                 lastTime = time + timeToCall;
251                 return window.setTimeout(fn, timeToCall);
252         }
253
254         var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer,
255             cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
256                        getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };
257
258
259         // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
260         // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
261         // `context` if given. When `immediate` is set, `fn` is called immediately if
262         // the browser doesn't have native support for
263         // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
264         // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
265         L.Util.requestAnimFrame = function (fn, context, immediate) {
266                 if (immediate && requestFn === timeoutDefer) {
267                         fn.call(context);
268                 } else {
269                         return requestFn.call(window, L.bind(fn, context));
270                 }
271         };
272
273         // @function cancelAnimFrame(id: Number): undefined
274         // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
275         L.Util.cancelAnimFrame = function (id) {
276                 if (id) {
277                         cancelFn.call(window, id);
278                 }
279         };
280 })();
281
282 // shortcuts for most used utility functions
283 L.extend = L.Util.extend;
284 L.bind = L.Util.bind;
285 L.stamp = L.Util.stamp;
286 L.setOptions = L.Util.setOptions;
287
288
289
290
291 // @class Class
292 // @aka L.Class
293
294 // @section
295 // @uninheritable
296
297 // Thanks to John Resig and Dean Edwards for inspiration!
298
299 L.Class = function () {};
300
301 L.Class.extend = function (props) {
302
303         // @function extend(props: Object): Function
304         // [Extends the current class](#class-inheritance) given the properties to be included.
305         // Returns a Javascript function that is a class constructor (to be called with `new`).
306         var NewClass = function () {
307
308                 // call the constructor
309                 if (this.initialize) {
310                         this.initialize.apply(this, arguments);
311                 }
312
313                 // call all constructor hooks
314                 this.callInitHooks();
315         };
316
317         var parentProto = NewClass.__super__ = this.prototype;
318
319         var proto = L.Util.create(parentProto);
320         proto.constructor = NewClass;
321
322         NewClass.prototype = proto;
323
324         // inherit parent's statics
325         for (var i in this) {
326                 if (this.hasOwnProperty(i) && i !== 'prototype') {
327                         NewClass[i] = this[i];
328                 }
329         }
330
331         // mix static properties into the class
332         if (props.statics) {
333                 L.extend(NewClass, props.statics);
334                 delete props.statics;
335         }
336
337         // mix includes into the prototype
338         if (props.includes) {
339                 L.Util.extend.apply(null, [proto].concat(props.includes));
340                 delete props.includes;
341         }
342
343         // merge options
344         if (proto.options) {
345                 props.options = L.Util.extend(L.Util.create(proto.options), props.options);
346         }
347
348         // mix given properties into the prototype
349         L.extend(proto, props);
350
351         proto._initHooks = [];
352
353         // add method for calling all hooks
354         proto.callInitHooks = function () {
355
356                 if (this._initHooksCalled) { return; }
357
358                 if (parentProto.callInitHooks) {
359                         parentProto.callInitHooks.call(this);
360                 }
361
362                 this._initHooksCalled = true;
363
364                 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
365                         proto._initHooks[i].call(this);
366                 }
367         };
368
369         return NewClass;
370 };
371
372
373 // @function include(properties: Object): this
374 // [Includes a mixin](#class-includes) into the current class.
375 L.Class.include = function (props) {
376         L.extend(this.prototype, props);
377         return this;
378 };
379
380 // @function mergeOptions(options: Object): this
381 // [Merges `options`](#class-options) into the defaults of the class.
382 L.Class.mergeOptions = function (options) {
383         L.extend(this.prototype.options, options);
384         return this;
385 };
386
387 // @function addInitHook(fn: Function): this
388 // Adds a [constructor hook](#class-constructor-hooks) to the class.
389 L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
390         var args = Array.prototype.slice.call(arguments, 1);
391
392         var init = typeof fn === 'function' ? fn : function () {
393                 this[fn].apply(this, args);
394         };
395
396         this.prototype._initHooks = this.prototype._initHooks || [];
397         this.prototype._initHooks.push(init);
398         return this;
399 };
400
401
402
403 /*
404  * @class Evented
405  * @aka L.Evented
406  * @inherits Class
407  *
408  * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
409  *
410  * @example
411  *
412  * ```js
413  * map.on('click', function(e) {
414  *      alert(e.latlng);
415  * } );
416  * ```
417  *
418  * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
419  *
420  * ```js
421  * function onClick(e) { ... }
422  *
423  * map.on('click', onClick);
424  * map.off('click', onClick);
425  * ```
426  */
427
428
429 L.Evented = L.Class.extend({
430
431         /* @method on(type: String, fn: Function, context?: Object): this
432          * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
433          *
434          * @alternative
435          * @method on(eventMap: Object): this
436          * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
437          */
438         on: function (types, fn, context) {
439
440                 // types can be a map of types/handlers
441                 if (typeof types === 'object') {
442                         for (var type in types) {
443                                 // we don't process space-separated events here for performance;
444                                 // it's a hot path since Layer uses the on(obj) syntax
445                                 this._on(type, types[type], fn);
446                         }
447
448                 } else {
449                         // types can be a string of space-separated words
450                         types = L.Util.splitWords(types);
451
452                         for (var i = 0, len = types.length; i < len; i++) {
453                                 this._on(types[i], fn, context);
454                         }
455                 }
456
457                 return this;
458         },
459
460         /* @method off(type: String, fn?: Function, context?: Object): this
461          * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
462          *
463          * @alternative
464          * @method off(eventMap: Object): this
465          * Removes a set of type/listener pairs.
466          *
467          * @alternative
468          * @method off: this
469          * Removes all listeners to all events on the object.
470          */
471         off: function (types, fn, context) {
472
473                 if (!types) {
474                         // clear all listeners if called without arguments
475                         delete this._events;
476
477                 } else if (typeof types === 'object') {
478                         for (var type in types) {
479                                 this._off(type, types[type], fn);
480                         }
481
482                 } else {
483                         types = L.Util.splitWords(types);
484
485                         for (var i = 0, len = types.length; i < len; i++) {
486                                 this._off(types[i], fn, context);
487                         }
488                 }
489
490                 return this;
491         },
492
493         // attach listener (without syntactic sugar now)
494         _on: function (type, fn, context) {
495                 this._events = this._events || {};
496
497                 /* get/init listeners for type */
498                 var typeListeners = this._events[type];
499                 if (!typeListeners) {
500                         typeListeners = [];
501                         this._events[type] = typeListeners;
502                 }
503
504                 if (context === this) {
505                         // Less memory footprint.
506                         context = undefined;
507                 }
508                 var newListener = {fn: fn, ctx: context},
509                     listeners = typeListeners;
510
511                 // check if fn already there
512                 for (var i = 0, len = listeners.length; i < len; i++) {
513                         if (listeners[i].fn === fn && listeners[i].ctx === context) {
514                                 return;
515                         }
516                 }
517
518                 listeners.push(newListener);
519                 typeListeners.count++;
520         },
521
522         _off: function (type, fn, context) {
523                 var listeners,
524                     i,
525                     len;
526
527                 if (!this._events) { return; }
528
529                 listeners = this._events[type];
530
531                 if (!listeners) {
532                         return;
533                 }
534
535                 if (!fn) {
536                         // Set all removed listeners to noop so they are not called if remove happens in fire
537                         for (i = 0, len = listeners.length; i < len; i++) {
538                                 listeners[i].fn = L.Util.falseFn;
539                         }
540                         // clear all listeners for a type if function isn't specified
541                         delete this._events[type];
542                         return;
543                 }
544
545                 if (context === this) {
546                         context = undefined;
547                 }
548
549                 if (listeners) {
550
551                         // find fn and remove it
552                         for (i = 0, len = listeners.length; i < len; i++) {
553                                 var l = listeners[i];
554                                 if (l.ctx !== context) { continue; }
555                                 if (l.fn === fn) {
556
557                                         // set the removed listener to noop so that's not called if remove happens in fire
558                                         l.fn = L.Util.falseFn;
559
560                                         if (this._firingCount) {
561                                                 /* copy array in case events are being fired */
562                                                 this._events[type] = listeners = listeners.slice();
563                                         }
564                                         listeners.splice(i, 1);
565
566                                         return;
567                                 }
568                         }
569                 }
570         },
571
572         // @method fire(type: String, data?: Object, propagate?: Boolean): this
573         // Fires an event of the specified type. You can optionally provide an data
574         // object — the first argument of the listener function will contain its
575         // properties. The event might can optionally be propagated to event parents.
576         fire: function (type, data, propagate) {
577                 if (!this.listens(type, propagate)) { return this; }
578
579                 var event = L.Util.extend({}, data, {type: type, target: this});
580
581                 if (this._events) {
582                         var listeners = this._events[type];
583
584                         if (listeners) {
585                                 this._firingCount = (this._firingCount + 1) || 1;
586                                 for (var i = 0, len = listeners.length; i < len; i++) {
587                                         var l = listeners[i];
588                                         l.fn.call(l.ctx || this, event);
589                                 }
590
591                                 this._firingCount--;
592                         }
593                 }
594
595                 if (propagate) {
596                         // propagate the event to parents (set with addEventParent)
597                         this._propagateEvent(event);
598                 }
599
600                 return this;
601         },
602
603         // @method listens(type: String): Boolean
604         // Returns `true` if a particular event type has any listeners attached to it.
605         listens: function (type, propagate) {
606                 var listeners = this._events && this._events[type];
607                 if (listeners && listeners.length) { return true; }
608
609                 if (propagate) {
610                         // also check parents for listeners if event propagates
611                         for (var id in this._eventParents) {
612                                 if (this._eventParents[id].listens(type, propagate)) { return true; }
613                         }
614                 }
615                 return false;
616         },
617
618         // @method once(…): this
619         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
620         once: function (types, fn, context) {
621
622                 if (typeof types === 'object') {
623                         for (var type in types) {
624                                 this.once(type, types[type], fn);
625                         }
626                         return this;
627                 }
628
629                 var handler = L.bind(function () {
630                         this
631                             .off(types, fn, context)
632                             .off(types, handler, context);
633                 }, this);
634
635                 // add a listener that's executed once and removed after that
636                 return this
637                     .on(types, fn, context)
638                     .on(types, handler, context);
639         },
640
641         // @method addEventParent(obj: Evented): this
642         // Adds an event parent - an `Evented` that will receive propagated events
643         addEventParent: function (obj) {
644                 this._eventParents = this._eventParents || {};
645                 this._eventParents[L.stamp(obj)] = obj;
646                 return this;
647         },
648
649         // @method removeEventParent(obj: Evented): this
650         // Removes an event parent, so it will stop receiving propagated events
651         removeEventParent: function (obj) {
652                 if (this._eventParents) {
653                         delete this._eventParents[L.stamp(obj)];
654                 }
655                 return this;
656         },
657
658         _propagateEvent: function (e) {
659                 for (var id in this._eventParents) {
660                         this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
661                 }
662         }
663 });
664
665 var proto = L.Evented.prototype;
666
667 // aliases; we should ditch those eventually
668
669 // @method addEventListener(…): this
670 // Alias to [`on(…)`](#evented-on)
671 proto.addEventListener = proto.on;
672
673 // @method removeEventListener(…): this
674 // Alias to [`off(…)`](#evented-off)
675
676 // @method clearAllEventListeners(…): this
677 // Alias to [`off()`](#evented-off)
678 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
679
680 // @method addOneTimeEventListener(…): this
681 // Alias to [`once(…)`](#evented-once)
682 proto.addOneTimeEventListener = proto.once;
683
684 // @method fireEvent(…): this
685 // Alias to [`fire(…)`](#evented-fire)
686 proto.fireEvent = proto.fire;
687
688 // @method hasEventListeners(…): Boolean
689 // Alias to [`listens(…)`](#evented-listens)
690 proto.hasEventListeners = proto.listens;
691
692 L.Mixin = {Events: proto};
693
694
695
696 /*
697  * @namespace Browser
698  * @aka L.Browser
699  *
700  * A namespace with static properties for browser/feature detection used by Leaflet internally.
701  *
702  * @example
703  *
704  * ```js
705  * if (L.Browser.ielt9) {
706  *   alert('Upgrade your browser, dude!');
707  * }
708  * ```
709  */
710
711 (function () {
712
713         var ua = navigator.userAgent.toLowerCase(),
714             doc = document.documentElement,
715
716             ie = 'ActiveXObject' in window,
717
718             webkit    = ua.indexOf('webkit') !== -1,
719             phantomjs = ua.indexOf('phantom') !== -1,
720             android23 = ua.search('android [23]') !== -1,
721             chrome    = ua.indexOf('chrome') !== -1,
722             gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,
723
724             win = navigator.platform.indexOf('Win') === 0,
725
726             mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
727             msPointer = !window.PointerEvent && window.MSPointerEvent,
728             pointer = window.PointerEvent || msPointer,
729
730             ie3d = ie && ('transition' in doc.style),
731             webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
732             gecko3d = 'MozPerspective' in doc.style,
733             opera12 = 'OTransition' in doc.style;
734
735
736         var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
737                         (window.DocumentTouch && document instanceof window.DocumentTouch));
738
739         L.Browser = {
740
741                 // @property ie: Boolean
742                 // `true` for all Internet Explorer versions (not Edge).
743                 ie: ie,
744
745                 // @property ielt9: Boolean
746                 // `true` for Internet Explorer versions less than 9.
747                 ielt9: ie && !document.addEventListener,
748
749                 // @property edge: Boolean
750                 // `true` for the Edge web browser.
751                 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
752
753                 // @property webkit: Boolean
754                 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
755                 webkit: webkit,
756
757                 // @property gecko: Boolean
758                 // `true` for gecko-based browsers like Firefox.
759                 gecko: gecko,
760
761                 // @property android: Boolean
762                 // `true` for any browser running on an Android platform.
763                 android: ua.indexOf('android') !== -1,
764
765                 // @property android23: Boolean
766                 // `true` for browsers running on Android 2 or Android 3.
767                 android23: android23,
768
769                 // @property chrome: Boolean
770                 // `true` for the Chrome browser.
771                 chrome: chrome,
772
773                 // @property safari: Boolean
774                 // `true` for the Safari browser.
775                 safari: !chrome && ua.indexOf('safari') !== -1,
776
777
778                 // @property win: Boolean
779                 // `true` when the browser is running in a Windows platform
780                 win: win,
781
782
783                 // @property ie3d: Boolean
784                 // `true` for all Internet Explorer versions supporting CSS transforms.
785                 ie3d: ie3d,
786
787                 // @property webkit3d: Boolean
788                 // `true` for webkit-based browsers supporting CSS transforms.
789                 webkit3d: webkit3d,
790
791                 // @property gecko3d: Boolean
792                 // `true` for gecko-based browsers supporting CSS transforms.
793                 gecko3d: gecko3d,
794
795                 // @property opera12: Boolean
796                 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
797                 opera12: opera12,
798
799                 // @property any3d: Boolean
800                 // `true` for all browsers supporting CSS transforms.
801                 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
802
803
804                 // @property mobile: Boolean
805                 // `true` for all browsers running in a mobile device.
806                 mobile: mobile,
807
808                 // @property mobileWebkit: Boolean
809                 // `true` for all webkit-based browsers in a mobile device.
810                 mobileWebkit: mobile && webkit,
811
812                 // @property mobileWebkit3d: Boolean
813                 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
814                 mobileWebkit3d: mobile && webkit3d,
815
816                 // @property mobileOpera: Boolean
817                 // `true` for the Opera browser in a mobile device.
818                 mobileOpera: mobile && window.opera,
819
820                 // @property mobileGecko: Boolean
821                 // `true` for gecko-based browsers running in a mobile device.
822                 mobileGecko: mobile && gecko,
823
824
825                 // @property touch: Boolean
826                 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
827                 touch: !!touch,
828
829                 // @property msPointer: Boolean
830                 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
831                 msPointer: !!msPointer,
832
833                 // @property pointer: Boolean
834                 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
835                 pointer: !!pointer,
836
837
838                 // @property retina: Boolean
839                 // `true` for browsers on a high-resolution "retina" screen.
840                 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
841         };
842
843 }());
844
845
846
847 /*
848  * @class Point
849  * @aka L.Point
850  *
851  * Represents a point with `x` and `y` coordinates in pixels.
852  *
853  * @example
854  *
855  * ```js
856  * var point = L.point(200, 300);
857  * ```
858  *
859  * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
860  *
861  * ```js
862  * map.panBy([200, 300]);
863  * map.panBy(L.point(200, 300));
864  * ```
865  */
866
867 L.Point = function (x, y, round) {
868         this.x = (round ? Math.round(x) : x);
869         this.y = (round ? Math.round(y) : y);
870 };
871
872 L.Point.prototype = {
873
874         // @method clone(): Point
875         // Returns a copy of the current point.
876         clone: function () {
877                 return new L.Point(this.x, this.y);
878         },
879
880         // @method add(otherPoint: Point): Point
881         // Returns the result of addition of the current and the given points.
882         add: function (point) {
883                 // non-destructive, returns a new point
884                 return this.clone()._add(L.point(point));
885         },
886
887         _add: function (point) {
888                 // destructive, used directly for performance in situations where it's safe to modify existing point
889                 this.x += point.x;
890                 this.y += point.y;
891                 return this;
892         },
893
894         // @method subtract(otherPoint: Point): Point
895         // Returns the result of subtraction of the given point from the current.
896         subtract: function (point) {
897                 return this.clone()._subtract(L.point(point));
898         },
899
900         _subtract: function (point) {
901                 this.x -= point.x;
902                 this.y -= point.y;
903                 return this;
904         },
905
906         // @method divideBy(num: Number): Point
907         // Returns the result of division of the current point by the given number.
908         divideBy: function (num) {
909                 return this.clone()._divideBy(num);
910         },
911
912         _divideBy: function (num) {
913                 this.x /= num;
914                 this.y /= num;
915                 return this;
916         },
917
918         // @method multiplyBy(num: Number): Point
919         // Returns the result of multiplication of the current point by the given number.
920         multiplyBy: function (num) {
921                 return this.clone()._multiplyBy(num);
922         },
923
924         _multiplyBy: function (num) {
925                 this.x *= num;
926                 this.y *= num;
927                 return this;
928         },
929
930         // @method scaleBy(scale: Point): Point
931         // Multiply each coordinate of the current point by each coordinate of
932         // `scale`. In linear algebra terms, multiply the point by the
933         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
934         // defined by `scale`.
935         scaleBy: function (point) {
936                 return new L.Point(this.x * point.x, this.y * point.y);
937         },
938
939         // @method unscaleBy(scale: Point): Point
940         // Inverse of `scaleBy`. Divide each coordinate of the current point by
941         // each coordinate of `scale`.
942         unscaleBy: function (point) {
943                 return new L.Point(this.x / point.x, this.y / point.y);
944         },
945
946         // @method round(): Point
947         // Returns a copy of the current point with rounded coordinates.
948         round: function () {
949                 return this.clone()._round();
950         },
951
952         _round: function () {
953                 this.x = Math.round(this.x);
954                 this.y = Math.round(this.y);
955                 return this;
956         },
957
958         // @method floor(): Point
959         // Returns a copy of the current point with floored coordinates (rounded down).
960         floor: function () {
961                 return this.clone()._floor();
962         },
963
964         _floor: function () {
965                 this.x = Math.floor(this.x);
966                 this.y = Math.floor(this.y);
967                 return this;
968         },
969
970         // @method ceil(): Point
971         // Returns a copy of the current point with ceiled coordinates (rounded up).
972         ceil: function () {
973                 return this.clone()._ceil();
974         },
975
976         _ceil: function () {
977                 this.x = Math.ceil(this.x);
978                 this.y = Math.ceil(this.y);
979                 return this;
980         },
981
982         // @method distanceTo(otherPoint: Point): Number
983         // Returns the cartesian distance between the current and the given points.
984         distanceTo: function (point) {
985                 point = L.point(point);
986
987                 var x = point.x - this.x,
988                     y = point.y - this.y;
989
990                 return Math.sqrt(x * x + y * y);
991         },
992
993         // @method equals(otherPoint: Point): Boolean
994         // Returns `true` if the given point has the same coordinates.
995         equals: function (point) {
996                 point = L.point(point);
997
998                 return point.x === this.x &&
999                        point.y === this.y;
1000         },
1001
1002         // @method contains(otherPoint: Point): Boolean
1003         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1004         contains: function (point) {
1005                 point = L.point(point);
1006
1007                 return Math.abs(point.x) <= Math.abs(this.x) &&
1008                        Math.abs(point.y) <= Math.abs(this.y);
1009         },
1010
1011         // @method toString(): String
1012         // Returns a string representation of the point for debugging purposes.
1013         toString: function () {
1014                 return 'Point(' +
1015                         L.Util.formatNum(this.x) + ', ' +
1016                         L.Util.formatNum(this.y) + ')';
1017         }
1018 };
1019
1020 // @factory L.point(x: Number, y: Number, round?: Boolean)
1021 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1022
1023 // @alternative
1024 // @factory L.point(coords: Number[])
1025 // Expects an array of the form `[x, y]` instead.
1026
1027 // @alternative
1028 // @factory L.point(coords: Object)
1029 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1030 L.point = function (x, y, round) {
1031         if (x instanceof L.Point) {
1032                 return x;
1033         }
1034         if (L.Util.isArray(x)) {
1035                 return new L.Point(x[0], x[1]);
1036         }
1037         if (x === undefined || x === null) {
1038                 return x;
1039         }
1040         if (typeof x === 'object' && 'x' in x && 'y' in x) {
1041                 return new L.Point(x.x, x.y);
1042         }
1043         return new L.Point(x, y, round);
1044 };
1045
1046
1047
1048 /*
1049  * @class Bounds
1050  * @aka L.Bounds
1051  *
1052  * Represents a rectangular area in pixel coordinates.
1053  *
1054  * @example
1055  *
1056  * ```js
1057  * var p1 = L.point(10, 10),
1058  * p2 = L.point(40, 60),
1059  * bounds = L.bounds(p1, p2);
1060  * ```
1061  *
1062  * 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:
1063  *
1064  * ```js
1065  * otherBounds.intersects([[10, 10], [40, 60]]);
1066  * ```
1067  */
1068
1069 L.Bounds = function (a, b) {
1070         if (!a) { return; }
1071
1072         var points = b ? [a, b] : a;
1073
1074         for (var i = 0, len = points.length; i < len; i++) {
1075                 this.extend(points[i]);
1076         }
1077 };
1078
1079 L.Bounds.prototype = {
1080         // @method extend(point: Point): this
1081         // Extends the bounds to contain the given point.
1082         extend: function (point) { // (Point)
1083                 point = L.point(point);
1084
1085                 // @property min: Point
1086                 // The top left corner of the rectangle.
1087                 // @property max: Point
1088                 // The bottom right corner of the rectangle.
1089                 if (!this.min && !this.max) {
1090                         this.min = point.clone();
1091                         this.max = point.clone();
1092                 } else {
1093                         this.min.x = Math.min(point.x, this.min.x);
1094                         this.max.x = Math.max(point.x, this.max.x);
1095                         this.min.y = Math.min(point.y, this.min.y);
1096                         this.max.y = Math.max(point.y, this.max.y);
1097                 }
1098                 return this;
1099         },
1100
1101         // @method getCenter(round?: Boolean): Point
1102         // Returns the center point of the bounds.
1103         getCenter: function (round) {
1104                 return new L.Point(
1105                         (this.min.x + this.max.x) / 2,
1106                         (this.min.y + this.max.y) / 2, round);
1107         },
1108
1109         // @method getBottomLeft(): Point
1110         // Returns the bottom-left point of the bounds.
1111         getBottomLeft: function () {
1112                 return new L.Point(this.min.x, this.max.y);
1113         },
1114
1115         // @method getTopRight(): Point
1116         // Returns the top-right point of the bounds.
1117         getTopRight: function () { // -> Point
1118                 return new L.Point(this.max.x, this.min.y);
1119         },
1120
1121         // @method getSize(): Point
1122         // Returns the size of the given bounds
1123         getSize: function () {
1124                 return this.max.subtract(this.min);
1125         },
1126
1127         // @method contains(otherBounds: Bounds): Boolean
1128         // Returns `true` if the rectangle contains the given one.
1129         // @alternative
1130         // @method contains(point: Point): Boolean
1131         // Returns `true` if the rectangle contains the given point.
1132         contains: function (obj) {
1133                 var min, max;
1134
1135                 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1136                         obj = L.point(obj);
1137                 } else {
1138                         obj = L.bounds(obj);
1139                 }
1140
1141                 if (obj instanceof L.Bounds) {
1142                         min = obj.min;
1143                         max = obj.max;
1144                 } else {
1145                         min = max = obj;
1146                 }
1147
1148                 return (min.x >= this.min.x) &&
1149                        (max.x <= this.max.x) &&
1150                        (min.y >= this.min.y) &&
1151                        (max.y <= this.max.y);
1152         },
1153
1154         // @method intersects(otherBounds: Bounds): Boolean
1155         // Returns `true` if the rectangle intersects the given bounds. Two bounds
1156         // intersect if they have at least one point in common.
1157         intersects: function (bounds) { // (Bounds) -> Boolean
1158                 bounds = L.bounds(bounds);
1159
1160                 var min = this.min,
1161                     max = this.max,
1162                     min2 = bounds.min,
1163                     max2 = bounds.max,
1164                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1165                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1166
1167                 return xIntersects && yIntersects;
1168         },
1169
1170         // @method overlaps(otherBounds: Bounds): Boolean
1171         // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1172         // overlap if their intersection is an area.
1173         overlaps: function (bounds) { // (Bounds) -> Boolean
1174                 bounds = L.bounds(bounds);
1175
1176                 var min = this.min,
1177                     max = this.max,
1178                     min2 = bounds.min,
1179                     max2 = bounds.max,
1180                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1181                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1182
1183                 return xOverlaps && yOverlaps;
1184         },
1185
1186         isValid: function () {
1187                 return !!(this.min && this.max);
1188         }
1189 };
1190
1191
1192 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1193 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1194 // @alternative
1195 // @factory L.bounds(points: Point[])
1196 // Creates a Bounds object from the points it contains
1197 L.bounds = function (a, b) {
1198         if (!a || a instanceof L.Bounds) {
1199                 return a;
1200         }
1201         return new L.Bounds(a, b);
1202 };
1203
1204
1205
1206 /*
1207  * @class Transformation
1208  * @aka L.Transformation
1209  *
1210  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1211  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1212  * the reverse. Used by Leaflet in its projections code.
1213  *
1214  * @example
1215  *
1216  * ```js
1217  * var transformation = new L.Transformation(2, 5, -1, 10),
1218  *      p = L.point(1, 2),
1219  *      p2 = transformation.transform(p), //  L.point(7, 8)
1220  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1221  * ```
1222  */
1223
1224
1225 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1226 // Creates a `Transformation` object with the given coefficients.
1227 L.Transformation = function (a, b, c, d) {
1228         this._a = a;
1229         this._b = b;
1230         this._c = c;
1231         this._d = d;
1232 };
1233
1234 L.Transformation.prototype = {
1235         // @method transform(point: Point, scale?: Number): Point
1236         // Returns a transformed point, optionally multiplied by the given scale.
1237         // Only accepts real `L.Point` instances, not arrays.
1238         transform: function (point, scale) { // (Point, Number) -> Point
1239                 return this._transform(point.clone(), scale);
1240         },
1241
1242         // destructive transform (faster)
1243         _transform: function (point, scale) {
1244                 scale = scale || 1;
1245                 point.x = scale * (this._a * point.x + this._b);
1246                 point.y = scale * (this._c * point.y + this._d);
1247                 return point;
1248         },
1249
1250         // @method untransform(point: Point, scale?: Number): Point
1251         // Returns the reverse transformation of the given point, optionally divided
1252         // by the given scale. Only accepts real `L.Point` instances, not arrays.
1253         untransform: function (point, scale) {
1254                 scale = scale || 1;
1255                 return new L.Point(
1256                         (point.x / scale - this._b) / this._a,
1257                         (point.y / scale - this._d) / this._c);
1258         }
1259 };
1260
1261
1262
1263 /*
1264  * @namespace DomUtil
1265  *
1266  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1267  * tree, used by Leaflet internally.
1268  *
1269  * Most functions expecting or returning a `HTMLElement` also work for
1270  * SVG elements. The only difference is that classes refer to CSS classes
1271  * in HTML and SVG classes in SVG.
1272  */
1273
1274 L.DomUtil = {
1275
1276         // @function get(id: String|HTMLElement): HTMLElement
1277         // Returns an element given its DOM id, or returns the element itself
1278         // if it was passed directly.
1279         get: function (id) {
1280                 return typeof id === 'string' ? document.getElementById(id) : id;
1281         },
1282
1283         // @function getStyle(el: HTMLElement, styleAttrib: String): String
1284         // Returns the value for a certain style attribute on an element,
1285         // including computed values or values set through CSS.
1286         getStyle: function (el, style) {
1287
1288                 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1289
1290                 if ((!value || value === 'auto') && document.defaultView) {
1291                         var css = document.defaultView.getComputedStyle(el, null);
1292                         value = css ? css[style] : null;
1293                 }
1294
1295                 return value === 'auto' ? null : value;
1296         },
1297
1298         // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1299         // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1300         create: function (tagName, className, container) {
1301
1302                 var el = document.createElement(tagName);
1303                 el.className = className || '';
1304
1305                 if (container) {
1306                         container.appendChild(el);
1307                 }
1308
1309                 return el;
1310         },
1311
1312         // @function remove(el: HTMLElement)
1313         // Removes `el` from its parent element
1314         remove: function (el) {
1315                 var parent = el.parentNode;
1316                 if (parent) {
1317                         parent.removeChild(el);
1318                 }
1319         },
1320
1321         // @function empty(el: HTMLElement)
1322         // Removes all of `el`'s children elements from `el`
1323         empty: function (el) {
1324                 while (el.firstChild) {
1325                         el.removeChild(el.firstChild);
1326                 }
1327         },
1328
1329         // @function toFront(el: HTMLElement)
1330         // Makes `el` the last children of its parent, so it renders in front of the other children.
1331         toFront: function (el) {
1332                 el.parentNode.appendChild(el);
1333         },
1334
1335         // @function toBack(el: HTMLElement)
1336         // Makes `el` the first children of its parent, so it renders back from the other children.
1337         toBack: function (el) {
1338                 var parent = el.parentNode;
1339                 parent.insertBefore(el, parent.firstChild);
1340         },
1341
1342         // @function hasClass(el: HTMLElement, name: String): Boolean
1343         // Returns `true` if the element's class attribute contains `name`.
1344         hasClass: function (el, name) {
1345                 if (el.classList !== undefined) {
1346                         return el.classList.contains(name);
1347                 }
1348                 var className = L.DomUtil.getClass(el);
1349                 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1350         },
1351
1352         // @function addClass(el: HTMLElement, name: String)
1353         // Adds `name` to the element's class attribute.
1354         addClass: function (el, name) {
1355                 if (el.classList !== undefined) {
1356                         var classes = L.Util.splitWords(name);
1357                         for (var i = 0, len = classes.length; i < len; i++) {
1358                                 el.classList.add(classes[i]);
1359                         }
1360                 } else if (!L.DomUtil.hasClass(el, name)) {
1361                         var className = L.DomUtil.getClass(el);
1362                         L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1363                 }
1364         },
1365
1366         // @function removeClass(el: HTMLElement, name: String)
1367         // Removes `name` from the element's class attribute.
1368         removeClass: function (el, name) {
1369                 if (el.classList !== undefined) {
1370                         el.classList.remove(name);
1371                 } else {
1372                         L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1373                 }
1374         },
1375
1376         // @function setClass(el: HTMLElement, name: String)
1377         // Sets the element's class.
1378         setClass: function (el, name) {
1379                 if (el.className.baseVal === undefined) {
1380                         el.className = name;
1381                 } else {
1382                         // in case of SVG element
1383                         el.className.baseVal = name;
1384                 }
1385         },
1386
1387         // @function getClass(el: HTMLElement): String
1388         // Returns the element's class.
1389         getClass: function (el) {
1390                 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1391         },
1392
1393         // @function setOpacity(el: HTMLElement, opacity: Number)
1394         // Set the opacity of an element (including old IE support).
1395         // `opacity` must be a number from `0` to `1`.
1396         setOpacity: function (el, value) {
1397
1398                 if ('opacity' in el.style) {
1399                         el.style.opacity = value;
1400
1401                 } else if ('filter' in el.style) {
1402                         L.DomUtil._setOpacityIE(el, value);
1403                 }
1404         },
1405
1406         _setOpacityIE: function (el, value) {
1407                 var filter = false,
1408                     filterName = 'DXImageTransform.Microsoft.Alpha';
1409
1410                 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1411                 try {
1412                         filter = el.filters.item(filterName);
1413                 } catch (e) {
1414                         // don't set opacity to 1 if we haven't already set an opacity,
1415                         // it isn't needed and breaks transparent pngs.
1416                         if (value === 1) { return; }
1417                 }
1418
1419                 value = Math.round(value * 100);
1420
1421                 if (filter) {
1422                         filter.Enabled = (value !== 100);
1423                         filter.Opacity = value;
1424                 } else {
1425                         el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1426                 }
1427         },
1428
1429         // @function testProp(props: String[]): String|false
1430         // Goes through the array of style names and returns the first name
1431         // that is a valid style name for an element. If no such name is found,
1432         // it returns false. Useful for vendor-prefixed styles like `transform`.
1433         testProp: function (props) {
1434
1435                 var style = document.documentElement.style;
1436
1437                 for (var i = 0; i < props.length; i++) {
1438                         if (props[i] in style) {
1439                                 return props[i];
1440                         }
1441                 }
1442                 return false;
1443         },
1444
1445         // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1446         // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1447         // and optionally scaled by `scale`. Does not have an effect if the
1448         // browser doesn't support 3D CSS transforms.
1449         setTransform: function (el, offset, scale) {
1450                 var pos = offset || new L.Point(0, 0);
1451
1452                 el.style[L.DomUtil.TRANSFORM] =
1453                         (L.Browser.ie3d ?
1454                                 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1455                                 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1456                         (scale ? ' scale(' + scale + ')' : '');
1457         },
1458
1459         // @function setPosition(el: HTMLElement, position: Point)
1460         // Sets the position of `el` to coordinates specified by `position`,
1461         // using CSS translate or top/left positioning depending on the browser
1462         // (used by Leaflet internally to position its layers).
1463         setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1464
1465                 /*eslint-disable */
1466                 el._leaflet_pos = point;
1467                 /*eslint-enable */
1468
1469                 if (L.Browser.any3d) {
1470                         L.DomUtil.setTransform(el, point);
1471                 } else {
1472                         el.style.left = point.x + 'px';
1473                         el.style.top = point.y + 'px';
1474                 }
1475         },
1476
1477         // @function getPosition(el: HTMLElement): Point
1478         // Returns the coordinates of an element previously positioned with setPosition.
1479         getPosition: function (el) {
1480                 // this method is only used for elements previously positioned using setPosition,
1481                 // so it's safe to cache the position for performance
1482
1483                 return el._leaflet_pos || new L.Point(0, 0);
1484         }
1485 };
1486
1487
1488 (function () {
1489         // prefix style property names
1490
1491         // @property TRANSFORM: String
1492         // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1493         L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1494                         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1495
1496
1497         // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1498         // the same for the transitionend event, in particular the Android 4.1 stock browser
1499
1500         // @property TRANSITION: String
1501         // Vendor-prefixed transform style name.
1502         var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1503                         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1504
1505         L.DomUtil.TRANSITION_END =
1506                         transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1507
1508         // @function disableTextSelection()
1509         // Prevents the user from generating `selectstart` DOM events, usually generated
1510         // when the user drags the mouse through a page with text. Used internally
1511         // by Leaflet to override the behaviour of any click-and-drag interaction on
1512         // the map. Affects drag interactions on the whole document.
1513
1514         // @function enableTextSelection()
1515         // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1516         if ('onselectstart' in document) {
1517                 L.DomUtil.disableTextSelection = function () {
1518                         L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1519                 };
1520                 L.DomUtil.enableTextSelection = function () {
1521                         L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1522                 };
1523
1524         } else {
1525                 var userSelectProperty = L.DomUtil.testProp(
1526                         ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1527
1528                 L.DomUtil.disableTextSelection = function () {
1529                         if (userSelectProperty) {
1530                                 var style = document.documentElement.style;
1531                                 this._userSelect = style[userSelectProperty];
1532                                 style[userSelectProperty] = 'none';
1533                         }
1534                 };
1535                 L.DomUtil.enableTextSelection = function () {
1536                         if (userSelectProperty) {
1537                                 document.documentElement.style[userSelectProperty] = this._userSelect;
1538                                 delete this._userSelect;
1539                         }
1540                 };
1541         }
1542
1543         // @function disableImageDrag()
1544         // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1545         // for `dragstart` DOM events, usually generated when the user drags an image.
1546         L.DomUtil.disableImageDrag = function () {
1547                 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1548         };
1549
1550         // @function enableImageDrag()
1551         // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1552         L.DomUtil.enableImageDrag = function () {
1553                 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1554         };
1555
1556         // @function preventOutline(el: HTMLElement)
1557         // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1558         // of the element `el` invisible. Used internally by Leaflet to prevent
1559         // focusable elements from displaying an outline when the user performs a
1560         // drag interaction on them.
1561         L.DomUtil.preventOutline = function (element) {
1562                 while (element.tabIndex === -1) {
1563                         element = element.parentNode;
1564                 }
1565                 if (!element || !element.style) { return; }
1566                 L.DomUtil.restoreOutline();
1567                 this._outlineElement = element;
1568                 this._outlineStyle = element.style.outline;
1569                 element.style.outline = 'none';
1570                 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1571         };
1572
1573         // @function restoreOutline()
1574         // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1575         L.DomUtil.restoreOutline = function () {
1576                 if (!this._outlineElement) { return; }
1577                 this._outlineElement.style.outline = this._outlineStyle;
1578                 delete this._outlineElement;
1579                 delete this._outlineStyle;
1580                 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1581         };
1582 })();
1583
1584
1585
1586 /* @class LatLng
1587  * @aka L.LatLng
1588  *
1589  * Represents a geographical point with a certain latitude and longitude.
1590  *
1591  * @example
1592  *
1593  * ```
1594  * var latlng = L.latLng(50.5, 30.5);
1595  * ```
1596  *
1597  * 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:
1598  *
1599  * ```
1600  * map.panTo([50, 30]);
1601  * map.panTo({lon: 30, lat: 50});
1602  * map.panTo({lat: 50, lng: 30});
1603  * map.panTo(L.latLng(50, 30));
1604  * ```
1605  */
1606
1607 L.LatLng = function (lat, lng, alt) {
1608         if (isNaN(lat) || isNaN(lng)) {
1609                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1610         }
1611
1612         // @property lat: Number
1613         // Latitude in degrees
1614         this.lat = +lat;
1615
1616         // @property lng: Number
1617         // Longitude in degrees
1618         this.lng = +lng;
1619
1620         // @property alt: Number
1621         // Altitude in meters (optional)
1622         if (alt !== undefined) {
1623                 this.alt = +alt;
1624         }
1625 };
1626
1627 L.LatLng.prototype = {
1628         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1629         // 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.
1630         equals: function (obj, maxMargin) {
1631                 if (!obj) { return false; }
1632
1633                 obj = L.latLng(obj);
1634
1635                 var margin = Math.max(
1636                         Math.abs(this.lat - obj.lat),
1637                         Math.abs(this.lng - obj.lng));
1638
1639                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1640         },
1641
1642         // @method toString(): String
1643         // Returns a string representation of the point (for debugging purposes).
1644         toString: function (precision) {
1645                 return 'LatLng(' +
1646                         L.Util.formatNum(this.lat, precision) + ', ' +
1647                         L.Util.formatNum(this.lng, precision) + ')';
1648         },
1649
1650         // @method distanceTo(otherLatLng: LatLng): Number
1651         // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1652         distanceTo: function (other) {
1653                 return L.CRS.Earth.distance(this, L.latLng(other));
1654         },
1655
1656         // @method wrap(): LatLng
1657         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1658         wrap: function () {
1659                 return L.CRS.Earth.wrapLatLng(this);
1660         },
1661
1662         // @method toBounds(sizeInMeters: Number): LatLngBounds
1663         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters` meters apart from the `LatLng`.
1664         toBounds: function (sizeInMeters) {
1665                 var latAccuracy = 180 * sizeInMeters / 40075017,
1666                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1667
1668                 return L.latLngBounds(
1669                         [this.lat - latAccuracy, this.lng - lngAccuracy],
1670                         [this.lat + latAccuracy, this.lng + lngAccuracy]);
1671         },
1672
1673         clone: function () {
1674                 return new L.LatLng(this.lat, this.lng, this.alt);
1675         }
1676 };
1677
1678
1679
1680 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1681 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1682
1683 // @alternative
1684 // @factory L.latLng(coords: Array): LatLng
1685 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1686
1687 // @alternative
1688 // @factory L.latLng(coords: Object): LatLng
1689 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1690
1691 L.latLng = function (a, b, c) {
1692         if (a instanceof L.LatLng) {
1693                 return a;
1694         }
1695         if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1696                 if (a.length === 3) {
1697                         return new L.LatLng(a[0], a[1], a[2]);
1698                 }
1699                 if (a.length === 2) {
1700                         return new L.LatLng(a[0], a[1]);
1701                 }
1702                 return null;
1703         }
1704         if (a === undefined || a === null) {
1705                 return a;
1706         }
1707         if (typeof a === 'object' && 'lat' in a) {
1708                 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1709         }
1710         if (b === undefined) {
1711                 return null;
1712         }
1713         return new L.LatLng(a, b, c);
1714 };
1715
1716
1717
1718 /*
1719  * @class LatLngBounds
1720  * @aka L.LatLngBounds
1721  *
1722  * Represents a rectangular geographical area on a map.
1723  *
1724  * @example
1725  *
1726  * ```js
1727  * var southWest = L.latLng(40.712, -74.227),
1728  * northEast = L.latLng(40.774, -74.125),
1729  * bounds = L.latLngBounds(southWest, northEast);
1730  * ```
1731  *
1732  * 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:
1733  *
1734  * ```js
1735  * map.fitBounds([
1736  *      [40.712, -74.227],
1737  *      [40.774, -74.125]
1738  * ]);
1739  * ```
1740  */
1741
1742 L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
1743         if (!southWest) { return; }
1744
1745         var latlngs = northEast ? [southWest, northEast] : southWest;
1746
1747         for (var i = 0, len = latlngs.length; i < len; i++) {
1748                 this.extend(latlngs[i]);
1749         }
1750 };
1751
1752 L.LatLngBounds.prototype = {
1753
1754         // @method extend(latlng: LatLng): this
1755         // Extend the bounds to contain the given point
1756
1757         // @alternative
1758         // @method extend(otherBounds: LatLngBounds): this
1759         // Extend the bounds to contain the given bounds
1760         extend: function (obj) {
1761                 var sw = this._southWest,
1762                     ne = this._northEast,
1763                     sw2, ne2;
1764
1765                 if (obj instanceof L.LatLng) {
1766                         sw2 = obj;
1767                         ne2 = obj;
1768
1769                 } else if (obj instanceof L.LatLngBounds) {
1770                         sw2 = obj._southWest;
1771                         ne2 = obj._northEast;
1772
1773                         if (!sw2 || !ne2) { return this; }
1774
1775                 } else {
1776                         return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1777                 }
1778
1779                 if (!sw && !ne) {
1780                         this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1781                         this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1782                 } else {
1783                         sw.lat = Math.min(sw2.lat, sw.lat);
1784                         sw.lng = Math.min(sw2.lng, sw.lng);
1785                         ne.lat = Math.max(ne2.lat, ne.lat);
1786                         ne.lng = Math.max(ne2.lng, ne.lng);
1787                 }
1788
1789                 return this;
1790         },
1791
1792         // @method pad(bufferRatio: Number): LatLngBounds
1793         // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1794         pad: function (bufferRatio) {
1795                 var sw = this._southWest,
1796                     ne = this._northEast,
1797                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1798                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1799
1800                 return new L.LatLngBounds(
1801                         new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1802                         new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1803         },
1804
1805         // @method getCenter(): LatLng
1806         // Returns the center point of the bounds.
1807         getCenter: function () {
1808                 return new L.LatLng(
1809                         (this._southWest.lat + this._northEast.lat) / 2,
1810                         (this._southWest.lng + this._northEast.lng) / 2);
1811         },
1812
1813         // @method getSouthWest(): LatLng
1814         // Returns the south-west point of the bounds.
1815         getSouthWest: function () {
1816                 return this._southWest;
1817         },
1818
1819         // @method getNorthEast(): LatLng
1820         // Returns the north-east point of the bounds.
1821         getNorthEast: function () {
1822                 return this._northEast;
1823         },
1824
1825         // @method getNorthWest(): LatLng
1826         // Returns the north-west point of the bounds.
1827         getNorthWest: function () {
1828                 return new L.LatLng(this.getNorth(), this.getWest());
1829         },
1830
1831         // @method getSouthEast(): LatLng
1832         // Returns the south-east point of the bounds.
1833         getSouthEast: function () {
1834                 return new L.LatLng(this.getSouth(), this.getEast());
1835         },
1836
1837         // @method getWest(): Number
1838         // Returns the west longitude of the bounds
1839         getWest: function () {
1840                 return this._southWest.lng;
1841         },
1842
1843         // @method getSouth(): Number
1844         // Returns the south latitude of the bounds
1845         getSouth: function () {
1846                 return this._southWest.lat;
1847         },
1848
1849         // @method getEast(): Number
1850         // Returns the east longitude of the bounds
1851         getEast: function () {
1852                 return this._northEast.lng;
1853         },
1854
1855         // @method getNorth(): Number
1856         // Returns the north latitude of the bounds
1857         getNorth: function () {
1858                 return this._northEast.lat;
1859         },
1860
1861         // @method contains(otherBounds: LatLngBounds): Boolean
1862         // Returns `true` if the rectangle contains the given one.
1863
1864         // @alternative
1865         // @method contains (latlng: LatLng): Boolean
1866         // Returns `true` if the rectangle contains the given point.
1867         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1868                 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
1869                         obj = L.latLng(obj);
1870                 } else {
1871                         obj = L.latLngBounds(obj);
1872                 }
1873
1874                 var sw = this._southWest,
1875                     ne = this._northEast,
1876                     sw2, ne2;
1877
1878                 if (obj instanceof L.LatLngBounds) {
1879                         sw2 = obj.getSouthWest();
1880                         ne2 = obj.getNorthEast();
1881                 } else {
1882                         sw2 = ne2 = obj;
1883                 }
1884
1885                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1886                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1887         },
1888
1889         // @method intersects(otherBounds: LatLngBounds): Boolean
1890         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1891         intersects: function (bounds) {
1892                 bounds = L.latLngBounds(bounds);
1893
1894                 var sw = this._southWest,
1895                     ne = this._northEast,
1896                     sw2 = bounds.getSouthWest(),
1897                     ne2 = bounds.getNorthEast(),
1898
1899                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1900                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1901
1902                 return latIntersects && lngIntersects;
1903         },
1904
1905         // @method overlaps(otherBounds: Bounds): Boolean
1906         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1907         overlaps: function (bounds) {
1908                 bounds = L.latLngBounds(bounds);
1909
1910                 var sw = this._southWest,
1911                     ne = this._northEast,
1912                     sw2 = bounds.getSouthWest(),
1913                     ne2 = bounds.getNorthEast(),
1914
1915                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1916                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1917
1918                 return latOverlaps && lngOverlaps;
1919         },
1920
1921         // @method toBBoxString(): String
1922         // 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.
1923         toBBoxString: function () {
1924                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1925         },
1926
1927         // @method equals(otherBounds: LatLngBounds): Boolean
1928         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1929         equals: function (bounds) {
1930                 if (!bounds) { return false; }
1931
1932                 bounds = L.latLngBounds(bounds);
1933
1934                 return this._southWest.equals(bounds.getSouthWest()) &&
1935                        this._northEast.equals(bounds.getNorthEast());
1936         },
1937
1938         // @method isValid(): Boolean
1939         // Returns `true` if the bounds are properly initialized.
1940         isValid: function () {
1941                 return !!(this._southWest && this._northEast);
1942         }
1943 };
1944
1945 // TODO International date line?
1946
1947 // @factory L.latLngBounds(southWest: LatLng, northEast: LatLng)
1948 // Creates a `LatLngBounds` object by defining south-west and north-east corners of the rectangle.
1949
1950 // @alternative
1951 // @factory L.latLngBounds(latlngs: LatLng[])
1952 // 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).
1953 L.latLngBounds = function (a, b) {
1954         if (a instanceof L.LatLngBounds) {
1955                 return a;
1956         }
1957         return new L.LatLngBounds(a, b);
1958 };
1959
1960
1961
1962 /*
1963  * @namespace Projection
1964  * @section
1965  * Leaflet comes with a set of already defined Projections out of the box:
1966  *
1967  * @projection L.Projection.LonLat
1968  *
1969  * Equirectangular, or Plate Carree projection — the most simple projection,
1970  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1971  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1972  * `EPSG:3395` and `Simple` CRS.
1973  */
1974
1975 L.Projection = {};
1976
1977 L.Projection.LonLat = {
1978         project: function (latlng) {
1979                 return new L.Point(latlng.lng, latlng.lat);
1980         },
1981
1982         unproject: function (point) {
1983                 return new L.LatLng(point.y, point.x);
1984         },
1985
1986         bounds: L.bounds([-180, -90], [180, 90])
1987 };
1988
1989
1990
1991 /*
1992  * @namespace Projection
1993  * @projection L.Projection.SphericalMercator
1994  *
1995  * Spherical Mercator projection — the most common projection for online maps,
1996  * used by almost all free and commercial tile providers. Assumes that Earth is
1997  * a sphere. Used by the `EPSG:3857` CRS.
1998  */
1999
2000 L.Projection.SphericalMercator = {
2001
2002         R: 6378137,
2003         MAX_LATITUDE: 85.0511287798,
2004
2005         project: function (latlng) {
2006                 var d = Math.PI / 180,
2007                     max = this.MAX_LATITUDE,
2008                     lat = Math.max(Math.min(max, latlng.lat), -max),
2009                     sin = Math.sin(lat * d);
2010
2011                 return new L.Point(
2012                                 this.R * latlng.lng * d,
2013                                 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2014         },
2015
2016         unproject: function (point) {
2017                 var d = 180 / Math.PI;
2018
2019                 return new L.LatLng(
2020                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2021                         point.x * d / this.R);
2022         },
2023
2024         bounds: (function () {
2025                 var d = 6378137 * Math.PI;
2026                 return L.bounds([-d, -d], [d, d]);
2027         })()
2028 };
2029
2030
2031
2032 /*
2033  * @class CRS
2034  * @aka L.CRS
2035  * Abstract class that defines coordinate reference systems for projecting
2036  * geographical points into pixel (screen) coordinates and back (and to
2037  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2038  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2039  *
2040  * Leaflet defines the most usual CRSs by default. If you want to use a
2041  * CRS not defined by default, take a look at the
2042  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2043  */
2044
2045 L.CRS = {
2046         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2047         // Projects geographical coordinates into pixel coordinates for a given zoom.
2048         latLngToPoint: function (latlng, zoom) {
2049                 var projectedPoint = this.projection.project(latlng),
2050                     scale = this.scale(zoom);
2051
2052                 return this.transformation._transform(projectedPoint, scale);
2053         },
2054
2055         // @method pointToLatLng(point: Point, zoom: Number): LatLng
2056         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2057         // zoom into geographical coordinates.
2058         pointToLatLng: function (point, zoom) {
2059                 var scale = this.scale(zoom),
2060                     untransformedPoint = this.transformation.untransform(point, scale);
2061
2062                 return this.projection.unproject(untransformedPoint);
2063         },
2064
2065         // @method project(latlng: LatLng): Point
2066         // Projects geographical coordinates into coordinates in units accepted for
2067         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2068         project: function (latlng) {
2069                 return this.projection.project(latlng);
2070         },
2071
2072         // @method unproject(point: Point): LatLng
2073         // Given a projected coordinate returns the corresponding LatLng.
2074         // The inverse of `project`.
2075         unproject: function (point) {
2076                 return this.projection.unproject(point);
2077         },
2078
2079         // @method scale(zoom: Number): Number
2080         // Returns the scale used when transforming projected coordinates into
2081         // pixel coordinates for a particular zoom. For example, it returns
2082         // `256 * 2^zoom` for Mercator-based CRS.
2083         scale: function (zoom) {
2084                 return 256 * Math.pow(2, zoom);
2085         },
2086
2087         // @method zoom(scale: Number): Number
2088         // Inverse of `scale()`, returns the zoom level corresponding to a scale
2089         // factor of `scale`.
2090         zoom: function (scale) {
2091                 return Math.log(scale / 256) / Math.LN2;
2092         },
2093
2094         // @method getProjectedBounds(zoom: Number): Bounds
2095         // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2096         getProjectedBounds: function (zoom) {
2097                 if (this.infinite) { return null; }
2098
2099                 var b = this.projection.bounds,
2100                     s = this.scale(zoom),
2101                     min = this.transformation.transform(b.min, s),
2102                     max = this.transformation.transform(b.max, s);
2103
2104                 return L.bounds(min, max);
2105         },
2106
2107         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2108         // Returns the distance between two geographical coordinates.
2109
2110         // @property code: String
2111         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2112         //
2113         // @property wrapLng: Number[]
2114         // An array of two numbers defining whether the longitude (horizontal) coordinate
2115         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2116         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2117         //
2118         // @property wrapLat: Number[]
2119         // Like `wrapLng`, but for the latitude (vertical) axis.
2120
2121         // wrapLng: [min, max],
2122         // wrapLat: [min, max],
2123
2124         // @property infinite: Boolean
2125         // If true, the coordinate space will be unbounded (infinite in both axes)
2126         infinite: false,
2127
2128         // @method wrapLatLng(latlng: LatLng): LatLng
2129         // Returns a `LatLng` where lat and lng has been wrapped according to the
2130         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2131         wrapLatLng: function (latlng) {
2132                 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2133                     lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2134                     alt = latlng.alt;
2135
2136                 return L.latLng(lat, lng, alt);
2137         }
2138 };
2139
2140
2141
2142 /*
2143  * @namespace CRS
2144  * @crs L.CRS.Simple
2145  *
2146  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2147  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2148  * axis should still be inverted (going from bottom to top). `distance()` returns
2149  * simple euclidean distance.
2150  */
2151
2152 L.CRS.Simple = L.extend({}, L.CRS, {
2153         projection: L.Projection.LonLat,
2154         transformation: new L.Transformation(1, 0, -1, 0),
2155
2156         scale: function (zoom) {
2157                 return Math.pow(2, zoom);
2158         },
2159
2160         zoom: function (scale) {
2161                 return Math.log(scale) / Math.LN2;
2162         },
2163
2164         distance: function (latlng1, latlng2) {
2165                 var dx = latlng2.lng - latlng1.lng,
2166                     dy = latlng2.lat - latlng1.lat;
2167
2168                 return Math.sqrt(dx * dx + dy * dy);
2169         },
2170
2171         infinite: true
2172 });
2173
2174
2175
2176 /*
2177  * @namespace CRS
2178  * @crs L.CRS.Earth
2179  *
2180  * Serves as the base for CRS that are global such that they cover the earth.
2181  * Can only be used as the base for other CRS and cannot be used directly,
2182  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2183  * meters.
2184  */
2185
2186 L.CRS.Earth = L.extend({}, L.CRS, {
2187         wrapLng: [-180, 180],
2188
2189         // Mean Earth Radius, as recommended for use by
2190         // the International Union of Geodesy and Geophysics,
2191         // see http://rosettacode.org/wiki/Haversine_formula
2192         R: 6371000,
2193
2194         // distance between two geographical points using spherical law of cosines approximation
2195         distance: function (latlng1, latlng2) {
2196                 var rad = Math.PI / 180,
2197                     lat1 = latlng1.lat * rad,
2198                     lat2 = latlng2.lat * rad,
2199                     a = Math.sin(lat1) * Math.sin(lat2) +
2200                         Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2201
2202                 return this.R * Math.acos(Math.min(a, 1));
2203         }
2204 });
2205
2206
2207
2208 /*
2209  * @namespace CRS
2210  * @crs L.CRS.EPSG3857
2211  *
2212  * The most common CRS for online maps, used by almost all free and commercial
2213  * tile providers. Uses Spherical Mercator projection. Set in by default in
2214  * Map's `crs` option.
2215  */
2216
2217 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2218         code: 'EPSG:3857',
2219         projection: L.Projection.SphericalMercator,
2220
2221         transformation: (function () {
2222                 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2223                 return new L.Transformation(scale, 0.5, -scale, 0.5);
2224         }())
2225 });
2226
2227 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2228         code: 'EPSG:900913'
2229 });
2230
2231
2232
2233 /*
2234  * @namespace CRS
2235  * @crs L.CRS.EPSG4326
2236  *
2237  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2238  */
2239
2240 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2241         code: 'EPSG:4326',
2242         projection: L.Projection.LonLat,
2243         transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2244 });
2245
2246
2247
2248 /*
2249  * @class Map
2250  * @aka L.Map
2251  * @inherits Evented
2252  *
2253  * The central class of the API — it is used to create a map on a page and manipulate it.
2254  *
2255  * @example
2256  *
2257  * ```js
2258  * // initialize the map on the "map" div with a given center and zoom
2259  * var map = L.map('map', {
2260  *      center: [51.505, -0.09],
2261  *      zoom: 13
2262  * });
2263  * ```
2264  *
2265  */
2266
2267 L.Map = L.Evented.extend({
2268
2269         options: {
2270                 // @section Map State Options
2271                 // @option crs: CRS = L.CRS.EPSG3857
2272                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2273                 // sure what it means.
2274                 crs: L.CRS.EPSG3857,
2275
2276                 // @option center: LatLng = undefined
2277                 // Initial geographic center of the map
2278                 center: undefined,
2279
2280                 // @option zoom: Number = undefined
2281                 // Initial map zoom level
2282                 zoom: undefined,
2283
2284                 // @option minZoom: Number = undefined
2285                 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2286                 minZoom: undefined,
2287
2288                 // @option maxZoom: Number = undefined
2289                 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2290                 maxZoom: undefined,
2291
2292                 // @option layers: Layer[] = []
2293                 // Array of layers that will be added to the map initially
2294                 layers: [],
2295
2296                 // @option maxBounds: LatLngBounds = null
2297                 // When this option is set, the map restricts the view to the given
2298                 // geographical bounds, bouncing the user back when he tries to pan
2299                 // outside the view. To set the restriction dynamically, use
2300                 // [`setMaxBounds`](#map-setmaxbounds) method.
2301                 maxBounds: undefined,
2302
2303                 // @option renderer: Renderer = *
2304                 // The default method for drawing vector layers on the map. `L.SVG`
2305                 // or `L.Canvas` by default depending on browser support.
2306                 renderer: undefined,
2307
2308
2309                 // @section Animation Options
2310                 // @option fadeAnimation: Boolean = true
2311                 // Whether the tile fade animation is enabled. By default it's enabled
2312                 // in all browsers that support CSS3 Transitions except Android.
2313                 fadeAnimation: true,
2314
2315                 // @option markerZoomAnimation: Boolean = true
2316                 // Whether markers animate their zoom with the zoom animation, if disabled
2317                 // they will disappear for the length of the animation. By default it's
2318                 // enabled in all browsers that support CSS3 Transitions except Android.
2319                 markerZoomAnimation: true,
2320
2321                 // @option transform3DLimit: Number = 2^23
2322                 // Defines the maximum size of a CSS translation transform. The default
2323                 // value should not be changed unless a web browser positions layers in
2324                 // the wrong place after doing a large `panBy`.
2325                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2326
2327                 // @section Interaction Options
2328                 // @option zoomSnap: Number = 1
2329                 // Forces the map's zoom level to always be a multiple of this, particularly
2330                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2331                 // By default, the zoom level snaps to the nearest integer; lower values
2332                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2333                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2334                 zoomSnap: 1,
2335
2336                 // @option zoomDelta: Number = 1
2337                 // Controls how much the map's zoom level will change after a
2338                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2339                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2340                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2341                 zoomDelta: 1,
2342
2343                 // @option trackResize: Boolean = true
2344                 // Whether the map automatically handles browser window resize to update itself.
2345                 trackResize: true
2346         },
2347
2348         initialize: function (id, options) { // (HTMLElement or String, Object)
2349                 options = L.setOptions(this, options);
2350
2351                 this._initContainer(id);
2352                 this._initLayout();
2353
2354                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2355                 this._onResize = L.bind(this._onResize, this);
2356
2357                 this._initEvents();
2358
2359                 if (options.maxBounds) {
2360                         this.setMaxBounds(options.maxBounds);
2361                 }
2362
2363                 if (options.zoom !== undefined) {
2364                         this._zoom = this._limitZoom(options.zoom);
2365                 }
2366
2367                 if (options.center && options.zoom !== undefined) {
2368                         this.setView(L.latLng(options.center), options.zoom, {reset: true});
2369                 }
2370
2371                 this._handlers = [];
2372                 this._layers = {};
2373                 this._zoomBoundLayers = {};
2374                 this._sizeChanged = true;
2375
2376                 this.callInitHooks();
2377
2378                 this._addLayers(this.options.layers);
2379         },
2380
2381
2382         // @section Methods for modifying map state
2383
2384         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2385         // Sets the view of the map (geographical center and zoom) with the given
2386         // animation options.
2387         setView: function (center, zoom) {
2388                 // replaced by animation-powered implementation in Map.PanAnimation.js
2389                 zoom = zoom === undefined ? this.getZoom() : zoom;
2390                 this._resetView(L.latLng(center), zoom);
2391                 return this;
2392         },
2393
2394         // @method setZoom(zoom: Number, options: Zoom/pan options): this
2395         // Sets the zoom of the map.
2396         setZoom: function (zoom, options) {
2397                 if (!this._loaded) {
2398                         this._zoom = zoom;
2399                         return this;
2400                 }
2401                 return this.setView(this.getCenter(), zoom, {zoom: options});
2402         },
2403
2404         // @method zoomIn(delta?: Number, options?: Zoom options): this
2405         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2406         zoomIn: function (delta, options) {
2407                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2408                 return this.setZoom(this._zoom + delta, options);
2409         },
2410
2411         // @method zoomOut(delta?: Number, options?: Zoom options): this
2412         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2413         zoomOut: function (delta, options) {
2414                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2415                 return this.setZoom(this._zoom - delta, options);
2416         },
2417
2418         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2419         // Zooms the map while keeping a specified geographical point on the map
2420         // stationary (e.g. used internally for scroll zoom and double-click zoom).
2421         // @alternative
2422         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2423         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2424         setZoomAround: function (latlng, zoom, options) {
2425                 var scale = this.getZoomScale(zoom),
2426                     viewHalf = this.getSize().divideBy(2),
2427                     containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2428
2429                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2430                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2431
2432                 return this.setView(newCenter, zoom, {zoom: options});
2433         },
2434
2435         _getBoundsCenterZoom: function (bounds, options) {
2436
2437                 options = options || {};
2438                 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2439
2440                 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2441                     paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2442
2443                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2444
2445                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2446
2447                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2448
2449                     swPoint = this.project(bounds.getSouthWest(), zoom),
2450                     nePoint = this.project(bounds.getNorthEast(), zoom),
2451                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2452
2453                 return {
2454                         center: center,
2455                         zoom: zoom
2456                 };
2457         },
2458
2459         // @method fitBounds(bounds: LatLngBounds, options: fitBounds options): this
2460         // Sets a map view that contains the given geographical bounds with the
2461         // maximum zoom level possible.
2462         fitBounds: function (bounds, options) {
2463
2464                 bounds = L.latLngBounds(bounds);
2465
2466                 if (!bounds.isValid()) {
2467                         throw new Error('Bounds are not valid.');
2468                 }
2469
2470                 var target = this._getBoundsCenterZoom(bounds, options);
2471                 return this.setView(target.center, target.zoom, options);
2472         },
2473
2474         // @method fitWorld(options?: fitBounds options): this
2475         // Sets a map view that mostly contains the whole world with the maximum
2476         // zoom level possible.
2477         fitWorld: function (options) {
2478                 return this.fitBounds([[-90, -180], [90, 180]], options);
2479         },
2480
2481         // @method panTo(latlng: LatLng, options?: Pan options): this
2482         // Pans the map to a given center.
2483         panTo: function (center, options) { // (LatLng)
2484                 return this.setView(center, this._zoom, {pan: options});
2485         },
2486
2487         // @method panBy(offset: Point): this
2488         // Pans the map by a given number of pixels (animated).
2489         panBy: function (offset) { // (Point)
2490                 // replaced with animated panBy in Map.PanAnimation.js
2491                 this.fire('movestart');
2492
2493                 this._rawPanBy(L.point(offset));
2494
2495                 this.fire('move');
2496                 return this.fire('moveend');
2497         },
2498
2499         // @method setMaxBounds(bounds: Bounds): this
2500         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2501         setMaxBounds: function (bounds) {
2502                 bounds = L.latLngBounds(bounds);
2503
2504                 if (!bounds.isValid()) {
2505                         this.options.maxBounds = null;
2506                         return this.off('moveend', this._panInsideMaxBounds);
2507                 } else if (this.options.maxBounds) {
2508                         this.off('moveend', this._panInsideMaxBounds);
2509                 }
2510
2511                 this.options.maxBounds = bounds;
2512
2513                 if (this._loaded) {
2514                         this._panInsideMaxBounds();
2515                 }
2516
2517                 return this.on('moveend', this._panInsideMaxBounds);
2518         },
2519
2520         // @method setMinZoom(zoom: Number): this
2521         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2522         setMinZoom: function (zoom) {
2523                 this.options.minZoom = zoom;
2524
2525                 if (this._loaded && this.getZoom() < this.options.minZoom) {
2526                         return this.setZoom(zoom);
2527                 }
2528
2529                 return this;
2530         },
2531
2532         // @method setMaxZoom(zoom: Number): this
2533         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2534         setMaxZoom: function (zoom) {
2535                 this.options.maxZoom = zoom;
2536
2537                 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2538                         return this.setZoom(zoom);
2539                 }
2540
2541                 return this;
2542         },
2543
2544         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2545         // 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.
2546         panInsideBounds: function (bounds, options) {
2547                 this._enforcingBounds = true;
2548                 var center = this.getCenter(),
2549                     newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2550
2551                 if (!center.equals(newCenter)) {
2552                         this.panTo(newCenter, options);
2553                 }
2554
2555                 this._enforcingBounds = false;
2556                 return this;
2557         },
2558
2559         // @method invalidateSize(options: Zoom/Pan options): this
2560         // Checks if the map container size changed and updates the map if so —
2561         // call it after you've changed the map size dynamically, also animating
2562         // pan by default. If `options.pan` is `false`, panning will not occur.
2563         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2564         // that it doesn't happen often even if the method is called many
2565         // times in a row.
2566
2567         // @alternative
2568         // @method invalidateSize(animate: Boolean): this
2569         // Checks if the map container size changed and updates the map if so —
2570         // call it after you've changed the map size dynamically, also animating
2571         // pan by default.
2572         invalidateSize: function (options) {
2573                 if (!this._loaded) { return this; }
2574
2575                 options = L.extend({
2576                         animate: false,
2577                         pan: true
2578                 }, options === true ? {animate: true} : options);
2579
2580                 var oldSize = this.getSize();
2581                 this._sizeChanged = true;
2582                 this._lastCenter = null;
2583
2584                 var newSize = this.getSize(),
2585                     oldCenter = oldSize.divideBy(2).round(),
2586                     newCenter = newSize.divideBy(2).round(),
2587                     offset = oldCenter.subtract(newCenter);
2588
2589                 if (!offset.x && !offset.y) { return this; }
2590
2591                 if (options.animate && options.pan) {
2592                         this.panBy(offset);
2593
2594                 } else {
2595                         if (options.pan) {
2596                                 this._rawPanBy(offset);
2597                         }
2598
2599                         this.fire('move');
2600
2601                         if (options.debounceMoveend) {
2602                                 clearTimeout(this._sizeTimer);
2603                                 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2604                         } else {
2605                                 this.fire('moveend');
2606                         }
2607                 }
2608
2609                 // @section Map state change events
2610                 // @event resize: ResizeEvent
2611                 // Fired when the map is resized.
2612                 return this.fire('resize', {
2613                         oldSize: oldSize,
2614                         newSize: newSize
2615                 });
2616         },
2617
2618         // @section Methods for modifying map state
2619         // @method stop(): this
2620         // Stops the currently running `panTo` or `flyTo` animation, if any.
2621         stop: function () {
2622                 this.setZoom(this._limitZoom(this._zoom));
2623                 if (!this.options.zoomSnap) {
2624                         this.fire('viewreset');
2625                 }
2626                 return this._stop();
2627         },
2628
2629
2630         // TODO handler.addTo
2631         // TODO Appropiate docs section?
2632         // @section Other Methods
2633         // @method addHandler(name: String, HandlerClass: Function): this
2634         // Adds a new `Handler` to the map, given its name and constructor function.
2635         addHandler: function (name, HandlerClass) {
2636                 if (!HandlerClass) { return this; }
2637
2638                 var handler = this[name] = new HandlerClass(this);
2639
2640                 this._handlers.push(handler);
2641
2642                 if (this.options[name]) {
2643                         handler.enable();
2644                 }
2645
2646                 return this;
2647         },
2648
2649         // @method remove(): this
2650         // Destroys the map and clears all related event listeners.
2651         remove: function () {
2652
2653                 this._initEvents(true);
2654
2655                 if (this._containerId !== this._container._leaflet_id) {
2656                         throw new Error('Map container is being reused by another instance');
2657                 }
2658
2659                 try {
2660                         // throws error in IE6-8
2661                         delete this._container._leaflet_id;
2662                         delete this._containerId;
2663                 } catch (e) {
2664                         /*eslint-disable */
2665                         this._container._leaflet_id = undefined;
2666                         /*eslint-enable */
2667                         this._containerId = undefined;
2668                 }
2669
2670                 L.DomUtil.remove(this._mapPane);
2671
2672                 if (this._clearControlPos) {
2673                         this._clearControlPos();
2674                 }
2675
2676                 this._clearHandlers();
2677
2678                 if (this._loaded) {
2679                         // @section Map state change events
2680                         // @event unload: Event
2681                         // Fired when the map is destroyed with [remove](#map-remove) method.
2682                         this.fire('unload');
2683                 }
2684
2685                 for (var i in this._layers) {
2686                         this._layers[i].remove();
2687                 }
2688
2689                 return this;
2690         },
2691
2692         // @section Other Methods
2693         // @method createPane(name: String, container?: HTMLElement): HTMLElement
2694         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2695         // then returns it. The pane is created as a children of `container`, or
2696         // as a children of the main map pane if not set.
2697         createPane: function (name, container) {
2698                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
2699                     pane = L.DomUtil.create('div', className, container || this._mapPane);
2700
2701                 if (name) {
2702                         this._panes[name] = pane;
2703                 }
2704                 return pane;
2705         },
2706
2707         // @section Methods for Getting Map State
2708
2709         // @method getCenter(): LatLng
2710         // Returns the geographical center of the map view
2711         getCenter: function () {
2712                 this._checkIfLoaded();
2713
2714                 if (this._lastCenter && !this._moved()) {
2715                         return this._lastCenter;
2716                 }
2717                 return this.layerPointToLatLng(this._getCenterLayerPoint());
2718         },
2719
2720         // @method getZoom(): Number
2721         // Returns the current zoom level of the map view
2722         getZoom: function () {
2723                 return this._zoom;
2724         },
2725
2726         // @method getBounds(): LatLngBounds
2727         // Returns the geographical bounds visible in the current map view
2728         getBounds: function () {
2729                 var bounds = this.getPixelBounds(),
2730                     sw = this.unproject(bounds.getBottomLeft()),
2731                     ne = this.unproject(bounds.getTopRight());
2732
2733                 return new L.LatLngBounds(sw, ne);
2734         },
2735
2736         // @method getMinZoom(): Number
2737         // 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.
2738         getMinZoom: function () {
2739                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
2740         },
2741
2742         // @method getMaxZoom(): Number
2743         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
2744         getMaxZoom: function () {
2745                 return this.options.maxZoom === undefined ?
2746                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
2747                         this.options.maxZoom;
2748         },
2749
2750         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
2751         // Returns the maximum zoom level on which the given bounds fit to the map
2752         // view in its entirety. If `inside` (optional) is set to `true`, the method
2753         // instead returns the minimum zoom level on which the map view fits into
2754         // the given bounds in its entirety.
2755         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
2756                 bounds = L.latLngBounds(bounds);
2757                 padding = L.point(padding || [0, 0]);
2758
2759                 var zoom = this.getZoom() || 0,
2760                     min = this.getMinZoom(),
2761                     max = this.getMaxZoom(),
2762                     nw = bounds.getNorthWest(),
2763                     se = bounds.getSouthEast(),
2764                     size = this.getSize().subtract(padding),
2765                     boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)),
2766                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
2767
2768                 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
2769                 zoom = this.getScaleZoom(scale, zoom);
2770
2771                 if (snap) {
2772                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
2773                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
2774                 }
2775
2776                 return Math.max(min, Math.min(max, zoom));
2777         },
2778
2779         // @method getSize(): Point
2780         // Returns the current size of the map container (in pixels).
2781         getSize: function () {
2782                 if (!this._size || this._sizeChanged) {
2783                         this._size = new L.Point(
2784                                 this._container.clientWidth,
2785                                 this._container.clientHeight);
2786
2787                         this._sizeChanged = false;
2788                 }
2789                 return this._size.clone();
2790         },
2791
2792         // @method getPixelBounds(): Bounds
2793         // Returns the bounds of the current map view in projected pixel
2794         // coordinates (sometimes useful in layer and overlay implementations).
2795         getPixelBounds: function (center, zoom) {
2796                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
2797                 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
2798         },
2799
2800         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
2801         // the map pane? "left point of the map layer" can be confusing, specially
2802         // since there can be negative offsets.
2803         // @method getPixelOrigin(): Point
2804         // Returns the projected pixel coordinates of the top left point of
2805         // the map layer (useful in custom layer and overlay implementations).
2806         getPixelOrigin: function () {
2807                 this._checkIfLoaded();
2808                 return this._pixelOrigin;
2809         },
2810
2811         // @method getPixelWorldBounds(zoom?: Number): Bounds
2812         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
2813         // If `zoom` is omitted, the map's current zoom level is used.
2814         getPixelWorldBounds: function (zoom) {
2815                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
2816         },
2817
2818         // @section Other Methods
2819
2820         // @method getPane(pane: String|HTMLElement): HTMLElement
2821         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
2822         getPane: function (pane) {
2823                 return typeof pane === 'string' ? this._panes[pane] : pane;
2824         },
2825
2826         // @method getPanes(): Object
2827         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
2828         // the panes as values.
2829         getPanes: function () {
2830                 return this._panes;
2831         },
2832
2833         // @method getContainer: HTMLElement
2834         // Returns the HTML element that contains the map.
2835         getContainer: function () {
2836                 return this._container;
2837         },
2838
2839
2840         // @section Conversion Methods
2841
2842         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
2843         // Returns the scale factor to be applied to a map transition from zoom level
2844         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
2845         getZoomScale: function (toZoom, fromZoom) {
2846                 // TODO replace with universal implementation after refactoring projections
2847                 var crs = this.options.crs;
2848                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2849                 return crs.scale(toZoom) / crs.scale(fromZoom);
2850         },
2851
2852         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
2853         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
2854         // level and everything is scaled by a factor of `scale`. Inverse of
2855         // [`getZoomScale`](#map-getZoomScale).
2856         getScaleZoom: function (scale, fromZoom) {
2857                 var crs = this.options.crs;
2858                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
2859                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
2860                 return isNaN(zoom) ? Infinity : zoom;
2861         },
2862
2863         // @method project(latlng: LatLng, zoom: Number): Point
2864         // Projects a geographical coordinate `LatLng` according to the projection
2865         // of the map's CRS, then scales it according to `zoom` and the CRS's
2866         // `Transformation`. The result is pixel coordinate relative to
2867         // the CRS origin.
2868         project: function (latlng, zoom) {
2869                 zoom = zoom === undefined ? this._zoom : zoom;
2870                 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
2871         },
2872
2873         // @method unproject(point: Point, zoom: Number): LatLng
2874         // Inverse of [`project`](#map-project).
2875         unproject: function (point, zoom) {
2876                 zoom = zoom === undefined ? this._zoom : zoom;
2877                 return this.options.crs.pointToLatLng(L.point(point), zoom);
2878         },
2879
2880         // @method layerPointToLatLng(point: Point): LatLng
2881         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2882         // returns the corresponding geographical coordinate (for the current zoom level).
2883         layerPointToLatLng: function (point) {
2884                 var projectedPoint = L.point(point).add(this.getPixelOrigin());
2885                 return this.unproject(projectedPoint);
2886         },
2887
2888         // @method latLngToLayerPoint(latlng: LatLng): Point
2889         // Given a geographical coordinate, returns the corresponding pixel coordinate
2890         // relative to the [origin pixel](#map-getpixelorigin).
2891         latLngToLayerPoint: function (latlng) {
2892                 var projectedPoint = this.project(L.latLng(latlng))._round();
2893                 return projectedPoint._subtract(this.getPixelOrigin());
2894         },
2895
2896         // @method wrapLatLng(latlng: LatLng): LatLng
2897         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
2898         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
2899         // CRS's bounds.
2900         // By default this means longitude is wrapped around the dateline so its
2901         // value is between -180 and +180 degrees.
2902         wrapLatLng: function (latlng) {
2903                 return this.options.crs.wrapLatLng(L.latLng(latlng));
2904         },
2905
2906         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2907         // Returns the distance between two geographical coordinates according to
2908         // the map's CRS. By default this measures distance in meters.
2909         distance: function (latlng1, latlng2) {
2910                 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
2911         },
2912
2913         // @method containerPointToLayerPoint(point: Point): Point
2914         // Given a pixel coordinate relative to the map container, returns the corresponding
2915         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
2916         containerPointToLayerPoint: function (point) { // (Point)
2917                 return L.point(point).subtract(this._getMapPanePos());
2918         },
2919
2920         // @method layerPointToContainerPoint(point: Point): Point
2921         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
2922         // returns the corresponding pixel coordinate relative to the map container.
2923         layerPointToContainerPoint: function (point) { // (Point)
2924                 return L.point(point).add(this._getMapPanePos());
2925         },
2926
2927         // @method containerPointToLatLng(point: Point): Point
2928         // Given a pixel coordinate relative to the map container, returns
2929         // the corresponding geographical coordinate (for the current zoom level).
2930         containerPointToLatLng: function (point) {
2931                 var layerPoint = this.containerPointToLayerPoint(L.point(point));
2932                 return this.layerPointToLatLng(layerPoint);
2933         },
2934
2935         // @method latLngToContainerPoint(latlng: LatLng): Point
2936         // Given a geographical coordinate, returns the corresponding pixel coordinate
2937         // relative to the map container.
2938         latLngToContainerPoint: function (latlng) {
2939                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
2940         },
2941
2942         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
2943         // Given a MouseEvent object, returns the pixel coordinate relative to the
2944         // map container where the event took place.
2945         mouseEventToContainerPoint: function (e) {
2946                 return L.DomEvent.getMousePosition(e, this._container);
2947         },
2948
2949         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
2950         // Given a MouseEvent object, returns the pixel coordinate relative to
2951         // the [origin pixel](#map-getpixelorigin) where the event took place.
2952         mouseEventToLayerPoint: function (e) {
2953                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
2954         },
2955
2956         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
2957         // Given a MouseEvent object, returns geographical coordinate where the
2958         // event took place.
2959         mouseEventToLatLng: function (e) { // (MouseEvent)
2960                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
2961         },
2962
2963
2964         // map initialization methods
2965
2966         _initContainer: function (id) {
2967                 var container = this._container = L.DomUtil.get(id);
2968
2969                 if (!container) {
2970                         throw new Error('Map container not found.');
2971                 } else if (container._leaflet_id) {
2972                         throw new Error('Map container is already initialized.');
2973                 }
2974
2975                 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
2976                 this._containerId = L.Util.stamp(container);
2977         },
2978
2979         _initLayout: function () {
2980                 var container = this._container;
2981
2982                 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
2983
2984                 L.DomUtil.addClass(container, 'leaflet-container' +
2985                         (L.Browser.touch ? ' leaflet-touch' : '') +
2986                         (L.Browser.retina ? ' leaflet-retina' : '') +
2987                         (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
2988                         (L.Browser.safari ? ' leaflet-safari' : '') +
2989                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
2990
2991                 var position = L.DomUtil.getStyle(container, 'position');
2992
2993                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
2994                         container.style.position = 'relative';
2995                 }
2996
2997                 this._initPanes();
2998
2999                 if (this._initControlPos) {
3000                         this._initControlPos();
3001                 }
3002         },
3003
3004         _initPanes: function () {
3005                 var panes = this._panes = {};
3006                 this._paneRenderers = {};
3007
3008                 // @section
3009                 //
3010                 // Panes are DOM elements used to control the ordering of layers on the map. You
3011                 // can access panes with [`map.getPane`](#map-getpane) or
3012                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3013                 // [`map.createPane`](#map-createpane) method.
3014                 //
3015                 // Every map has the following default panes that differ only in zIndex.
3016                 //
3017                 // @pane mapPane: HTMLElement = 'auto'
3018                 // Pane that contains all other map panes
3019
3020                 this._mapPane = this.createPane('mapPane', this._container);
3021                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3022
3023                 // @pane tilePane: HTMLElement = 200
3024                 // Pane for `GridLayer`s and `TileLayer`s
3025                 this.createPane('tilePane');
3026                 // @pane overlayPane: HTMLElement = 400
3027                 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3028                 this.createPane('shadowPane');
3029                 // @pane shadowPane: HTMLElement = 500
3030                 // Pane for overlay shadows (e.g. `Marker` shadows)
3031                 this.createPane('overlayPane');
3032                 // @pane markerPane: HTMLElement = 600
3033                 // Pane for `Icon`s of `Marker`s
3034                 this.createPane('markerPane');
3035                 // @pane tooltipPane: HTMLElement = 650
3036                 // Pane for tooltip.
3037                 this.createPane('tooltipPane');
3038                 // @pane popupPane: HTMLElement = 700
3039                 // Pane for `Popup`s.
3040                 this.createPane('popupPane');
3041
3042                 if (!this.options.markerZoomAnimation) {
3043                         L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3044                         L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3045                 }
3046         },
3047
3048
3049         // private methods that modify map state
3050
3051         // @section Map state change events
3052         _resetView: function (center, zoom) {
3053                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3054
3055                 var loading = !this._loaded;
3056                 this._loaded = true;
3057                 zoom = this._limitZoom(zoom);
3058
3059                 this.fire('viewprereset');
3060
3061                 var zoomChanged = this._zoom !== zoom;
3062                 this
3063                         ._moveStart(zoomChanged)
3064                         ._move(center, zoom)
3065                         ._moveEnd(zoomChanged);
3066
3067                 // @event viewreset: Event
3068                 // Fired when the map needs to redraw its content (this usually happens
3069                 // on map zoom or load). Very useful for creating custom overlays.
3070                 this.fire('viewreset');
3071
3072                 // @event load: Event
3073                 // Fired when the map is initialized (when its center and zoom are set
3074                 // for the first time).
3075                 if (loading) {
3076                         this.fire('load');
3077                 }
3078         },
3079
3080         _moveStart: function (zoomChanged) {
3081                 // @event zoomstart: Event
3082                 // Fired when the map zoom is about to change (e.g. before zoom animation).
3083                 // @event movestart: Event
3084                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3085                 if (zoomChanged) {
3086                         this.fire('zoomstart');
3087                 }
3088                 return this.fire('movestart');
3089         },
3090
3091         _move: function (center, zoom, data) {
3092                 if (zoom === undefined) {
3093                         zoom = this._zoom;
3094                 }
3095                 var zoomChanged = this._zoom !== zoom;
3096
3097                 this._zoom = zoom;
3098                 this._lastCenter = center;
3099                 this._pixelOrigin = this._getNewPixelOrigin(center);
3100
3101                 // @event zoom: Event
3102                 // Fired repeatedly during any change in zoom level, including zoom
3103                 // and fly animations.
3104                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
3105                         this.fire('zoom', data);
3106                 }
3107
3108                 // @event move: Event
3109                 // Fired repeatedly during any movement of the map, including pan and
3110                 // fly animations.
3111                 return this.fire('move', data);
3112         },
3113
3114         _moveEnd: function (zoomChanged) {
3115                 // @event zoomend: Event
3116                 // Fired when the map has changed, after any animations.
3117                 if (zoomChanged) {
3118                         this.fire('zoomend');
3119                 }
3120
3121                 // @event moveend: Event
3122                 // Fired when the center of the map stops changing (e.g. user stopped
3123                 // dragging the map).
3124                 return this.fire('moveend');
3125         },
3126
3127         _stop: function () {
3128                 L.Util.cancelAnimFrame(this._flyToFrame);
3129                 if (this._panAnim) {
3130                         this._panAnim.stop();
3131                 }
3132                 return this;
3133         },
3134
3135         _rawPanBy: function (offset) {
3136                 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3137         },
3138
3139         _getZoomSpan: function () {
3140                 return this.getMaxZoom() - this.getMinZoom();
3141         },
3142
3143         _panInsideMaxBounds: function () {
3144                 if (!this._enforcingBounds) {
3145                         this.panInsideBounds(this.options.maxBounds);
3146                 }
3147         },
3148
3149         _checkIfLoaded: function () {
3150                 if (!this._loaded) {
3151                         throw new Error('Set map center and zoom first.');
3152                 }
3153         },
3154
3155         // DOM event handling
3156
3157         // @section Interaction events
3158         _initEvents: function (remove) {
3159                 if (!L.DomEvent) { return; }
3160
3161                 this._targets = {};
3162                 this._targets[L.stamp(this._container)] = this;
3163
3164                 var onOff = remove ? 'off' : 'on';
3165
3166                 // @event click: MouseEvent
3167                 // Fired when the user clicks (or taps) the map.
3168                 // @event dblclick: MouseEvent
3169                 // Fired when the user double-clicks (or double-taps) the map.
3170                 // @event mousedown: MouseEvent
3171                 // Fired when the user pushes the mouse button on the map.
3172                 // @event mouseup: MouseEvent
3173                 // Fired when the user releases the mouse button on the map.
3174                 // @event mouseover: MouseEvent
3175                 // Fired when the mouse enters the map.
3176                 // @event mouseout: MouseEvent
3177                 // Fired when the mouse leaves the map.
3178                 // @event mousemove: MouseEvent
3179                 // Fired while the mouse moves over the map.
3180                 // @event contextmenu: MouseEvent
3181                 // Fired when the user pushes the right mouse button on the map, prevents
3182                 // default browser context menu from showing if there are listeners on
3183                 // this event. Also fired on mobile when the user holds a single touch
3184                 // for a second (also called long press).
3185                 // @event keypress: KeyboardEvent
3186                 // Fired when the user presses a key from the keyboard while the map is focused.
3187                 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3188                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3189
3190                 if (this.options.trackResize) {
3191                         L.DomEvent[onOff](window, 'resize', this._onResize, this);
3192                 }
3193
3194                 if (L.Browser.any3d && this.options.transform3DLimit) {
3195                         this[onOff]('moveend', this._onMoveEnd);
3196                 }
3197         },
3198
3199         _onResize: function () {
3200                 L.Util.cancelAnimFrame(this._resizeRequest);
3201                 this._resizeRequest = L.Util.requestAnimFrame(
3202                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
3203         },
3204
3205         _onScroll: function () {
3206                 this._container.scrollTop  = 0;
3207                 this._container.scrollLeft = 0;
3208         },
3209
3210         _onMoveEnd: function () {
3211                 var pos = this._getMapPanePos();
3212                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3213                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3214                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3215                         this._resetView(this.getCenter(), this.getZoom());
3216                 }
3217         },
3218
3219         _findEventTargets: function (e, type) {
3220                 var targets = [],
3221                     target,
3222                     isHover = type === 'mouseout' || type === 'mouseover',
3223                     src = e.target || e.srcElement,
3224                     dragging = false;
3225
3226                 while (src) {
3227                         target = this._targets[L.stamp(src)];
3228                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3229                                 // Prevent firing click after you just dragged an object.
3230                                 dragging = true;
3231                                 break;
3232                         }
3233                         if (target && target.listens(type, true)) {
3234                                 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3235                                 targets.push(target);
3236                                 if (isHover) { break; }
3237                         }
3238                         if (src === this._container) { break; }
3239                         src = src.parentNode;
3240                 }
3241                 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3242                         targets = [this];
3243                 }
3244                 return targets;
3245         },
3246
3247         _handleDOMEvent: function (e) {
3248                 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3249
3250                 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3251
3252                 if (type === 'mousedown') {
3253                         // prevents outline when clicking on keyboard-focusable element
3254                         L.DomUtil.preventOutline(e.target || e.srcElement);
3255                 }
3256
3257                 this._fireDOMEvent(e, type);
3258         },
3259
3260         _fireDOMEvent: function (e, type, targets) {
3261
3262                 if (e.type === 'click') {
3263                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3264                         // @event preclick: MouseEvent
3265                         // Fired before mouse click on the map (sometimes useful when you
3266                         // want something to happen on click before any existing click
3267                         // handlers start running).
3268                         var synth = L.Util.extend({}, e);
3269                         synth.type = 'preclick';
3270                         this._fireDOMEvent(synth, synth.type, targets);
3271                 }
3272
3273                 if (e._stopped) { return; }
3274
3275                 // Find the layer the event is propagating from and its parents.
3276                 targets = (targets || []).concat(this._findEventTargets(e, type));
3277
3278                 if (!targets.length) { return; }
3279
3280                 var target = targets[0];
3281                 if (type === 'contextmenu' && target.listens(type, true)) {
3282                         L.DomEvent.preventDefault(e);
3283                 }
3284
3285                 var data = {
3286                         originalEvent: e
3287                 };
3288
3289                 if (e.type !== 'keypress') {
3290                         var isMarker = target instanceof L.Marker;
3291                         data.containerPoint = isMarker ?
3292                                         this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3293                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3294                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3295                 }
3296
3297                 for (var i = 0; i < targets.length; i++) {
3298                         targets[i].fire(type, data, true);
3299                         if (data.originalEvent._stopped ||
3300                                 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3301                 }
3302         },
3303
3304         _draggableMoved: function (obj) {
3305                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3306                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3307         },
3308
3309         _clearHandlers: function () {
3310                 for (var i = 0, len = this._handlers.length; i < len; i++) {
3311                         this._handlers[i].disable();
3312                 }
3313         },
3314
3315         // @section Other Methods
3316
3317         // @method whenReady(fn: Function, context?: Object): this
3318         // Runs the given function `fn` when the map gets initialized with
3319         // a view (center and zoom) and at least one layer, or immediately
3320         // if it's already initialized, optionally passing a function context.
3321         whenReady: function (callback, context) {
3322                 if (this._loaded) {
3323                         callback.call(context || this, {target: this});
3324                 } else {
3325                         this.on('load', callback, context);
3326                 }
3327                 return this;
3328         },
3329
3330
3331         // private methods for getting map state
3332
3333         _getMapPanePos: function () {
3334                 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3335         },
3336
3337         _moved: function () {
3338                 var pos = this._getMapPanePos();
3339                 return pos && !pos.equals([0, 0]);
3340         },
3341
3342         _getTopLeftPoint: function (center, zoom) {
3343                 var pixelOrigin = center && zoom !== undefined ?
3344                         this._getNewPixelOrigin(center, zoom) :
3345                         this.getPixelOrigin();
3346                 return pixelOrigin.subtract(this._getMapPanePos());
3347         },
3348
3349         _getNewPixelOrigin: function (center, zoom) {
3350                 var viewHalf = this.getSize()._divideBy(2);
3351                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3352         },
3353
3354         _latLngToNewLayerPoint: function (latlng, zoom, center) {
3355                 var topLeft = this._getNewPixelOrigin(center, zoom);
3356                 return this.project(latlng, zoom)._subtract(topLeft);
3357         },
3358
3359         // layer point of the current center
3360         _getCenterLayerPoint: function () {
3361                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3362         },
3363
3364         // offset of the specified place to the current center in pixels
3365         _getCenterOffset: function (latlng) {
3366                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3367         },
3368
3369         // adjust center for view to get inside bounds
3370         _limitCenter: function (center, zoom, bounds) {
3371
3372                 if (!bounds) { return center; }
3373
3374                 var centerPoint = this.project(center, zoom),
3375                     viewHalf = this.getSize().divideBy(2),
3376                     viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3377                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3378
3379                 // If offset is less than a pixel, ignore.
3380                 // This prevents unstable projections from getting into
3381                 // an infinite loop of tiny offsets.
3382                 if (offset.round().equals([0, 0])) {
3383                         return center;
3384                 }
3385
3386                 return this.unproject(centerPoint.add(offset), zoom);
3387         },
3388
3389         // adjust offset for view to get inside bounds
3390         _limitOffset: function (offset, bounds) {
3391                 if (!bounds) { return offset; }
3392
3393                 var viewBounds = this.getPixelBounds(),
3394                     newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3395
3396                 return offset.add(this._getBoundsOffset(newBounds, bounds));
3397         },
3398
3399         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3400         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3401                 var projectedMaxBounds = L.bounds(
3402                         this.project(maxBounds.getNorthEast(), zoom),
3403                         this.project(maxBounds.getSouthWest(), zoom)
3404                     ),
3405                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3406                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3407
3408                     dx = this._rebound(minOffset.x, -maxOffset.x),
3409                     dy = this._rebound(minOffset.y, -maxOffset.y);
3410
3411                 return new L.Point(dx, dy);
3412         },
3413
3414         _rebound: function (left, right) {
3415                 return left + right > 0 ?
3416                         Math.round(left - right) / 2 :
3417                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3418         },
3419
3420         _limitZoom: function (zoom) {
3421                 var min = this.getMinZoom(),
3422                     max = this.getMaxZoom(),
3423                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3424                 if (snap) {
3425                         zoom = Math.round(zoom / snap) * snap;
3426                 }
3427                 return Math.max(min, Math.min(max, zoom));
3428         }
3429 });
3430
3431 // @section
3432
3433 // @factory L.map(id: String, options?: Map options)
3434 // Instantiates a map object given the DOM ID of a `<div>` element
3435 // and optionally an object literal with `Map options`.
3436 //
3437 // @alternative
3438 // @factory L.map(el: HTMLElement, options?: Map options)
3439 // Instantiates a map object given an instance of a `<div>` HTML element
3440 // and optionally an object literal with `Map options`.
3441 L.map = function (id, options) {
3442         return new L.Map(id, options);
3443 };
3444
3445
3446
3447
3448 /*
3449  * @class Layer
3450  * @inherits Evented
3451  * @aka L.Layer
3452  * @aka ILayer
3453  *
3454  * A set of methods from the Layer base class that all Leaflet layers use.
3455  * Inherits all methods, options and events from `L.Evented`.
3456  *
3457  * @example
3458  *
3459  * ```js
3460  * var layer = L.Marker(latlng).addTo(map);
3461  * layer.addTo(map);
3462  * layer.remove();
3463  * ```
3464  *
3465  * @event add: Event
3466  * Fired after the layer is added to a map
3467  *
3468  * @event remove: Event
3469  * Fired after the layer is removed from a map
3470  */
3471
3472
3473 L.Layer = L.Evented.extend({
3474
3475         // Classes extending `L.Layer` will inherit the following options:
3476         options: {
3477                 // @option pane: String = 'overlayPane'
3478                 // 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.
3479                 pane: 'overlayPane',
3480                 nonBubblingEvents: []  // Array of events that should not be bubbled to DOM parents (like the map)
3481         },
3482
3483         /* @section
3484          * Classes extending `L.Layer` will inherit the following methods:
3485          *
3486          * @method addTo(map: Map): this
3487          * Adds the layer to the given map
3488          */
3489         addTo: function (map) {
3490                 map.addLayer(this);
3491                 return this;
3492         },
3493
3494         // @method remove: this
3495         // Removes the layer from the map it is currently active on.
3496         remove: function () {
3497                 return this.removeFrom(this._map || this._mapToAdd);
3498         },
3499
3500         // @method removeFrom(map: Map): this
3501         // Removes the layer from the given map
3502         removeFrom: function (obj) {
3503                 if (obj) {
3504                         obj.removeLayer(this);
3505                 }
3506                 return this;
3507         },
3508
3509         // @method getPane(name? : String): HTMLElement
3510         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3511         getPane: function (name) {
3512                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3513         },
3514
3515         addInteractiveTarget: function (targetEl) {
3516                 this._map._targets[L.stamp(targetEl)] = this;
3517                 return this;
3518         },
3519
3520         removeInteractiveTarget: function (targetEl) {
3521                 delete this._map._targets[L.stamp(targetEl)];
3522                 return this;
3523         },
3524
3525         _layerAdd: function (e) {
3526                 var map = e.target;
3527
3528                 // check in case layer gets added and then removed before the map is ready
3529                 if (!map.hasLayer(this)) { return; }
3530
3531                 this._map = map;
3532                 this._zoomAnimated = map._zoomAnimated;
3533
3534                 if (this.getEvents) {
3535                         var events = this.getEvents();
3536                         map.on(events, this);
3537                         this.once('remove', function () {
3538                                 map.off(events, this);
3539                         }, this);
3540                 }
3541
3542                 this.onAdd(map);
3543
3544                 if (this.getAttribution && this._map.attributionControl) {
3545                         this._map.attributionControl.addAttribution(this.getAttribution());
3546                 }
3547
3548                 this.fire('add');
3549                 map.fire('layeradd', {layer: this});
3550         }
3551 });
3552
3553 /* @section Extension methods
3554  * @uninheritable
3555  *
3556  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
3557  *
3558  * @method onAdd(map: Map): this
3559  * 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).
3560  *
3561  * @method onRemove(map: Map): this
3562  * 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).
3563  *
3564  * @method getEvents(): Object
3565  * 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.
3566  *
3567  * @method getAttribution(): String
3568  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
3569  *
3570  * @method beforeAdd(map: Map): this
3571  * 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.
3572  */
3573
3574
3575 /* @namespace Map
3576  * @section Layer events
3577  *
3578  * @event layeradd: LayerEvent
3579  * Fired when a new layer is added to the map.
3580  *
3581  * @event layerremove: LayerEvent
3582  * Fired when some layer is removed from the map
3583  *
3584  * @section Methods for Layers and Controls
3585  */
3586 L.Map.include({
3587         // @method addLayer(layer: Layer): this
3588         // Adds the given layer to the map
3589         addLayer: function (layer) {
3590                 var id = L.stamp(layer);
3591                 if (this._layers[id]) { return this; }
3592                 this._layers[id] = layer;
3593
3594                 layer._mapToAdd = this;
3595
3596                 if (layer.beforeAdd) {
3597                         layer.beforeAdd(this);
3598                 }
3599
3600                 this.whenReady(layer._layerAdd, layer);
3601
3602                 return this;
3603         },
3604
3605         // @method removeLayer(layer: Layer): this
3606         // Removes the given layer from the map.
3607         removeLayer: function (layer) {
3608                 var id = L.stamp(layer);
3609
3610                 if (!this._layers[id]) { return this; }
3611
3612                 if (this._loaded) {
3613                         layer.onRemove(this);
3614                 }
3615
3616                 if (layer.getAttribution && this.attributionControl) {
3617                         this.attributionControl.removeAttribution(layer.getAttribution());
3618                 }
3619
3620                 delete this._layers[id];
3621
3622                 if (this._loaded) {
3623                         this.fire('layerremove', {layer: layer});
3624                         layer.fire('remove');
3625                 }
3626
3627                 layer._map = layer._mapToAdd = null;
3628
3629                 return this;
3630         },
3631
3632         // @method hasLayer(layer: Layer): Boolean
3633         // Returns `true` if the given layer is currently added to the map
3634         hasLayer: function (layer) {
3635                 return !!layer && (L.stamp(layer) in this._layers);
3636         },
3637
3638         /* @method eachLayer(fn: Function, context?: Object): this
3639          * Iterates over the layers of the map, optionally specifying context of the iterator function.
3640          * ```
3641          * map.eachLayer(function(layer){
3642          *     layer.bindPopup('Hello');
3643          * });
3644          * ```
3645          */
3646         eachLayer: function (method, context) {
3647                 for (var i in this._layers) {
3648                         method.call(context, this._layers[i]);
3649                 }
3650                 return this;
3651         },
3652
3653         _addLayers: function (layers) {
3654                 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
3655
3656                 for (var i = 0, len = layers.length; i < len; i++) {
3657                         this.addLayer(layers[i]);
3658                 }
3659         },
3660
3661         _addZoomLimit: function (layer) {
3662                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
3663                         this._zoomBoundLayers[L.stamp(layer)] = layer;
3664                         this._updateZoomLevels();
3665                 }
3666         },
3667
3668         _removeZoomLimit: function (layer) {
3669                 var id = L.stamp(layer);
3670
3671                 if (this._zoomBoundLayers[id]) {
3672                         delete this._zoomBoundLayers[id];
3673                         this._updateZoomLevels();
3674                 }
3675         },
3676
3677         _updateZoomLevels: function () {
3678                 var minZoom = Infinity,
3679                     maxZoom = -Infinity,
3680                     oldZoomSpan = this._getZoomSpan();
3681
3682                 for (var i in this._zoomBoundLayers) {
3683                         var options = this._zoomBoundLayers[i].options;
3684
3685                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
3686                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
3687                 }
3688
3689                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
3690                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
3691
3692                 // @section Map state change events
3693                 // @event zoomlevelschange: Event
3694                 // Fired when the number of zoomlevels on the map is changed due
3695                 // to adding or removing a layer.
3696                 if (oldZoomSpan !== this._getZoomSpan()) {
3697                         this.fire('zoomlevelschange');
3698                 }
3699         }
3700 });
3701
3702
3703
3704 /*
3705  * @namespace Projection
3706  * @projection L.Projection.Mercator
3707  *
3708  * 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.
3709  */
3710
3711 L.Projection.Mercator = {
3712         R: 6378137,
3713         R_MINOR: 6356752.314245179,
3714
3715         bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
3716
3717         project: function (latlng) {
3718                 var d = Math.PI / 180,
3719                     r = this.R,
3720                     y = latlng.lat * d,
3721                     tmp = this.R_MINOR / r,
3722                     e = Math.sqrt(1 - tmp * tmp),
3723                     con = e * Math.sin(y);
3724
3725                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
3726                 y = -r * Math.log(Math.max(ts, 1E-10));
3727
3728                 return new L.Point(latlng.lng * d * r, y);
3729         },
3730
3731         unproject: function (point) {
3732                 var d = 180 / Math.PI,
3733                     r = this.R,
3734                     tmp = this.R_MINOR / r,
3735                     e = Math.sqrt(1 - tmp * tmp),
3736                     ts = Math.exp(-point.y / r),
3737                     phi = Math.PI / 2 - 2 * Math.atan(ts);
3738
3739                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
3740                         con = e * Math.sin(phi);
3741                         con = Math.pow((1 - con) / (1 + con), e / 2);
3742                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
3743                         phi += dphi;
3744                 }
3745
3746                 return new L.LatLng(phi * d, point.x * d / r);
3747         }
3748 };
3749
3750
3751
3752 /*
3753  * @namespace CRS
3754  * @crs L.CRS.EPSG3395
3755  *
3756  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
3757  */
3758
3759 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
3760         code: 'EPSG:3395',
3761         projection: L.Projection.Mercator,
3762
3763         transformation: (function () {
3764                 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
3765                 return new L.Transformation(scale, 0.5, -scale, 0.5);
3766         }())
3767 });
3768
3769
3770
3771 /*
3772  * @class GridLayer
3773  * @inherits Layer
3774  * @aka L.GridLayer
3775  *
3776  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
3777  * 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.
3778  *
3779  *
3780  * @section Synchronous usage
3781  * @example
3782  *
3783  * 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.
3784  *
3785  * ```js
3786  * var CanvasLayer = L.GridLayer.extend({
3787  *     createTile: function(coords){
3788  *         // create a <canvas> element for drawing
3789  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3790  *
3791  *         // setup tile width and height according to the options
3792  *         var size = this.getTileSize();
3793  *         tile.width = size.x;
3794  *         tile.height = size.y;
3795  *
3796  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
3797  *         var ctx = tile.getContext('2d');
3798  *
3799  *         // return the tile so it can be rendered on screen
3800  *         return tile;
3801  *     }
3802  * });
3803  * ```
3804  *
3805  * @section Asynchronous usage
3806  * @example
3807  *
3808  * 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.
3809  *
3810  * ```js
3811  * var CanvasLayer = L.GridLayer.extend({
3812  *     createTile: function(coords, done){
3813  *         var error;
3814  *
3815  *         // create a <canvas> element for drawing
3816  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
3817  *
3818  *         // setup tile width and height according to the options
3819  *         var size = this.getTileSize();
3820  *         tile.width = size.x;
3821  *         tile.height = size.y;
3822  *
3823  *         // draw something asynchronously and pass the tile to the done() callback
3824  *         setTimeout(function() {
3825  *             done(error, tile);
3826  *         }, 1000);
3827  *
3828  *         return tile;
3829  *     }
3830  * });
3831  * ```
3832  *
3833  * @section
3834  */
3835
3836
3837 L.GridLayer = L.Layer.extend({
3838
3839         // @section
3840         // @aka GridLayer options
3841         options: {
3842                 // @option tileSize: Number|Point = 256
3843                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
3844                 tileSize: 256,
3845
3846                 // @option opacity: Number = 1.0
3847                 // Opacity of the tiles. Can be used in the `createTile()` function.
3848                 opacity: 1,
3849
3850                 // @option updateWhenIdle: Boolean = depends
3851                 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
3852                 updateWhenIdle: L.Browser.mobile,
3853
3854                 // @option updateWhenZooming: Boolean = true
3855                 // 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.
3856                 updateWhenZooming: true,
3857
3858                 // @option updateInterval: Number = 200
3859                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
3860                 updateInterval: 200,
3861
3862                 // @option attribution: String = null
3863                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3864                 attribution: null,
3865
3866                 // @option zIndex: Number = 1
3867                 // The explicit zIndex of the tile layer.
3868                 zIndex: 1,
3869
3870                 // @option bounds: LatLngBounds = undefined
3871                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
3872                 bounds: null,
3873
3874                 // @option minZoom: Number = 0
3875                 // The minimum zoom level that tiles will be loaded at. By default the entire map.
3876                 minZoom: 0,
3877
3878                 // @option maxZoom: Number = undefined
3879                 // The maximum zoom level that tiles will be loaded at.
3880                 maxZoom: undefined,
3881
3882                 // @option noWrap: Boolean = false
3883                 // Whether the layer is wrapped around the antimeridian. If `true`, the
3884                 // GridLayer will only be displayed once at low zoom levels. Has no
3885                 // effect when the [map CRS](#map-crs) doesn't wrap around.
3886                 noWrap: false,
3887
3888                 // @option pane: String = 'tilePane'
3889                 // `Map pane` where the grid layer will be added.
3890                 pane: 'tilePane',
3891
3892                 // @option className: String = ''
3893                 // A custom class name to assign to the tile layer. Empty by default.
3894                 className: '',
3895
3896                 // @option keepBuffer: Number = 2
3897                 // When panning the map, keep this many rows and columns of tiles before unloading them.
3898                 keepBuffer: 2
3899         },
3900
3901         initialize: function (options) {
3902                 L.setOptions(this, options);
3903         },
3904
3905         onAdd: function () {
3906                 this._initContainer();
3907
3908                 this._levels = {};
3909                 this._tiles = {};
3910
3911                 this._resetView();
3912                 this._update();
3913         },
3914
3915         beforeAdd: function (map) {
3916                 map._addZoomLimit(this);
3917         },
3918
3919         onRemove: function (map) {
3920                 this._removeAllTiles();
3921                 L.DomUtil.remove(this._container);
3922                 map._removeZoomLimit(this);
3923                 this._container = null;
3924                 this._tileZoom = null;
3925         },
3926
3927         // @method bringToFront: this
3928         // Brings the tile layer to the top of all tile layers.
3929         bringToFront: function () {
3930                 if (this._map) {
3931                         L.DomUtil.toFront(this._container);
3932                         this._setAutoZIndex(Math.max);
3933                 }
3934                 return this;
3935         },
3936
3937         // @method bringToBack: this
3938         // Brings the tile layer to the bottom of all tile layers.
3939         bringToBack: function () {
3940                 if (this._map) {
3941                         L.DomUtil.toBack(this._container);
3942                         this._setAutoZIndex(Math.min);
3943                 }
3944                 return this;
3945         },
3946
3947         // @method getAttribution: String
3948         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3949         getAttribution: function () {
3950                 return this.options.attribution;
3951         },
3952
3953         // @method getContainer: HTMLElement
3954         // Returns the HTML element that contains the tiles for this layer.
3955         getContainer: function () {
3956                 return this._container;
3957         },
3958
3959         // @method setOpacity(opacity: Number): this
3960         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
3961         setOpacity: function (opacity) {
3962                 this.options.opacity = opacity;
3963                 this._updateOpacity();
3964                 return this;
3965         },
3966
3967         // @method setZIndex(zIndex: Number): this
3968         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
3969         setZIndex: function (zIndex) {
3970                 this.options.zIndex = zIndex;
3971                 this._updateZIndex();
3972
3973                 return this;
3974         },
3975
3976         // @method isLoading: Boolean
3977         // Returns `true` if any tile in the grid layer has not finished loading.
3978         isLoading: function () {
3979                 return this._loading;
3980         },
3981
3982         // @method redraw: this
3983         // Causes the layer to clear all the tiles and request them again.
3984         redraw: function () {
3985                 if (this._map) {
3986                         this._removeAllTiles();
3987                         this._update();
3988                 }
3989                 return this;
3990         },
3991
3992         getEvents: function () {
3993                 var events = {
3994                         viewprereset: this._invalidateAll,
3995                         viewreset: this._resetView,
3996                         zoom: this._resetView,
3997                         moveend: this._onMoveEnd
3998                 };
3999
4000                 if (!this.options.updateWhenIdle) {
4001                         // update tiles on move, but not more often than once per given interval
4002                         if (!this._onMove) {
4003                                 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4004                         }
4005
4006                         events.move = this._onMove;
4007                 }
4008
4009                 if (this._zoomAnimated) {
4010                         events.zoomanim = this._animateZoom;
4011                 }
4012
4013                 return events;
4014         },
4015
4016         // @section Extension methods
4017         // Layers extending `GridLayer` shall reimplement the following method.
4018         // @method createTile(coords: Object, done?: Function): HTMLElement
4019         // Called only internally, must be overriden by classes extending `GridLayer`.
4020         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4021         // is specified, it must be called when the tile has finished loading and drawing.
4022         createTile: function () {
4023                 return document.createElement('div');
4024         },
4025
4026         // @section
4027         // @method getTileSize: Point
4028         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4029         getTileSize: function () {
4030                 var s = this.options.tileSize;
4031                 return s instanceof L.Point ? s : new L.Point(s, s);
4032         },
4033
4034         _updateZIndex: function () {
4035                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4036                         this._container.style.zIndex = this.options.zIndex;
4037                 }
4038         },
4039
4040         _setAutoZIndex: function (compare) {
4041                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4042
4043                 var layers = this.getPane().children,
4044                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4045
4046                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4047
4048                         zIndex = layers[i].style.zIndex;
4049
4050                         if (layers[i] !== this._container && zIndex) {
4051                                 edgeZIndex = compare(edgeZIndex, +zIndex);
4052                         }
4053                 }
4054
4055                 if (isFinite(edgeZIndex)) {
4056                         this.options.zIndex = edgeZIndex + compare(-1, 1);
4057                         this._updateZIndex();
4058                 }
4059         },
4060
4061         _updateOpacity: function () {
4062                 if (!this._map) { return; }
4063
4064                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4065                 if (L.Browser.ielt9) { return; }
4066
4067                 L.DomUtil.setOpacity(this._container, this.options.opacity);
4068
4069                 var now = +new Date(),
4070                     nextFrame = false,
4071                     willPrune = false;
4072
4073                 for (var key in this._tiles) {
4074                         var tile = this._tiles[key];
4075                         if (!tile.current || !tile.loaded) { continue; }
4076
4077                         var fade = Math.min(1, (now - tile.loaded) / 200);
4078
4079                         L.DomUtil.setOpacity(tile.el, fade);
4080                         if (fade < 1) {
4081                                 nextFrame = true;
4082                         } else {
4083                                 if (tile.active) { willPrune = true; }
4084                                 tile.active = true;
4085                         }
4086                 }
4087
4088                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4089
4090                 if (nextFrame) {
4091                         L.Util.cancelAnimFrame(this._fadeFrame);
4092                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4093                 }
4094         },
4095
4096         _initContainer: function () {
4097                 if (this._container) { return; }
4098
4099                 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4100                 this._updateZIndex();
4101
4102                 if (this.options.opacity < 1) {
4103                         this._updateOpacity();
4104                 }
4105
4106                 this.getPane().appendChild(this._container);
4107         },
4108
4109         _updateLevels: function () {
4110
4111                 var zoom = this._tileZoom,
4112                     maxZoom = this.options.maxZoom;
4113
4114                 if (zoom === undefined) { return undefined; }
4115
4116                 for (var z in this._levels) {
4117                         if (this._levels[z].el.children.length || z === zoom) {
4118                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4119                         } else {
4120                                 L.DomUtil.remove(this._levels[z].el);
4121                                 this._removeTilesAtZoom(z);
4122                                 delete this._levels[z];
4123                         }
4124                 }
4125
4126                 var level = this._levels[zoom],
4127                     map = this._map;
4128
4129                 if (!level) {
4130                         level = this._levels[zoom] = {};
4131
4132                         level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4133                         level.el.style.zIndex = maxZoom;
4134
4135                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
4136                         level.zoom = zoom;
4137
4138                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
4139
4140                         // force the browser to consider the newly added element for transition
4141                         L.Util.falseFn(level.el.offsetWidth);
4142                 }
4143
4144                 this._level = level;
4145
4146                 return level;
4147         },
4148
4149         _pruneTiles: function () {
4150                 if (!this._map) {
4151                         return;
4152                 }
4153
4154                 var key, tile;
4155
4156                 var zoom = this._map.getZoom();
4157                 if (zoom > this.options.maxZoom ||
4158                         zoom < this.options.minZoom) {
4159                         this._removeAllTiles();
4160                         return;
4161                 }
4162
4163                 for (key in this._tiles) {
4164                         tile = this._tiles[key];
4165                         tile.retain = tile.current;
4166                 }
4167
4168                 for (key in this._tiles) {
4169                         tile = this._tiles[key];
4170                         if (tile.current && !tile.active) {
4171                                 var coords = tile.coords;
4172                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
4173                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
4174                                 }
4175                         }
4176                 }
4177
4178                 for (key in this._tiles) {
4179                         if (!this._tiles[key].retain) {
4180                                 this._removeTile(key);
4181                         }
4182                 }
4183         },
4184
4185         _removeTilesAtZoom: function (zoom) {
4186                 for (var key in this._tiles) {
4187                         if (this._tiles[key].coords.z !== zoom) {
4188                                 continue;
4189                         }
4190                         this._removeTile(key);
4191                 }
4192         },
4193
4194         _removeAllTiles: function () {
4195                 for (var key in this._tiles) {
4196                         this._removeTile(key);
4197                 }
4198         },
4199
4200         _invalidateAll: function () {
4201                 for (var z in this._levels) {
4202                         L.DomUtil.remove(this._levels[z].el);
4203                         delete this._levels[z];
4204                 }
4205                 this._removeAllTiles();
4206
4207                 this._tileZoom = null;
4208         },
4209
4210         _retainParent: function (x, y, z, minZoom) {
4211                 var x2 = Math.floor(x / 2),
4212                     y2 = Math.floor(y / 2),
4213                     z2 = z - 1,
4214                     coords2 = new L.Point(+x2, +y2);
4215                 coords2.z = +z2;
4216
4217                 var key = this._tileCoordsToKey(coords2),
4218                     tile = this._tiles[key];
4219
4220                 if (tile && tile.active) {
4221                         tile.retain = true;
4222                         return true;
4223
4224                 } else if (tile && tile.loaded) {
4225                         tile.retain = true;
4226                 }
4227
4228                 if (z2 > minZoom) {
4229                         return this._retainParent(x2, y2, z2, minZoom);
4230                 }
4231
4232                 return false;
4233         },
4234
4235         _retainChildren: function (x, y, z, maxZoom) {
4236
4237                 for (var i = 2 * x; i < 2 * x + 2; i++) {
4238                         for (var j = 2 * y; j < 2 * y + 2; j++) {
4239
4240                                 var coords = new L.Point(i, j);
4241                                 coords.z = z + 1;
4242
4243                                 var key = this._tileCoordsToKey(coords),
4244                                     tile = this._tiles[key];
4245
4246                                 if (tile && tile.active) {
4247                                         tile.retain = true;
4248                                         continue;
4249
4250                                 } else if (tile && tile.loaded) {
4251                                         tile.retain = true;
4252                                 }
4253
4254                                 if (z + 1 < maxZoom) {
4255                                         this._retainChildren(i, j, z + 1, maxZoom);
4256                                 }
4257                         }
4258                 }
4259         },
4260
4261         _resetView: function (e) {
4262                 var animating = e && (e.pinch || e.flyTo);
4263                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
4264         },
4265
4266         _animateZoom: function (e) {
4267                 this._setView(e.center, e.zoom, true, e.noUpdate);
4268         },
4269
4270         _setView: function (center, zoom, noPrune, noUpdate) {
4271                 var tileZoom = Math.round(zoom);
4272                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
4273                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
4274                         tileZoom = undefined;
4275                 }
4276
4277                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
4278
4279                 if (!noUpdate || tileZoomChanged) {
4280
4281                         this._tileZoom = tileZoom;
4282
4283                         if (this._abortLoading) {
4284                                 this._abortLoading();
4285                         }
4286
4287                         this._updateLevels();
4288                         this._resetGrid();
4289
4290                         if (tileZoom !== undefined) {
4291                                 this._update(center);
4292                         }
4293
4294                         if (!noPrune) {
4295                                 this._pruneTiles();
4296                         }
4297
4298                         // Flag to prevent _updateOpacity from pruning tiles during
4299                         // a zoom anim or a pinch gesture
4300                         this._noPrune = !!noPrune;
4301                 }
4302
4303                 this._setZoomTransforms(center, zoom);
4304         },
4305
4306         _setZoomTransforms: function (center, zoom) {
4307                 for (var i in this._levels) {
4308                         this._setZoomTransform(this._levels[i], center, zoom);
4309                 }
4310         },
4311
4312         _setZoomTransform: function (level, center, zoom) {
4313                 var scale = this._map.getZoomScale(zoom, level.zoom),
4314                     translate = level.origin.multiplyBy(scale)
4315                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
4316
4317                 if (L.Browser.any3d) {
4318                         L.DomUtil.setTransform(level.el, translate, scale);
4319                 } else {
4320                         L.DomUtil.setPosition(level.el, translate);
4321                 }
4322         },
4323
4324         _resetGrid: function () {
4325                 var map = this._map,
4326                     crs = map.options.crs,
4327                     tileSize = this._tileSize = this.getTileSize(),
4328                     tileZoom = this._tileZoom;
4329
4330                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
4331                 if (bounds) {
4332                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
4333                 }
4334
4335                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
4336                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
4337                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
4338                 ];
4339                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
4340                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
4341                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
4342                 ];
4343         },
4344
4345         _onMoveEnd: function () {
4346                 if (!this._map || this._map._animatingZoom) { return; }
4347
4348                 this._update();
4349         },
4350
4351         _getTiledPixelBounds: function (center) {
4352                 var map = this._map,
4353                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
4354                     scale = map.getZoomScale(mapZoom, this._tileZoom),
4355                     pixelCenter = map.project(center, this._tileZoom).floor(),
4356                     halfSize = map.getSize().divideBy(scale * 2);
4357
4358                 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
4359         },
4360
4361         // Private method to load tiles in the grid's active zoom level according to map bounds
4362         _update: function (center) {
4363                 var map = this._map;
4364                 if (!map) { return; }
4365                 var zoom = map.getZoom();
4366
4367                 if (center === undefined) { center = map.getCenter(); }
4368                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
4369
4370                 var pixelBounds = this._getTiledPixelBounds(center),
4371                     tileRange = this._pxBoundsToTileRange(pixelBounds),
4372                     tileCenter = tileRange.getCenter(),
4373                     queue = [],
4374                     margin = this.options.keepBuffer,
4375                     noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
4376                                               tileRange.getTopRight().add([margin, -margin]));
4377
4378                 for (var key in this._tiles) {
4379                         var c = this._tiles[key].coords;
4380                         if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
4381                                 this._tiles[key].current = false;
4382                         }
4383                 }
4384
4385                 // _update just loads more tiles. If the tile zoom level differs too much
4386                 // from the map's, let _setView reset levels and prune old tiles.
4387                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
4388
4389                 // create a queue of coordinates to load tiles from
4390                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
4391                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
4392                                 var coords = new L.Point(i, j);
4393                                 coords.z = this._tileZoom;
4394
4395                                 if (!this._isValidTile(coords)) { continue; }
4396
4397                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
4398                                 if (tile) {
4399                                         tile.current = true;
4400                                 } else {
4401                                         queue.push(coords);
4402                                 }
4403                         }
4404                 }
4405
4406                 // sort tile queue to load tiles in order of their distance to center
4407                 queue.sort(function (a, b) {
4408                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
4409                 });
4410
4411                 if (queue.length !== 0) {
4412                         // if its the first batch of tiles to load
4413                         if (!this._loading) {
4414                                 this._loading = true;
4415                                 // @event loading: Event
4416                                 // Fired when the grid layer starts loading tiles.
4417                                 this.fire('loading');
4418                         }
4419
4420                         // create DOM fragment to append tiles in one batch
4421                         var fragment = document.createDocumentFragment();
4422
4423                         for (i = 0; i < queue.length; i++) {
4424                                 this._addTile(queue[i], fragment);
4425                         }
4426
4427                         this._level.el.appendChild(fragment);
4428                 }
4429         },
4430
4431         _isValidTile: function (coords) {
4432                 var crs = this._map.options.crs;
4433
4434                 if (!crs.infinite) {
4435                         // don't load tile if it's out of bounds and not wrapped
4436                         var bounds = this._globalTileRange;
4437                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
4438                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
4439                 }
4440
4441                 if (!this.options.bounds) { return true; }
4442
4443                 // don't load tile if it doesn't intersect the bounds in options
4444                 var tileBounds = this._tileCoordsToBounds(coords);
4445                 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
4446         },
4447
4448         _keyToBounds: function (key) {
4449                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
4450         },
4451
4452         // converts tile coordinates to its geographical bounds
4453         _tileCoordsToBounds: function (coords) {
4454
4455                 var map = this._map,
4456                     tileSize = this.getTileSize(),
4457
4458                     nwPoint = coords.scaleBy(tileSize),
4459                     sePoint = nwPoint.add(tileSize),
4460
4461                     nw = map.unproject(nwPoint, coords.z),
4462                     se = map.unproject(sePoint, coords.z);
4463
4464                 if (!this.options.noWrap) {
4465                         nw = map.wrapLatLng(nw);
4466                         se = map.wrapLatLng(se);
4467                 }
4468
4469                 return new L.LatLngBounds(nw, se);
4470         },
4471
4472         // converts tile coordinates to key for the tile cache
4473         _tileCoordsToKey: function (coords) {
4474                 return coords.x + ':' + coords.y + ':' + coords.z;
4475         },
4476
4477         // converts tile cache key to coordinates
4478         _keyToTileCoords: function (key) {
4479                 var k = key.split(':'),
4480                     coords = new L.Point(+k[0], +k[1]);
4481                 coords.z = +k[2];
4482                 return coords;
4483         },
4484
4485         _removeTile: function (key) {
4486                 var tile = this._tiles[key];
4487                 if (!tile) { return; }
4488
4489                 L.DomUtil.remove(tile.el);
4490
4491                 delete this._tiles[key];
4492
4493                 // @event tileunload: TileEvent
4494                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
4495                 this.fire('tileunload', {
4496                         tile: tile.el,
4497                         coords: this._keyToTileCoords(key)
4498                 });
4499         },
4500
4501         _initTile: function (tile) {
4502                 L.DomUtil.addClass(tile, 'leaflet-tile');
4503
4504                 var tileSize = this.getTileSize();
4505                 tile.style.width = tileSize.x + 'px';
4506                 tile.style.height = tileSize.y + 'px';
4507
4508                 tile.onselectstart = L.Util.falseFn;
4509                 tile.onmousemove = L.Util.falseFn;
4510
4511                 // update opacity on tiles in IE7-8 because of filter inheritance problems
4512                 if (L.Browser.ielt9 && this.options.opacity < 1) {
4513                         L.DomUtil.setOpacity(tile, this.options.opacity);
4514                 }
4515
4516                 // without this hack, tiles disappear after zoom on Chrome for Android
4517                 // https://github.com/Leaflet/Leaflet/issues/2078
4518                 if (L.Browser.android && !L.Browser.android23) {
4519                         tile.style.WebkitBackfaceVisibility = 'hidden';
4520                 }
4521         },
4522
4523         _addTile: function (coords, container) {
4524                 var tilePos = this._getTilePos(coords),
4525                     key = this._tileCoordsToKey(coords);
4526
4527                 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
4528
4529                 this._initTile(tile);
4530
4531                 // if createTile is defined with a second argument ("done" callback),
4532                 // we know that tile is async and will be ready later; otherwise
4533                 if (this.createTile.length < 2) {
4534                         // mark tile as ready, but delay one frame for opacity animation to happen
4535                         L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
4536                 }
4537
4538                 L.DomUtil.setPosition(tile, tilePos);
4539
4540                 // save tile in cache
4541                 this._tiles[key] = {
4542                         el: tile,
4543                         coords: coords,
4544                         current: true
4545                 };
4546
4547                 container.appendChild(tile);
4548                 // @event tileloadstart: TileEvent
4549                 // Fired when a tile is requested and starts loading.
4550                 this.fire('tileloadstart', {
4551                         tile: tile,
4552                         coords: coords
4553                 });
4554         },
4555
4556         _tileReady: function (coords, err, tile) {
4557                 if (!this._map) { return; }
4558
4559                 if (err) {
4560                         // @event tileerror: TileErrorEvent
4561                         // Fired when there is an error loading a tile.
4562                         this.fire('tileerror', {
4563                                 error: err,
4564                                 tile: tile,
4565                                 coords: coords
4566                         });
4567                 }
4568
4569                 var key = this._tileCoordsToKey(coords);
4570
4571                 tile = this._tiles[key];
4572                 if (!tile) { return; }
4573
4574                 tile.loaded = +new Date();
4575                 if (this._map._fadeAnimated) {
4576                         L.DomUtil.setOpacity(tile.el, 0);
4577                         L.Util.cancelAnimFrame(this._fadeFrame);
4578                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4579                 } else {
4580                         tile.active = true;
4581                         this._pruneTiles();
4582                 }
4583
4584                 L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
4585
4586                 // @event tileload: TileEvent
4587                 // Fired when a tile loads.
4588                 this.fire('tileload', {
4589                         tile: tile.el,
4590                         coords: coords
4591                 });
4592
4593                 if (this._noTilesToLoad()) {
4594                         this._loading = false;
4595                         // @event load: Event
4596                         // Fired when the grid layer loaded all visible tiles.
4597                         this.fire('load');
4598
4599                         if (L.Browser.ielt9 || !this._map._fadeAnimated) {
4600                                 L.Util.requestAnimFrame(this._pruneTiles, this);
4601                         } else {
4602                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
4603                                 // to trigger a pruning.
4604                                 setTimeout(L.bind(this._pruneTiles, this), 250);
4605                         }
4606                 }
4607         },
4608
4609         _getTilePos: function (coords) {
4610                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
4611         },
4612
4613         _wrapCoords: function (coords) {
4614                 var newCoords = new L.Point(
4615                         this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
4616                         this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
4617                 newCoords.z = coords.z;
4618                 return newCoords;
4619         },
4620
4621         _pxBoundsToTileRange: function (bounds) {
4622                 var tileSize = this.getTileSize();
4623                 return new L.Bounds(
4624                         bounds.min.unscaleBy(tileSize).floor(),
4625                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
4626         },
4627
4628         _noTilesToLoad: function () {
4629                 for (var key in this._tiles) {
4630                         if (!this._tiles[key].loaded) { return false; }
4631                 }
4632                 return true;
4633         }
4634 });
4635
4636 // @factory L.gridLayer(options?: GridLayer options)
4637 // Creates a new instance of GridLayer with the supplied options.
4638 L.gridLayer = function (options) {
4639         return new L.GridLayer(options);
4640 };
4641
4642
4643
4644 /*
4645  * @class TileLayer
4646  * @inherits GridLayer
4647  * @aka L.TileLayer
4648  * Used to load and display tile layers on the map. Extends `GridLayer`.
4649  *
4650  * @example
4651  *
4652  * ```js
4653  * L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
4654  * ```
4655  *
4656  * @section URL template
4657  * @example
4658  *
4659  * A string of the following form:
4660  *
4661  * ```
4662  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
4663  * ```
4664  *
4665  * `{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.
4666  *
4667  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
4668  *
4669  * ```
4670  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
4671  * ```
4672  */
4673
4674
4675 L.TileLayer = L.GridLayer.extend({
4676
4677         // @section
4678         // @aka TileLayer options
4679         options: {
4680                 // @option minZoom: Number = 0
4681                 // Minimum zoom number.
4682                 minZoom: 0,
4683
4684                 // @option maxZoom: Number = 18
4685                 // Maximum zoom number.
4686                 maxZoom: 18,
4687
4688                 // @option maxNativeZoom: Number = null
4689                 // Maximum zoom number the tile source has available. If it is specified,
4690                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
4691                 // from `maxNativeZoom` level and auto-scaled.
4692                 maxNativeZoom: null,
4693
4694                 // @option subdomains: String|String[] = 'abc'
4695                 // 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.
4696                 subdomains: 'abc',
4697
4698                 // @option errorTileUrl: String = ''
4699                 // URL to the tile image to show in place of the tile that failed to load.
4700                 errorTileUrl: '',
4701
4702                 // @option zoomOffset: Number = 0
4703                 // The zoom number used in tile URLs will be offset with this value.
4704                 zoomOffset: 0,
4705
4706                 // @option tms: Boolean = false
4707                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
4708                 tms: false,
4709
4710                 // @option zoomReverse: Boolean = false
4711                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
4712                 zoomReverse: false,
4713
4714                 // @option detectRetina: Boolean = false
4715                 // 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.
4716                 detectRetina: false,
4717
4718                 // @option crossOrigin: Boolean = false
4719                 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
4720                 crossOrigin: false
4721         },
4722
4723         initialize: function (url, options) {
4724
4725                 this._url = url;
4726
4727                 options = L.setOptions(this, options);
4728
4729                 // detecting retina displays, adjusting tileSize and zoom levels
4730                 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
4731
4732                         options.tileSize = Math.floor(options.tileSize / 2);
4733
4734                         if (!options.zoomReverse) {
4735                                 options.zoomOffset++;
4736                                 options.maxZoom--;
4737                         } else {
4738                                 options.zoomOffset--;
4739                                 options.minZoom++;
4740                         }
4741
4742                         options.minZoom = Math.max(0, options.minZoom);
4743                 }
4744
4745                 if (typeof options.subdomains === 'string') {
4746                         options.subdomains = options.subdomains.split('');
4747                 }
4748
4749                 // for https://github.com/Leaflet/Leaflet/issues/137
4750                 if (!L.Browser.android) {
4751                         this.on('tileunload', this._onTileRemove);
4752                 }
4753         },
4754
4755         // @method setUrl(url: String, noRedraw?: Boolean): this
4756         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
4757         setUrl: function (url, noRedraw) {
4758                 this._url = url;
4759
4760                 if (!noRedraw) {
4761                         this.redraw();
4762                 }
4763                 return this;
4764         },
4765
4766         // @method createTile(coords: Object, done?: Function): HTMLElement
4767         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
4768         // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
4769         // callback is called when the tile has been loaded.
4770         createTile: function (coords, done) {
4771                 var tile = document.createElement('img');
4772
4773                 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
4774                 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
4775
4776                 if (this.options.crossOrigin) {
4777                         tile.crossOrigin = '';
4778                 }
4779
4780                 /*
4781                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
4782                  http://www.w3.org/TR/WCAG20-TECHS/H67
4783                 */
4784                 tile.alt = '';
4785
4786                 tile.src = this.getTileUrl(coords);
4787
4788                 return tile;
4789         },
4790
4791         // @section Extension methods
4792         // @uninheritable
4793         // Layers extending `TileLayer` might reimplement the following method.
4794         // @method getTileUrl(coords: Object): String
4795         // Called only internally, returns the URL for a tile given its coordinates.
4796         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
4797         getTileUrl: function (coords) {
4798                 var data = {
4799                         r: L.Browser.retina ? '@2x' : '',
4800                         s: this._getSubdomain(coords),
4801                         x: coords.x,
4802                         y: coords.y,
4803                         z: this._getZoomForUrl()
4804                 };
4805                 if (this._map && !this._map.options.crs.infinite) {
4806                         var invertedY = this._globalTileRange.max.y - coords.y;
4807                         if (this.options.tms) {
4808                                 data['y'] = invertedY;
4809                         }
4810                         data['-y'] = invertedY;
4811                 }
4812
4813                 return L.Util.template(this._url, L.extend(data, this.options));
4814         },
4815
4816         _tileOnLoad: function (done, tile) {
4817                 // For https://github.com/Leaflet/Leaflet/issues/3332
4818                 if (L.Browser.ielt9) {
4819                         setTimeout(L.bind(done, this, null, tile), 0);
4820                 } else {
4821                         done(null, tile);
4822                 }
4823         },
4824
4825         _tileOnError: function (done, tile, e) {
4826                 var errorUrl = this.options.errorTileUrl;
4827                 if (errorUrl) {
4828                         tile.src = errorUrl;
4829                 }
4830                 done(e, tile);
4831         },
4832
4833         getTileSize: function () {
4834                 var map = this._map,
4835                     tileSize = L.GridLayer.prototype.getTileSize.call(this),
4836                     zoom = this._tileZoom + this.options.zoomOffset,
4837                     zoomN = this.options.maxNativeZoom;
4838
4839                 // increase tile size when overscaling
4840                 return zoomN !== null && zoom > zoomN ?
4841                                 tileSize.divideBy(map.getZoomScale(zoomN, zoom)).round() :
4842                                 tileSize;
4843         },
4844
4845         _onTileRemove: function (e) {
4846                 e.tile.onload = null;
4847         },
4848
4849         _getZoomForUrl: function () {
4850
4851                 var options = this.options,
4852                     zoom = this._tileZoom;
4853
4854                 if (options.zoomReverse) {
4855                         zoom = options.maxZoom - zoom;
4856                 }
4857
4858                 zoom += options.zoomOffset;
4859
4860                 return options.maxNativeZoom !== null ? Math.min(zoom, options.maxNativeZoom) : zoom;
4861         },
4862
4863         _getSubdomain: function (tilePoint) {
4864                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
4865                 return this.options.subdomains[index];
4866         },
4867
4868         // stops loading all tiles in the background layer
4869         _abortLoading: function () {
4870                 var i, tile;
4871                 for (i in this._tiles) {
4872                         if (this._tiles[i].coords.z !== this._tileZoom) {
4873                                 tile = this._tiles[i].el;
4874
4875                                 tile.onload = L.Util.falseFn;
4876                                 tile.onerror = L.Util.falseFn;
4877
4878                                 if (!tile.complete) {
4879                                         tile.src = L.Util.emptyImageUrl;
4880                                         L.DomUtil.remove(tile);
4881                                 }
4882                         }
4883                 }
4884         }
4885 });
4886
4887
4888 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
4889 // Instantiates a tile layer object given a `URL template` and optionally an options object.
4890
4891 L.tileLayer = function (url, options) {
4892         return new L.TileLayer(url, options);
4893 };
4894
4895
4896
4897 /*
4898  * @class TileLayer.WMS
4899  * @inherits TileLayer
4900  * @aka L.TileLayer.WMS
4901  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
4902  *
4903  * @example
4904  *
4905  * ```js
4906  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
4907  *      layers: 'nexrad-n0r-900913',
4908  *      format: 'image/png',
4909  *      transparent: true,
4910  *      attribution: "Weather data © 2012 IEM Nexrad"
4911  * });
4912  * ```
4913  */
4914
4915 L.TileLayer.WMS = L.TileLayer.extend({
4916
4917         // @section
4918         // @aka TileLayer.WMS options
4919         // If any custom options not documented here are used, they will be sent to the
4920         // WMS server as extra parameters in each request URL. This can be useful for
4921         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
4922         defaultWmsParams: {
4923                 service: 'WMS',
4924                 request: 'GetMap',
4925
4926                 // @option layers: String = ''
4927                 // **(required)** Comma-separated list of WMS layers to show.
4928                 layers: '',
4929
4930                 // @option styles: String = ''
4931                 // Comma-separated list of WMS styles.
4932                 styles: '',
4933
4934                 // @option format: String = 'image/jpeg'
4935                 // WMS image format (use `'image/png'` for layers with transparency).
4936                 format: 'image/jpeg',
4937
4938                 // @option transparent: Boolean = false
4939                 // If `true`, the WMS service will return images with transparency.
4940                 transparent: false,
4941
4942                 // @option version: String = '1.1.1'
4943                 // Version of the WMS service to use
4944                 version: '1.1.1'
4945         },
4946
4947         options: {
4948                 // @option crs: CRS = null
4949                 // Coordinate Reference System to use for the WMS requests, defaults to
4950                 // map CRS. Don't change this if you're not sure what it means.
4951                 crs: null,
4952
4953                 // @option uppercase: Boolean = false
4954                 // If `true`, WMS request parameter keys will be uppercase.
4955                 uppercase: false
4956         },
4957
4958         initialize: function (url, options) {
4959
4960                 this._url = url;
4961
4962                 var wmsParams = L.extend({}, this.defaultWmsParams);
4963
4964                 // all keys that are not TileLayer options go to WMS params
4965                 for (var i in options) {
4966                         if (!(i in this.options)) {
4967                                 wmsParams[i] = options[i];
4968                         }
4969                 }
4970
4971                 options = L.setOptions(this, options);
4972
4973                 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
4974
4975                 this.wmsParams = wmsParams;
4976         },
4977
4978         onAdd: function (map) {
4979
4980                 this._crs = this.options.crs || map.options.crs;
4981                 this._wmsVersion = parseFloat(this.wmsParams.version);
4982
4983                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
4984                 this.wmsParams[projectionKey] = this._crs.code;
4985
4986                 L.TileLayer.prototype.onAdd.call(this, map);
4987         },
4988
4989         getTileUrl: function (coords) {
4990
4991                 var tileBounds = this._tileCoordsToBounds(coords),
4992                     nw = this._crs.project(tileBounds.getNorthWest()),
4993                     se = this._crs.project(tileBounds.getSouthEast()),
4994
4995                     bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
4996                             [se.y, nw.x, nw.y, se.x] :
4997                             [nw.x, se.y, se.x, nw.y]).join(','),
4998
4999                     url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5000
5001                 return url +
5002                         L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5003                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5004         },
5005
5006         // @method setParams(params: Object, noRedraw?: Boolean): this
5007         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5008         setParams: function (params, noRedraw) {
5009
5010                 L.extend(this.wmsParams, params);
5011
5012                 if (!noRedraw) {
5013                         this.redraw();
5014                 }
5015
5016                 return this;
5017         }
5018 });
5019
5020
5021 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5022 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5023 L.tileLayer.wms = function (url, options) {
5024         return new L.TileLayer.WMS(url, options);
5025 };
5026
5027
5028
5029 /*
5030  * @class ImageOverlay
5031  * @aka L.ImageOverlay
5032  * @inherits Interactive layer
5033  *
5034  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5035  *
5036  * @example
5037  *
5038  * ```js
5039  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5040  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5041  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5042  * ```
5043  */
5044
5045 L.ImageOverlay = L.Layer.extend({
5046
5047         // @section
5048         // @aka ImageOverlay options
5049         options: {
5050                 // @option opacity: Number = 1.0
5051                 // The opacity of the image overlay.
5052                 opacity: 1,
5053
5054                 // @option alt: String = ''
5055                 // Text for the `alt` attribute of the image (useful for accessibility).
5056                 alt: '',
5057
5058                 // @option interactive: Boolean = false
5059                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5060                 interactive: false,
5061
5062                 // @option attribution: String = null
5063                 // An optional string containing HTML to be shown on the `Attribution control`
5064                 attribution: null,
5065
5066                 // @option crossOrigin: Boolean = false
5067                 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5068                 crossOrigin: false
5069         },
5070
5071         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5072                 this._url = url;
5073                 this._bounds = L.latLngBounds(bounds);
5074
5075                 L.setOptions(this, options);
5076         },
5077
5078         onAdd: function () {
5079                 if (!this._image) {
5080                         this._initImage();
5081
5082                         if (this.options.opacity < 1) {
5083                                 this._updateOpacity();
5084                         }
5085                 }
5086
5087                 if (this.options.interactive) {
5088                         L.DomUtil.addClass(this._image, 'leaflet-interactive');
5089                         this.addInteractiveTarget(this._image);
5090                 }
5091
5092                 this.getPane().appendChild(this._image);
5093                 this._reset();
5094         },
5095
5096         onRemove: function () {
5097                 L.DomUtil.remove(this._image);
5098                 if (this.options.interactive) {
5099                         this.removeInteractiveTarget(this._image);
5100                 }
5101         },
5102
5103         // @method setOpacity(opacity: Number): this
5104         // Sets the opacity of the overlay.
5105         setOpacity: function (opacity) {
5106                 this.options.opacity = opacity;
5107
5108                 if (this._image) {
5109                         this._updateOpacity();
5110                 }
5111                 return this;
5112         },
5113
5114         setStyle: function (styleOpts) {
5115                 if (styleOpts.opacity) {
5116                         this.setOpacity(styleOpts.opacity);
5117                 }
5118                 return this;
5119         },
5120
5121         // @method bringToFront(): this
5122         // Brings the layer to the top of all overlays.
5123         bringToFront: function () {
5124                 if (this._map) {
5125                         L.DomUtil.toFront(this._image);
5126                 }
5127                 return this;
5128         },
5129
5130         // @method bringToBack(): this
5131         // Brings the layer to the bottom of all overlays.
5132         bringToBack: function () {
5133                 if (this._map) {
5134                         L.DomUtil.toBack(this._image);
5135                 }
5136                 return this;
5137         },
5138
5139         // @method setUrl(url: String): this
5140         // Changes the URL of the image.
5141         setUrl: function (url) {
5142                 this._url = url;
5143
5144                 if (this._image) {
5145                         this._image.src = url;
5146                 }
5147                 return this;
5148         },
5149
5150         setBounds: function (bounds) {
5151                 this._bounds = bounds;
5152
5153                 if (this._map) {
5154                         this._reset();
5155                 }
5156                 return this;
5157         },
5158
5159         getAttribution: function () {
5160                 return this.options.attribution;
5161         },
5162
5163         getEvents: function () {
5164                 var events = {
5165                         zoom: this._reset,
5166                         viewreset: this._reset
5167                 };
5168
5169                 if (this._zoomAnimated) {
5170                         events.zoomanim = this._animateZoom;
5171                 }
5172
5173                 return events;
5174         },
5175
5176         getBounds: function () {
5177                 return this._bounds;
5178         },
5179
5180         getElement: function () {
5181                 return this._image;
5182         },
5183
5184         _initImage: function () {
5185                 var img = this._image = L.DomUtil.create('img',
5186                                 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
5187
5188                 img.onselectstart = L.Util.falseFn;
5189                 img.onmousemove = L.Util.falseFn;
5190
5191                 img.onload = L.bind(this.fire, this, 'load');
5192
5193                 if (this.options.crossOrigin) {
5194                         img.crossOrigin = '';
5195                 }
5196
5197                 img.src = this._url;
5198                 img.alt = this.options.alt;
5199         },
5200
5201         _animateZoom: function (e) {
5202                 var scale = this._map.getZoomScale(e.zoom),
5203                     offset = this._map._latLngToNewLayerPoint(this._bounds.getNorthWest(), e.zoom, e.center);
5204
5205                 L.DomUtil.setTransform(this._image, offset, scale);
5206         },
5207
5208         _reset: function () {
5209                 var image = this._image,
5210                     bounds = new L.Bounds(
5211                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
5212                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
5213                     size = bounds.getSize();
5214
5215                 L.DomUtil.setPosition(image, bounds.min);
5216
5217                 image.style.width  = size.x + 'px';
5218                 image.style.height = size.y + 'px';
5219         },
5220
5221         _updateOpacity: function () {
5222                 L.DomUtil.setOpacity(this._image, this.options.opacity);
5223         }
5224 });
5225
5226 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
5227 // Instantiates an image overlay object given the URL of the image and the
5228 // geographical bounds it is tied to.
5229 L.imageOverlay = function (url, bounds, options) {
5230         return new L.ImageOverlay(url, bounds, options);
5231 };
5232
5233
5234
5235 /*
5236  * @class Icon
5237  * @aka L.Icon
5238  * @inherits Layer
5239  *
5240  * Represents an icon to provide when creating a marker.
5241  *
5242  * @example
5243  *
5244  * ```js
5245  * var myIcon = L.icon({
5246  *     iconUrl: 'my-icon.png',
5247  *     iconRetinaUrl: 'my-icon@2x.png',
5248  *     iconSize: [38, 95],
5249  *     iconAnchor: [22, 94],
5250  *     popupAnchor: [-3, -76],
5251  *     shadowUrl: 'my-icon-shadow.png',
5252  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
5253  *     shadowSize: [68, 95],
5254  *     shadowAnchor: [22, 94]
5255  * });
5256  *
5257  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5258  * ```
5259  *
5260  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
5261  *
5262  */
5263
5264 L.Icon = L.Class.extend({
5265
5266         /* @section
5267          * @aka Icon options
5268          *
5269          * @option iconUrl: String = null
5270          * **(required)** The URL to the icon image (absolute or relative to your script path).
5271          *
5272          * @option iconRetinaUrl: String = null
5273          * The URL to a retina sized version of the icon image (absolute or relative to your
5274          * script path). Used for Retina screen devices.
5275          *
5276          * @option iconSize: Point = null
5277          * Size of the icon image in pixels.
5278          *
5279          * @option iconAnchor: Point = null
5280          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
5281          * will be aligned so that this point is at the marker's geographical location. Centered
5282          * by default if size is specified, also can be set in CSS with negative margins.
5283          *
5284          * @option popupAnchor: Point = null
5285          * The coordinates of the point from which popups will "open", relative to the icon anchor.
5286          *
5287          * @option shadowUrl: String = null
5288          * The URL to the icon shadow image. If not specified, no shadow image will be created.
5289          *
5290          * @option shadowRetinaUrl: String = null
5291          *
5292          * @option shadowSize: Point = null
5293          * Size of the shadow image in pixels.
5294          *
5295          * @option shadowAnchor: Point = null
5296          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
5297          * as iconAnchor if not specified).
5298          *
5299          * @option className: String = ''
5300          * A custom class name to assign to both icon and shadow images. Empty by default.
5301          */
5302
5303         initialize: function (options) {
5304                 L.setOptions(this, options);
5305         },
5306
5307         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
5308         // Called internally when the icon has to be shown, returns a `<img>` HTML element
5309         // styled according to the options.
5310         createIcon: function (oldIcon) {
5311                 return this._createIcon('icon', oldIcon);
5312         },
5313
5314         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
5315         // As `createIcon`, but for the shadow beneath it.
5316         createShadow: function (oldIcon) {
5317                 return this._createIcon('shadow', oldIcon);
5318         },
5319
5320         _createIcon: function (name, oldIcon) {
5321                 var src = this._getIconUrl(name);
5322
5323                 if (!src) {
5324                         if (name === 'icon') {
5325                                 throw new Error('iconUrl not set in Icon options (see the docs).');
5326                         }
5327                         return null;
5328                 }
5329
5330                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
5331                 this._setIconStyles(img, name);
5332
5333                 return img;
5334         },
5335
5336         _setIconStyles: function (img, name) {
5337                 var options = this.options;
5338                 var sizeOption = options[name + 'Size'];
5339
5340                 if (typeof sizeOption === 'number') {
5341                         sizeOption = [sizeOption, sizeOption];
5342                 }
5343
5344                 var size = L.point(sizeOption),
5345                     anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
5346                             size && size.divideBy(2, true));
5347
5348                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
5349
5350                 if (anchor) {
5351                         img.style.marginLeft = (-anchor.x) + 'px';
5352                         img.style.marginTop  = (-anchor.y) + 'px';
5353                 }
5354
5355                 if (size) {
5356                         img.style.width  = size.x + 'px';
5357                         img.style.height = size.y + 'px';
5358                 }
5359         },
5360
5361         _createImg: function (src, el) {
5362                 el = el || document.createElement('img');
5363                 el.src = src;
5364                 return el;
5365         },
5366
5367         _getIconUrl: function (name) {
5368                 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
5369         }
5370 });
5371
5372
5373 // @factory L.icon(options: Icon options)
5374 // Creates an icon instance with the given options.
5375 L.icon = function (options) {
5376         return new L.Icon(options);
5377 };
5378
5379
5380
5381 /*
5382  * @miniclass Icon.Default (Icon)
5383  * @aka L.Icon.Default
5384  * @section
5385  *
5386  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
5387  * no icon is specified. Points to the blue marker image distributed with Leaflet
5388  * releases.
5389  *
5390  * In order to change the default icon, just change the properties of `L.Icon.Default.prototype.options`
5391  * (which is a set of `Icon options`).
5392  */
5393
5394 L.Icon.Default = L.Icon.extend({
5395
5396         options: {
5397                 iconUrl:       'marker-icon.png',
5398                 iconRetinaUrl: 'marker-icon-2x.png',
5399                 shadowUrl:     'marker-shadow.png',
5400                 iconSize:    [25, 41],
5401                 iconAnchor:  [12, 41],
5402                 popupAnchor: [1, -34],
5403                 tooltipAnchor: [16, -28],
5404                 shadowSize:  [41, 41]
5405         },
5406
5407         _getIconUrl: function (name) {
5408                 if (!L.Icon.Default.imagePath) {        // Deprecated, backwards-compatibility only
5409                         L.Icon.Default.imagePath = this._detectIconPath();
5410                 }
5411
5412                 // @option imagePath: String
5413                 // `L.Icon.Default` will try to auto-detect the absolute location of the
5414                 // blue icon images. If you are placing these images in a non-standard
5415                 // way, set this option to point to the right absolute path.
5416                 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
5417         },
5418
5419         _detectIconPath: function () {
5420                 var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
5421                 var path = L.DomUtil.getStyle(el, 'background-image') ||
5422                            L.DomUtil.getStyle(el, 'backgroundImage');   // IE8
5423
5424                 document.body.removeChild(el);
5425
5426                 return path.indexOf('url') === 0 ?
5427                         path.replace(/^url\([\"\']?/, '').replace(/[\"\']?\)$/, '') : '';
5428         }
5429 });
5430
5431
5432
5433 /*
5434  * @class Marker
5435  * @inherits Interactive layer
5436  * @aka L.Marker
5437  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
5438  *
5439  * @example
5440  *
5441  * ```js
5442  * L.marker([50.5, 30.5]).addTo(map);
5443  * ```
5444  */
5445
5446 L.Marker = L.Layer.extend({
5447
5448         // @section
5449         // @aka Marker options
5450         options: {
5451                 // @option icon: Icon = *
5452                 // 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.
5453                 icon: new L.Icon.Default(),
5454
5455                 // Option inherited from "Interactive layer" abstract class
5456                 interactive: true,
5457
5458                 // @option draggable: Boolean = false
5459                 // Whether the marker is draggable with mouse/touch or not.
5460                 draggable: false,
5461
5462                 // @option keyboard: Boolean = true
5463                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
5464                 keyboard: true,
5465
5466                 // @option title: String = ''
5467                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
5468                 title: '',
5469
5470                 // @option alt: String = ''
5471                 // Text for the `alt` attribute of the icon image (useful for accessibility).
5472                 alt: '',
5473
5474                 // @option zIndexOffset: Number = 0
5475                 // 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).
5476                 zIndexOffset: 0,
5477
5478                 // @option opacity: Number = 1.0
5479                 // The opacity of the marker.
5480                 opacity: 1,
5481
5482                 // @option riseOnHover: Boolean = false
5483                 // If `true`, the marker will get on top of others when you hover the mouse over it.
5484                 riseOnHover: false,
5485
5486                 // @option riseOffset: Number = 250
5487                 // The z-index offset used for the `riseOnHover` feature.
5488                 riseOffset: 250,
5489
5490                 // @option pane: String = 'markerPane'
5491                 // `Map pane` where the markers icon will be added.
5492                 pane: 'markerPane',
5493
5494                 // FIXME: shadowPane is no longer a valid option
5495                 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
5496         },
5497
5498         /* @section
5499          *
5500          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
5501          */
5502
5503         initialize: function (latlng, options) {
5504                 L.setOptions(this, options);
5505                 this._latlng = L.latLng(latlng);
5506         },
5507
5508         onAdd: function (map) {
5509                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
5510
5511                 if (this._zoomAnimated) {
5512                         map.on('zoomanim', this._animateZoom, this);
5513                 }
5514
5515                 this._initIcon();
5516                 this.update();
5517         },
5518
5519         onRemove: function (map) {
5520                 if (this.dragging && this.dragging.enabled()) {
5521                         this.options.draggable = true;
5522                         this.dragging.removeHooks();
5523                 }
5524
5525                 if (this._zoomAnimated) {
5526                         map.off('zoomanim', this._animateZoom, this);
5527                 }
5528
5529                 this._removeIcon();
5530                 this._removeShadow();
5531         },
5532
5533         getEvents: function () {
5534                 return {
5535                         zoom: this.update,
5536                         viewreset: this.update
5537                 };
5538         },
5539
5540         // @method getLatLng: LatLng
5541         // Returns the current geographical position of the marker.
5542         getLatLng: function () {
5543                 return this._latlng;
5544         },
5545
5546         // @method setLatLng(latlng: LatLng): this
5547         // Changes the marker position to the given point.
5548         setLatLng: function (latlng) {
5549                 var oldLatLng = this._latlng;
5550                 this._latlng = L.latLng(latlng);
5551                 this.update();
5552
5553                 // @event move: Event
5554                 // 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`.
5555                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
5556         },
5557
5558         // @method setZIndexOffset(offset: Number): this
5559         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
5560         setZIndexOffset: function (offset) {
5561                 this.options.zIndexOffset = offset;
5562                 return this.update();
5563         },
5564
5565         // @method setIcon(icon: Icon): this
5566         // Changes the marker icon.
5567         setIcon: function (icon) {
5568
5569                 this.options.icon = icon;
5570
5571                 if (this._map) {
5572                         this._initIcon();
5573                         this.update();
5574                 }
5575
5576                 if (this._popup) {
5577                         this.bindPopup(this._popup, this._popup.options);
5578                 }
5579
5580                 return this;
5581         },
5582
5583         getElement: function () {
5584                 return this._icon;
5585         },
5586
5587         update: function () {
5588
5589                 if (this._icon) {
5590                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
5591                         this._setPos(pos);
5592                 }
5593
5594                 return this;
5595         },
5596
5597         _initIcon: function () {
5598                 var options = this.options,
5599                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
5600
5601                 var icon = options.icon.createIcon(this._icon),
5602                     addIcon = false;
5603
5604                 // if we're not reusing the icon, remove the old one and init new one
5605                 if (icon !== this._icon) {
5606                         if (this._icon) {
5607                                 this._removeIcon();
5608                         }
5609                         addIcon = true;
5610
5611                         if (options.title) {
5612                                 icon.title = options.title;
5613                         }
5614                         if (options.alt) {
5615                                 icon.alt = options.alt;
5616                         }
5617                 }
5618
5619                 L.DomUtil.addClass(icon, classToAdd);
5620
5621                 if (options.keyboard) {
5622                         icon.tabIndex = '0';
5623                 }
5624
5625                 this._icon = icon;
5626
5627                 if (options.riseOnHover) {
5628                         this.on({
5629                                 mouseover: this._bringToFront,
5630                                 mouseout: this._resetZIndex
5631                         });
5632                 }
5633
5634                 var newShadow = options.icon.createShadow(this._shadow),
5635                     addShadow = false;
5636
5637                 if (newShadow !== this._shadow) {
5638                         this._removeShadow();
5639                         addShadow = true;
5640                 }
5641
5642                 if (newShadow) {
5643                         L.DomUtil.addClass(newShadow, classToAdd);
5644                 }
5645                 this._shadow = newShadow;
5646
5647
5648                 if (options.opacity < 1) {
5649                         this._updateOpacity();
5650                 }
5651
5652
5653                 if (addIcon) {
5654                         this.getPane().appendChild(this._icon);
5655                 }
5656                 this._initInteraction();
5657                 if (newShadow && addShadow) {
5658                         this.getPane('shadowPane').appendChild(this._shadow);
5659                 }
5660         },
5661
5662         _removeIcon: function () {
5663                 if (this.options.riseOnHover) {
5664                         this.off({
5665                                 mouseover: this._bringToFront,
5666                                 mouseout: this._resetZIndex
5667                         });
5668                 }
5669
5670                 L.DomUtil.remove(this._icon);
5671                 this.removeInteractiveTarget(this._icon);
5672
5673                 this._icon = null;
5674         },
5675
5676         _removeShadow: function () {
5677                 if (this._shadow) {
5678                         L.DomUtil.remove(this._shadow);
5679                 }
5680                 this._shadow = null;
5681         },
5682
5683         _setPos: function (pos) {
5684                 L.DomUtil.setPosition(this._icon, pos);
5685
5686                 if (this._shadow) {
5687                         L.DomUtil.setPosition(this._shadow, pos);
5688                 }
5689
5690                 this._zIndex = pos.y + this.options.zIndexOffset;
5691
5692                 this._resetZIndex();
5693         },
5694
5695         _updateZIndex: function (offset) {
5696                 this._icon.style.zIndex = this._zIndex + offset;
5697         },
5698
5699         _animateZoom: function (opt) {
5700                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
5701
5702                 this._setPos(pos);
5703         },
5704
5705         _initInteraction: function () {
5706
5707                 if (!this.options.interactive) { return; }
5708
5709                 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
5710
5711                 this.addInteractiveTarget(this._icon);
5712
5713                 if (L.Handler.MarkerDrag) {
5714                         var draggable = this.options.draggable;
5715                         if (this.dragging) {
5716                                 draggable = this.dragging.enabled();
5717                                 this.dragging.disable();
5718                         }
5719
5720                         this.dragging = new L.Handler.MarkerDrag(this);
5721
5722                         if (draggable) {
5723                                 this.dragging.enable();
5724                         }
5725                 }
5726         },
5727
5728         // @method setOpacity(opacity: Number): this
5729         // Changes the opacity of the marker.
5730         setOpacity: function (opacity) {
5731                 this.options.opacity = opacity;
5732                 if (this._map) {
5733                         this._updateOpacity();
5734                 }
5735
5736                 return this;
5737         },
5738
5739         _updateOpacity: function () {
5740                 var opacity = this.options.opacity;
5741
5742                 L.DomUtil.setOpacity(this._icon, opacity);
5743
5744                 if (this._shadow) {
5745                         L.DomUtil.setOpacity(this._shadow, opacity);
5746                 }
5747         },
5748
5749         _bringToFront: function () {
5750                 this._updateZIndex(this.options.riseOffset);
5751         },
5752
5753         _resetZIndex: function () {
5754                 this._updateZIndex(0);
5755         }
5756 });
5757
5758
5759 // factory L.marker(latlng: LatLng, options? : Marker options)
5760
5761 // @factory L.marker(latlng: LatLng, options? : Marker options)
5762 // Instantiates a Marker object given a geographical point and optionally an options object.
5763 L.marker = function (latlng, options) {
5764         return new L.Marker(latlng, options);
5765 };
5766
5767
5768
5769 /*
5770  * @class DivIcon
5771  * @aka L.DivIcon
5772  * @inherits Icon
5773  *
5774  * Represents a lightweight icon for markers that uses a simple `<div>`
5775  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
5776  *
5777  * @example
5778  * ```js
5779  * var myIcon = L.divIcon({className: 'my-div-icon'});
5780  * // you can set .my-div-icon styles in CSS
5781  *
5782  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
5783  * ```
5784  *
5785  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
5786  */
5787
5788 L.DivIcon = L.Icon.extend({
5789         options: {
5790                 // @section
5791                 // @aka DivIcon options
5792                 iconSize: [12, 12], // also can be set through CSS
5793
5794                 // iconAnchor: (Point),
5795                 // popupAnchor: (Point),
5796
5797                 // @option html: String = ''
5798                 // Custom HTML code to put inside the div element, empty by default.
5799                 html: false,
5800
5801                 // @option bgPos: Point = [0, 0]
5802                 // Optional relative position of the background, in pixels
5803                 bgPos: null,
5804
5805                 className: 'leaflet-div-icon'
5806         },
5807
5808         createIcon: function (oldIcon) {
5809                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
5810                     options = this.options;
5811
5812                 div.innerHTML = options.html !== false ? options.html : '';
5813
5814                 if (options.bgPos) {
5815                         var bgPos = L.point(options.bgPos);
5816                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
5817                 }
5818                 this._setIconStyles(div, 'icon');
5819
5820                 return div;
5821         },
5822
5823         createShadow: function () {
5824                 return null;
5825         }
5826 });
5827
5828 // @factory L.divIcon(options: DivIcon options)
5829 // Creates a `DivIcon` instance with the given options.
5830 L.divIcon = function (options) {
5831         return new L.DivIcon(options);
5832 };
5833
5834
5835
5836 /*
5837  * @class DivOverlay
5838  * @inherits Layer
5839  * @aka L.DivOverlay
5840  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
5841  */
5842
5843 // @namespace DivOverlay
5844 L.DivOverlay = L.Layer.extend({
5845
5846         // @section
5847         // @aka DivOverlay options
5848         options: {
5849                 // @option offset: Point = Point(0, 7)
5850                 // The offset of the popup position. Useful to control the anchor
5851                 // of the popup when opening it on some overlays.
5852                 offset: [0, 7],
5853
5854                 // @option className: String = ''
5855                 // A custom CSS class name to assign to the popup.
5856                 className: '',
5857
5858                 // @option pane: String = 'popupPane'
5859                 // `Map pane` where the popup will be added.
5860                 pane: 'popupPane'
5861         },
5862
5863         initialize: function (options, source) {
5864                 L.setOptions(this, options);
5865
5866                 this._source = source;
5867         },
5868
5869         onAdd: function (map) {
5870                 this._zoomAnimated = map._zoomAnimated;
5871
5872                 if (!this._container) {
5873                         this._initLayout();
5874                 }
5875
5876                 if (map._fadeAnimated) {
5877                         L.DomUtil.setOpacity(this._container, 0);
5878                 }
5879
5880                 clearTimeout(this._removeTimeout);
5881                 this.getPane().appendChild(this._container);
5882                 this.update();
5883
5884                 if (map._fadeAnimated) {
5885                         L.DomUtil.setOpacity(this._container, 1);
5886                 }
5887
5888                 this.bringToFront();
5889         },
5890
5891         onRemove: function (map) {
5892                 if (map._fadeAnimated) {
5893                         L.DomUtil.setOpacity(this._container, 0);
5894                         this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
5895                 } else {
5896                         L.DomUtil.remove(this._container);
5897                 }
5898         },
5899
5900         // @namespace Popup
5901         // @method getLatLng: LatLng
5902         // Returns the geographical point of popup.
5903         getLatLng: function () {
5904                 return this._latlng;
5905         },
5906
5907         // @method setLatLng(latlng: LatLng): this
5908         // Sets the geographical point where the popup will open.
5909         setLatLng: function (latlng) {
5910                 this._latlng = L.latLng(latlng);
5911                 if (this._map) {
5912                         this._updatePosition();
5913                         this._adjustPan();
5914                 }
5915                 return this;
5916         },
5917
5918         // @method getContent: String|HTMLElement
5919         // Returns the content of the popup.
5920         getContent: function () {
5921                 return this._content;
5922         },
5923
5924         // @method setContent(htmlContent: String|HTMLElement|Function): this
5925         // 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.
5926         setContent: function (content) {
5927                 this._content = content;
5928                 this.update();
5929                 return this;
5930         },
5931
5932         // @method getElement: String|HTMLElement
5933         // Alias for [getContent()](#popup-getcontent)
5934         getElement: function () {
5935                 return this._container;
5936         },
5937
5938         // @method update: null
5939         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
5940         update: function () {
5941                 if (!this._map) { return; }
5942
5943                 this._container.style.visibility = 'hidden';
5944
5945                 this._updateContent();
5946                 this._updateLayout();
5947                 this._updatePosition();
5948
5949                 this._container.style.visibility = '';
5950
5951                 this._adjustPan();
5952         },
5953
5954         getEvents: function () {
5955                 var events = {
5956                         zoom: this._updatePosition,
5957                         viewreset: this._updatePosition
5958                 };
5959
5960                 if (this._zoomAnimated) {
5961                         events.zoomanim = this._animateZoom;
5962                 }
5963                 return events;
5964         },
5965
5966         // @method isOpen: Boolean
5967         // Returns `true` when the popup is visible on the map.
5968         isOpen: function () {
5969                 return !!this._map && this._map.hasLayer(this);
5970         },
5971
5972         // @method bringToFront: this
5973         // Brings this popup in front of other popups (in the same map pane).
5974         bringToFront: function () {
5975                 if (this._map) {
5976                         L.DomUtil.toFront(this._container);
5977                 }
5978                 return this;
5979         },
5980
5981         // @method bringToBack: this
5982         // Brings this popup to the back of other popups (in the same map pane).
5983         bringToBack: function () {
5984                 if (this._map) {
5985                         L.DomUtil.toBack(this._container);
5986                 }
5987                 return this;
5988         },
5989
5990         _updateContent: function () {
5991                 if (!this._content) { return; }
5992
5993                 var node = this._contentNode;
5994                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
5995
5996                 if (typeof content === 'string') {
5997                         node.innerHTML = content;
5998                 } else {
5999                         while (node.hasChildNodes()) {
6000                                 node.removeChild(node.firstChild);
6001                         }
6002                         node.appendChild(content);
6003                 }
6004                 this.fire('contentupdate');
6005         },
6006
6007         _updatePosition: function () {
6008                 if (!this._map) { return; }
6009
6010                 var pos = this._map.latLngToLayerPoint(this._latlng),
6011                     offset = L.point(this.options.offset),
6012                     anchor = this._getAnchor();
6013
6014                 if (this._zoomAnimated) {
6015                         L.DomUtil.setPosition(this._container, pos.add(anchor));
6016                 } else {
6017                         offset = offset.add(pos).add(anchor);
6018                 }
6019
6020                 var bottom = this._containerBottom = -offset.y,
6021                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6022
6023                 // bottom position the popup in case the height of the popup changes (images loading etc)
6024                 this._container.style.bottom = bottom + 'px';
6025                 this._container.style.left = left + 'px';
6026         },
6027
6028         _getAnchor: function () {
6029                 return [0, 0];
6030         }
6031
6032 });
6033
6034
6035
6036 /*
6037  * @class Popup
6038  * @inherits DivOverlay
6039  * @aka L.Popup
6040  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6041  * open popups while making sure that only one popup is open at one time
6042  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6043  *
6044  * @example
6045  *
6046  * If you want to just bind a popup to marker click and then open it, it's really easy:
6047  *
6048  * ```js
6049  * marker.bindPopup(popupContent).openPopup();
6050  * ```
6051  * Path overlays like polylines also have a `bindPopup` method.
6052  * Here's a more complicated way to open a popup on a map:
6053  *
6054  * ```js
6055  * var popup = L.popup()
6056  *      .setLatLng(latlng)
6057  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6058  *      .openOn(map);
6059  * ```
6060  */
6061
6062
6063 // @namespace Popup
6064 L.Popup = L.DivOverlay.extend({
6065
6066         // @section
6067         // @aka Popup options
6068         options: {
6069                 // @option maxWidth: Number = 300
6070                 // Max width of the popup, in pixels.
6071                 maxWidth: 300,
6072
6073                 // @option minWidth: Number = 50
6074                 // Min width of the popup, in pixels.
6075                 minWidth: 50,
6076
6077                 // @option maxHeight: Number = null
6078                 // If set, creates a scrollable container of the given height
6079                 // inside a popup if its content exceeds it.
6080                 maxHeight: null,
6081
6082                 // @option autoPan: Boolean = true
6083                 // Set it to `false` if you don't want the map to do panning animation
6084                 // to fit the opened popup.
6085                 autoPan: true,
6086
6087                 // @option autoPanPaddingTopLeft: Point = null
6088                 // The margin between the popup and the top left corner of the map
6089                 // view after autopanning was performed.
6090                 autoPanPaddingTopLeft: null,
6091
6092                 // @option autoPanPaddingBottomRight: Point = null
6093                 // The margin between the popup and the bottom right corner of the map
6094                 // view after autopanning was performed.
6095                 autoPanPaddingBottomRight: null,
6096
6097                 // @option autoPanPadding: Point = Point(5, 5)
6098                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
6099                 autoPanPadding: [5, 5],
6100
6101                 // @option keepInView: Boolean = false
6102                 // Set it to `true` if you want to prevent users from panning the popup
6103                 // off of the screen while it is open.
6104                 keepInView: false,
6105
6106                 // @option closeButton: Boolean = true
6107                 // Controls the presence of a close button in the popup.
6108                 closeButton: true,
6109
6110                 // @option autoClose: Boolean = true
6111                 // Set it to `false` if you want to override the default behavior of
6112                 // the popup closing when user clicks the map (set globally by
6113                 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
6114                 autoClose: true,
6115
6116                 // @option className: String = ''
6117                 // A custom CSS class name to assign to the popup.
6118                 className: ''
6119         },
6120
6121         // @namespace Popup
6122         // @method openOn(map: Map): this
6123         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
6124         openOn: function (map) {
6125                 map.openPopup(this);
6126                 return this;
6127         },
6128
6129         onAdd: function (map) {
6130                 L.DivOverlay.prototype.onAdd.call(this, map);
6131
6132                 // @namespace Map
6133                 // @section Popup events
6134                 // @event popupopen: PopupEvent
6135                 // Fired when a popup is opened in the map
6136                 map.fire('popupopen', {popup: this});
6137
6138                 if (this._source) {
6139                         // @namespace Layer
6140                         // @section Popup events
6141                         // @event popupopen: PopupEvent
6142                         // Fired when a popup bound to this layer is opened
6143                         this._source.fire('popupopen', {popup: this}, true);
6144                         // For non-path layers, we toggle the popup when clicking
6145                         // again the layer, so prevent the map to reopen it.
6146                         if (!(this._source instanceof L.Path)) {
6147                                 this._source.on('preclick', L.DomEvent.stopPropagation);
6148                         }
6149                 }
6150         },
6151
6152         onRemove: function (map) {
6153                 L.DivOverlay.prototype.onRemove.call(this, map);
6154
6155                 // @namespace Map
6156                 // @section Popup events
6157                 // @event popupclose: PopupEvent
6158                 // Fired when a popup in the map is closed
6159                 map.fire('popupclose', {popup: this});
6160
6161                 if (this._source) {
6162                         // @namespace Layer
6163                         // @section Popup events
6164                         // @event popupclose: PopupEvent
6165                         // Fired when a popup bound to this layer is closed
6166                         this._source.fire('popupclose', {popup: this}, true);
6167                         if (!(this._source instanceof L.Path)) {
6168                                 this._source.off('preclick', L.DomEvent.stopPropagation);
6169                         }
6170                 }
6171         },
6172
6173         getEvents: function () {
6174                 var events = L.DivOverlay.prototype.getEvents.call(this);
6175
6176                 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
6177                         events.preclick = this._close;
6178                 }
6179
6180                 if (this.options.keepInView) {
6181                         events.moveend = this._adjustPan;
6182                 }
6183
6184                 return events;
6185         },
6186
6187         _close: function () {
6188                 if (this._map) {
6189                         this._map.closePopup(this);
6190                 }
6191         },
6192
6193         _initLayout: function () {
6194                 var prefix = 'leaflet-popup',
6195                     container = this._container = L.DomUtil.create('div',
6196                         prefix + ' ' + (this.options.className || '') +
6197                         ' leaflet-zoom-animated');
6198
6199                 if (this.options.closeButton) {
6200                         var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
6201                         closeButton.href = '#close';
6202                         closeButton.innerHTML = '&#215;';
6203
6204                         L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
6205                 }
6206
6207                 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
6208                 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
6209
6210                 L.DomEvent
6211                         .disableClickPropagation(wrapper)
6212                         .disableScrollPropagation(this._contentNode)
6213                         .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
6214
6215                 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
6216                 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
6217         },
6218
6219         _updateLayout: function () {
6220                 var container = this._contentNode,
6221                     style = container.style;
6222
6223                 style.width = '';
6224                 style.whiteSpace = 'nowrap';
6225
6226                 var width = container.offsetWidth;
6227                 width = Math.min(width, this.options.maxWidth);
6228                 width = Math.max(width, this.options.minWidth);
6229
6230                 style.width = (width + 1) + 'px';
6231                 style.whiteSpace = '';
6232
6233                 style.height = '';
6234
6235                 var height = container.offsetHeight,
6236                     maxHeight = this.options.maxHeight,
6237                     scrolledClass = 'leaflet-popup-scrolled';
6238
6239                 if (maxHeight && height > maxHeight) {
6240                         style.height = maxHeight + 'px';
6241                         L.DomUtil.addClass(container, scrolledClass);
6242                 } else {
6243                         L.DomUtil.removeClass(container, scrolledClass);
6244                 }
6245
6246                 this._containerWidth = this._container.offsetWidth;
6247         },
6248
6249         _animateZoom: function (e) {
6250                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
6251                     anchor = this._getAnchor();
6252                 L.DomUtil.setPosition(this._container, pos.add(anchor));
6253         },
6254
6255         _adjustPan: function () {
6256                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
6257
6258                 var map = this._map,
6259                     marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
6260                     containerHeight = this._container.offsetHeight + marginBottom,
6261                     containerWidth = this._containerWidth,
6262                     layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
6263
6264                 layerPos._add(L.DomUtil.getPosition(this._container));
6265
6266                 var containerPos = map.layerPointToContainerPoint(layerPos),
6267                     padding = L.point(this.options.autoPanPadding),
6268                     paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
6269                     paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
6270                     size = map.getSize(),
6271                     dx = 0,
6272                     dy = 0;
6273
6274                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
6275                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
6276                 }
6277                 if (containerPos.x - dx - paddingTL.x < 0) { // left
6278                         dx = containerPos.x - paddingTL.x;
6279                 }
6280                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
6281                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
6282                 }
6283                 if (containerPos.y - dy - paddingTL.y < 0) { // top
6284                         dy = containerPos.y - paddingTL.y;
6285                 }
6286
6287                 // @namespace Map
6288                 // @section Popup events
6289                 // @event autopanstart: Event
6290                 // Fired when the map starts autopanning when opening a popup.
6291                 if (dx || dy) {
6292                         map
6293                             .fire('autopanstart')
6294                             .panBy([dx, dy]);
6295                 }
6296         },
6297
6298         _onCloseButtonClick: function (e) {
6299                 this._close();
6300                 L.DomEvent.stop(e);
6301         },
6302
6303         _getAnchor: function () {
6304                 // Where should we anchor the popup on the source layer?
6305                 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
6306         }
6307
6308 });
6309
6310 // @namespace Popup
6311 // @factory L.popup(options?: Popup options, source?: Layer)
6312 // 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.
6313 L.popup = function (options, source) {
6314         return new L.Popup(options, source);
6315 };
6316
6317
6318 /* @namespace Map
6319  * @section Interaction Options
6320  * @option closePopupOnClick: Boolean = true
6321  * Set it to `false` if you don't want popups to close when user clicks the map.
6322  */
6323 L.Map.mergeOptions({
6324         closePopupOnClick: true
6325 });
6326
6327
6328 // @namespace Map
6329 // @section Methods for Layers and Controls
6330 L.Map.include({
6331         // @method openPopup(popup: Popup): this
6332         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
6333         // @alternative
6334         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
6335         // Creates a popup with the specified content and options and opens it in the given point on a map.
6336         openPopup: function (popup, latlng, options) {
6337                 if (!(popup instanceof L.Popup)) {
6338                         popup = new L.Popup(options).setContent(popup);
6339                 }
6340
6341                 if (latlng) {
6342                         popup.setLatLng(latlng);
6343                 }
6344
6345                 if (this.hasLayer(popup)) {
6346                         return this;
6347                 }
6348
6349                 if (this._popup && this._popup.options.autoClose) {
6350                         this.closePopup();
6351                 }
6352
6353                 this._popup = popup;
6354                 return this.addLayer(popup);
6355         },
6356
6357         // @method closePopup(popup?: Popup): this
6358         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
6359         closePopup: function (popup) {
6360                 if (!popup || popup === this._popup) {
6361                         popup = this._popup;
6362                         this._popup = null;
6363                 }
6364                 if (popup) {
6365                         this.removeLayer(popup);
6366                 }
6367                 return this;
6368         }
6369 });
6370
6371
6372
6373 /*
6374  * @namespace Layer
6375  * @section Popup methods example
6376  *
6377  * All layers share a set of methods convenient for binding popups to it.
6378  *
6379  * ```js
6380  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
6381  * layer.openPopup();
6382  * layer.closePopup();
6383  * ```
6384  *
6385  * 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.
6386  */
6387
6388 // @section Popup methods
6389 L.Layer.include({
6390
6391         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
6392         // Binds a popup to the layer with the passed `content` and sets up the
6393         // neccessary event listeners. If a `Function` is passed it will receive
6394         // the layer as the first argument and should return a `String` or `HTMLElement`.
6395         bindPopup: function (content, options) {
6396
6397                 if (content instanceof L.Popup) {
6398                         L.setOptions(content, options);
6399                         this._popup = content;
6400                         content._source = this;
6401                 } else {
6402                         if (!this._popup || options) {
6403                                 this._popup = new L.Popup(options, this);
6404                         }
6405                         this._popup.setContent(content);
6406                 }
6407
6408                 if (!this._popupHandlersAdded) {
6409                         this.on({
6410                                 click: this._openPopup,
6411                                 remove: this.closePopup,
6412                                 move: this._movePopup
6413                         });
6414                         this._popupHandlersAdded = true;
6415                 }
6416
6417                 return this;
6418         },
6419
6420         // @method unbindPopup(): this
6421         // Removes the popup previously bound with `bindPopup`.
6422         unbindPopup: function () {
6423                 if (this._popup) {
6424                         this.off({
6425                                 click: this._openPopup,
6426                                 remove: this.closePopup,
6427                                 move: this._movePopup
6428                         });
6429                         this._popupHandlersAdded = false;
6430                         this._popup = null;
6431                 }
6432                 return this;
6433         },
6434
6435         // @method openPopup(latlng?: LatLng): this
6436         // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
6437         openPopup: function (layer, latlng) {
6438                 if (!(layer instanceof L.Layer)) {
6439                         latlng = layer;
6440                         layer = this;
6441                 }
6442
6443                 if (layer instanceof L.FeatureGroup) {
6444                         for (var id in this._layers) {
6445                                 layer = this._layers[id];
6446                                 break;
6447                         }
6448                 }
6449
6450                 if (!latlng) {
6451                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6452                 }
6453
6454                 if (this._popup && this._map) {
6455                         // set popup source to this layer
6456                         this._popup._source = layer;
6457
6458                         // update the popup (content, layout, ect...)
6459                         this._popup.update();
6460
6461                         // open the popup on the map
6462                         this._map.openPopup(this._popup, latlng);
6463                 }
6464
6465                 return this;
6466         },
6467
6468         // @method closePopup(): this
6469         // Closes the popup bound to this layer if it is open.
6470         closePopup: function () {
6471                 if (this._popup) {
6472                         this._popup._close();
6473                 }
6474                 return this;
6475         },
6476
6477         // @method togglePopup(): this
6478         // Opens or closes the popup bound to this layer depending on its current state.
6479         togglePopup: function (target) {
6480                 if (this._popup) {
6481                         if (this._popup._map) {
6482                                 this.closePopup();
6483                         } else {
6484                                 this.openPopup(target);
6485                         }
6486                 }
6487                 return this;
6488         },
6489
6490         // @method isPopupOpen(): boolean
6491         // Returns `true` if the popup bound to this layer is currently open.
6492         isPopupOpen: function () {
6493                 return this._popup.isOpen();
6494         },
6495
6496         // @method setPopupContent(content: String|HTMLElement|Popup): this
6497         // Sets the content of the popup bound to this layer.
6498         setPopupContent: function (content) {
6499                 if (this._popup) {
6500                         this._popup.setContent(content);
6501                 }
6502                 return this;
6503         },
6504
6505         // @method getPopup(): Popup
6506         // Returns the popup bound to this layer.
6507         getPopup: function () {
6508                 return this._popup;
6509         },
6510
6511         _openPopup: function (e) {
6512                 var layer = e.layer || e.target;
6513
6514                 if (!this._popup) {
6515                         return;
6516                 }
6517
6518                 if (!this._map) {
6519                         return;
6520                 }
6521
6522                 // prevent map click
6523                 L.DomEvent.stop(e);
6524
6525                 // if this inherits from Path its a vector and we can just
6526                 // open the popup at the new location
6527                 if (layer instanceof L.Path) {
6528                         this.openPopup(e.layer || e.target, e.latlng);
6529                         return;
6530                 }
6531
6532                 // otherwise treat it like a marker and figure out
6533                 // if we should toggle it open/closed
6534                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
6535                         this.closePopup();
6536                 } else {
6537                         this.openPopup(layer, e.latlng);
6538                 }
6539         },
6540
6541         _movePopup: function (e) {
6542                 this._popup.setLatLng(e.latlng);
6543         }
6544 });
6545
6546
6547
6548 /*
6549  * Popup extension to L.Marker, adding popup-related methods.
6550  */
6551
6552 L.Marker.include({
6553         _getPopupAnchor: function () {
6554                 return this.options.icon.options.popupAnchor || [0, 0];
6555         }
6556 });
6557
6558
6559
6560 /*
6561  * @class Tooltip
6562  * @inherits DivOverlay
6563  * @aka L.Tooltip
6564  * Used to display small texts on top of map layers.
6565  *
6566  * @example
6567  *
6568  * ```js
6569  * marker.bindTooltip("my tooltip text").openTooltip();
6570  * ```
6571  * Note about tooltip offset. Leaflet takes two options in consideration
6572  * for computing tooltip offseting:
6573  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
6574  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
6575  *   move it to the bottom. Negatives will move to the left and top.
6576  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
6577  *   should adapt this value if you use a custom icon.
6578  */
6579
6580
6581 // @namespace Tooltip
6582 L.Tooltip = L.DivOverlay.extend({
6583
6584         // @section
6585         // @aka Tooltip options
6586         options: {
6587                 // @option pane: String = 'tooltipPane'
6588                 // `Map pane` where the tooltip will be added.
6589                 pane: 'tooltipPane',
6590
6591                 // @option offset: Point = Point(0, 0)
6592                 // Optional offset of the tooltip position.
6593                 offset: [0, 0],
6594
6595                 // @option direction: String = 'auto'
6596                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
6597                 // `top`, `bottom`, `center`, `auto`.
6598                 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
6599                 // position on the map.
6600                 direction: 'auto',
6601
6602                 // @option permanent: Boolean = false
6603                 // Whether to open the tooltip permanently or only on mouseover.
6604                 permanent: false,
6605
6606                 // @option sticky: Boolean = false
6607                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
6608                 sticky: false,
6609
6610                 // @option interactive: Boolean = false
6611                 // If true, the tooltip will listen to the feature events.
6612                 interactive: false,
6613
6614                 // @option opacity: Number = 0.9
6615                 // Tooltip container opacity.
6616                 opacity: 0.9
6617         },
6618
6619         onAdd: function (map) {
6620                 L.DivOverlay.prototype.onAdd.call(this, map);
6621                 this.setOpacity(this.options.opacity);
6622
6623                 // @namespace Map
6624                 // @section Tooltip events
6625                 // @event tooltipopen: TooltipEvent
6626                 // Fired when a tooltip is opened in the map.
6627                 map.fire('tooltipopen', {tooltip: this});
6628
6629                 if (this._source) {
6630                         // @namespace Layer
6631                         // @section Tooltip events
6632                         // @event tooltipopen: TooltipEvent
6633                         // Fired when a tooltip bound to this layer is opened.
6634                         this._source.fire('tooltipopen', {tooltip: this}, true);
6635                 }
6636         },
6637
6638         onRemove: function (map) {
6639                 L.DivOverlay.prototype.onRemove.call(this, map);
6640
6641                 // @namespace Map
6642                 // @section Tooltip events
6643                 // @event tooltipclose: TooltipEvent
6644                 // Fired when a tooltip in the map is closed.
6645                 map.fire('tooltipclose', {tooltip: this});
6646
6647                 if (this._source) {
6648                         // @namespace Layer
6649                         // @section Tooltip events
6650                         // @event tooltipclose: TooltipEvent
6651                         // Fired when a tooltip bound to this layer is closed.
6652                         this._source.fire('tooltipclose', {tooltip: this}, true);
6653                 }
6654         },
6655
6656         getEvents: function () {
6657                 var events = L.DivOverlay.prototype.getEvents.call(this);
6658
6659                 if (L.Browser.touch && !this.options.permanent) {
6660                         events.preclick = this._close;
6661                 }
6662
6663                 return events;
6664         },
6665
6666         _close: function () {
6667                 if (this._map) {
6668                         this._map.closeTooltip(this);
6669                 }
6670         },
6671
6672         _initLayout: function () {
6673                 var prefix = 'leaflet-tooltip',
6674                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6675
6676                 this._contentNode = this._container = L.DomUtil.create('div', className);
6677         },
6678
6679         _updateLayout: function () {},
6680
6681         _adjustPan: function () {},
6682
6683         _setPosition: function (pos) {
6684                 var map = this._map,
6685                     container = this._container,
6686                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
6687                     tooltipPoint = map.layerPointToContainerPoint(pos),
6688                     direction = this.options.direction,
6689                     tooltipWidth = container.offsetWidth,
6690                     tooltipHeight = container.offsetHeight,
6691                     offset = L.point(this.options.offset),
6692                     anchor = this._getAnchor();
6693
6694                 if (direction === 'top') {
6695                         pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y));
6696                 } else if (direction === 'bottom') {
6697                         pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y));
6698                 } else if (direction === 'center') {
6699                         pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y));
6700                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
6701                         direction = 'right';
6702                         pos = pos.add([offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y]);
6703                 } else {
6704                         direction = 'left';
6705                         pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y));
6706                 }
6707
6708                 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
6709                 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
6710                 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
6711                 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
6712                 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
6713                 L.DomUtil.setPosition(container, pos);
6714         },
6715
6716         _updatePosition: function () {
6717                 var pos = this._map.latLngToLayerPoint(this._latlng);
6718                 this._setPosition(pos);
6719         },
6720
6721         setOpacity: function (opacity) {
6722                 this.options.opacity = opacity;
6723
6724                 if (this._container) {
6725                         L.DomUtil.setOpacity(this._container, opacity);
6726                 }
6727         },
6728
6729         _animateZoom: function (e) {
6730                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
6731                 this._setPosition(pos);
6732         },
6733
6734         _getAnchor: function () {
6735                 // Where should we anchor the tooltip on the source layer?
6736                 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
6737         }
6738
6739 });
6740
6741 // @namespace Tooltip
6742 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
6743 // 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.
6744 L.tooltip = function (options, source) {
6745         return new L.Tooltip(options, source);
6746 };
6747
6748 // @namespace Map
6749 // @section Methods for Layers and Controls
6750 L.Map.include({
6751
6752         // @method openTooltip(tooltip: Tooltip): this
6753         // Opens the specified tooltip.
6754         // @alternative
6755         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
6756         // Creates a tooltip with the specified content and options and open it.
6757         openTooltip: function (tooltip, latlng, options) {
6758                 if (!(tooltip instanceof L.Tooltip)) {
6759                         tooltip = new L.Tooltip(options).setContent(tooltip);
6760                 }
6761
6762                 if (latlng) {
6763                         tooltip.setLatLng(latlng);
6764                 }
6765
6766                 if (this.hasLayer(tooltip)) {
6767                         return this;
6768                 }
6769
6770                 return this.addLayer(tooltip);
6771         },
6772
6773         // @method closeTooltip(tooltip?: Tooltip): this
6774         // Closes the tooltip given as parameter.
6775         closeTooltip: function (tooltip) {
6776                 if (tooltip) {
6777                         this.removeLayer(tooltip);
6778                 }
6779                 return this;
6780         }
6781
6782 });
6783
6784
6785
6786 /*
6787  * @namespace Layer
6788  * @section Tooltip methods example
6789  *
6790  * All layers share a set of methods convenient for binding tooltips to it.
6791  *
6792  * ```js
6793  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
6794  * layer.openTooltip();
6795  * layer.closeTooltip();
6796  * ```
6797  */
6798
6799 // @section Tooltip methods
6800 L.Layer.include({
6801
6802         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
6803         // Binds a tooltip to the layer with the passed `content` and sets up the
6804         // neccessary event listeners. If a `Function` is passed it will receive
6805         // the layer as the first argument and should return a `String` or `HTMLElement`.
6806         bindTooltip: function (content, options) {
6807
6808                 if (content instanceof L.Tooltip) {
6809                         L.setOptions(content, options);
6810                         this._tooltip = content;
6811                         content._source = this;
6812                 } else {
6813                         if (!this._tooltip || options) {
6814                                 this._tooltip = L.tooltip(options, this);
6815                         }
6816                         this._tooltip.setContent(content);
6817
6818                 }
6819
6820                 this._initTooltipInteractions();
6821
6822                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
6823                         this.openTooltip();
6824                 }
6825
6826                 return this;
6827         },
6828
6829         // @method unbindTooltip(): this
6830         // Removes the tooltip previously bound with `bindTooltip`.
6831         unbindTooltip: function () {
6832                 if (this._tooltip) {
6833                         this._initTooltipInteractions(true);
6834                         this.closeTooltip();
6835                         this._tooltip = null;
6836                 }
6837                 return this;
6838         },
6839
6840         _initTooltipInteractions: function (remove) {
6841                 if (!remove && this._tooltipHandlersAdded) { return; }
6842                 var onOff = remove ? 'off' : 'on',
6843                     events = {
6844                         remove: this.closeTooltip,
6845                         move: this._moveTooltip
6846                     };
6847                 if (!this._tooltip.options.permanent) {
6848                         events.mouseover = this._openTooltip;
6849                         events.mouseout = this.closeTooltip;
6850                         if (this._tooltip.options.sticky) {
6851                                 events.mousemove = this._moveTooltip;
6852                         }
6853                         if (L.Browser.touch) {
6854                                 events.click = this._openTooltip;
6855                         }
6856                 } else {
6857                         events.add = this._openTooltip;
6858                 }
6859                 this[onOff](events);
6860                 this._tooltipHandlersAdded = !remove;
6861         },
6862
6863         // @method openTooltip(latlng?: LatLng): this
6864         // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
6865         openTooltip: function (layer, latlng) {
6866                 if (!(layer instanceof L.Layer)) {
6867                         latlng = layer;
6868                         layer = this;
6869                 }
6870
6871                 if (layer instanceof L.FeatureGroup) {
6872                         for (var id in this._layers) {
6873                                 layer = this._layers[id];
6874                                 break;
6875                         }
6876                 }
6877
6878                 if (!latlng) {
6879                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
6880                 }
6881
6882                 if (this._tooltip && this._map) {
6883
6884                         // set tooltip source to this layer
6885                         this._tooltip._source = layer;
6886
6887                         // update the tooltip (content, layout, ect...)
6888                         this._tooltip.update();
6889
6890                         // open the tooltip on the map
6891                         this._map.openTooltip(this._tooltip, latlng);
6892
6893                         // Tooltip container may not be defined if not permanent and never
6894                         // opened.
6895                         if (this._tooltip.options.interactive && this._tooltip._container) {
6896                                 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
6897                                 this.addInteractiveTarget(this._tooltip._container);
6898                         }
6899                 }
6900
6901                 return this;
6902         },
6903
6904         // @method closeTooltip(): this
6905         // Closes the tooltip bound to this layer if it is open.
6906         closeTooltip: function () {
6907                 if (this._tooltip) {
6908                         this._tooltip._close();
6909                         if (this._tooltip.options.interactive && this._tooltip._container) {
6910                                 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
6911                                 this.removeInteractiveTarget(this._tooltip._container);
6912                         }
6913                 }
6914                 return this;
6915         },
6916
6917         // @method toggleTooltip(): this
6918         // Opens or closes the tooltip bound to this layer depending on its current state.
6919         toggleTooltip: function (target) {
6920                 if (this._tooltip) {
6921                         if (this._tooltip._map) {
6922                                 this.closeTooltip();
6923                         } else {
6924                                 this.openTooltip(target);
6925                         }
6926                 }
6927                 return this;
6928         },
6929
6930         // @method isTooltipOpen(): boolean
6931         // Returns `true` if the tooltip bound to this layer is currently open.
6932         isTooltipOpen: function () {
6933                 return this._tooltip.isOpen();
6934         },
6935
6936         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
6937         // Sets the content of the tooltip bound to this layer.
6938         setTooltipContent: function (content) {
6939                 if (this._tooltip) {
6940                         this._tooltip.setContent(content);
6941                 }
6942                 return this;
6943         },
6944
6945         // @method getTooltip(): Tooltip
6946         // Returns the tooltip bound to this layer.
6947         getTooltip: function () {
6948                 return this._tooltip;
6949         },
6950
6951         _openTooltip: function (e) {
6952                 var layer = e.layer || e.target;
6953
6954                 if (!this._tooltip || !this._map) {
6955                         return;
6956                 }
6957                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
6958         },
6959
6960         _moveTooltip: function (e) {
6961                 var latlng = e.latlng, containerPoint, layerPoint;
6962                 if (this._tooltip.options.sticky && e.originalEvent) {
6963                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
6964                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
6965                         latlng = this._map.layerPointToLatLng(layerPoint);
6966                 }
6967                 this._tooltip.setLatLng(latlng);
6968         }
6969 });
6970
6971
6972
6973 /*
6974  * Tooltip extension to L.Marker, adding tooltip-related methods.
6975  */
6976
6977 L.Marker.include({
6978         _getTooltipAnchor: function () {
6979                 return this.options.icon.options.tooltipAnchor || [0, 0];
6980         }
6981 });
6982
6983
6984
6985 /*
6986  * @class LayerGroup
6987  * @aka L.LayerGroup
6988  * @inherits Layer
6989  *
6990  * Used to group several layers and handle them as one. If you add it to the map,
6991  * any layers added or removed from the group will be added/removed on the map as
6992  * well. Extends `Layer`.
6993  *
6994  * @example
6995  *
6996  * ```js
6997  * L.layerGroup([marker1, marker2])
6998  *      .addLayer(polyline)
6999  *      .addTo(map);
7000  * ```
7001  */
7002
7003 L.LayerGroup = L.Layer.extend({
7004
7005         initialize: function (layers) {
7006                 this._layers = {};
7007
7008                 var i, len;
7009
7010                 if (layers) {
7011                         for (i = 0, len = layers.length; i < len; i++) {
7012                                 this.addLayer(layers[i]);
7013                         }
7014                 }
7015         },
7016
7017         // @method addLayer(layer: Layer): this
7018         // Adds the given layer to the group.
7019         addLayer: function (layer) {
7020                 var id = this.getLayerId(layer);
7021
7022                 this._layers[id] = layer;
7023
7024                 if (this._map) {
7025                         this._map.addLayer(layer);
7026                 }
7027
7028                 return this;
7029         },
7030
7031         // @method removeLayer(layer: Layer): this
7032         // Removes the given layer from the group.
7033         // @alternative
7034         // @method removeLayer(id: Number): this
7035         // Removes the layer with the given internal ID from the group.
7036         removeLayer: function (layer) {
7037                 var id = layer in this._layers ? layer : this.getLayerId(layer);
7038
7039                 if (this._map && this._layers[id]) {
7040                         this._map.removeLayer(this._layers[id]);
7041                 }
7042
7043                 delete this._layers[id];
7044
7045                 return this;
7046         },
7047
7048         // @method hasLayer(layer: Layer): Boolean
7049         // Returns `true` if the given layer is currently added to the group.
7050         hasLayer: function (layer) {
7051                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7052         },
7053
7054         // @method clearLayers(): this
7055         // Removes all the layers from the group.
7056         clearLayers: function () {
7057                 for (var i in this._layers) {
7058                         this.removeLayer(this._layers[i]);
7059                 }
7060                 return this;
7061         },
7062
7063         // @method invoke(methodName: String, …): this
7064         // Calls `methodName` on every layer contained in this group, passing any
7065         // additional parameters. Has no effect if the layers contained do not
7066         // implement `methodName`.
7067         invoke: function (methodName) {
7068                 var args = Array.prototype.slice.call(arguments, 1),
7069                     i, layer;
7070
7071                 for (i in this._layers) {
7072                         layer = this._layers[i];
7073
7074                         if (layer[methodName]) {
7075                                 layer[methodName].apply(layer, args);
7076                         }
7077                 }
7078
7079                 return this;
7080         },
7081
7082         onAdd: function (map) {
7083                 for (var i in this._layers) {
7084                         map.addLayer(this._layers[i]);
7085                 }
7086         },
7087
7088         onRemove: function (map) {
7089                 for (var i in this._layers) {
7090                         map.removeLayer(this._layers[i]);
7091                 }
7092         },
7093
7094         // @method eachLayer(fn: Function, context?: Object): this
7095         // Iterates over the layers of the group, optionally specifying context of the iterator function.
7096         // ```js
7097         // group.eachLayer(function (layer) {
7098         //      layer.bindPopup('Hello');
7099         // });
7100         // ```
7101         eachLayer: function (method, context) {
7102                 for (var i in this._layers) {
7103                         method.call(context, this._layers[i]);
7104                 }
7105                 return this;
7106         },
7107
7108         // @method getLayer(id: Number): Layer
7109         // Returns the layer with the given internal ID.
7110         getLayer: function (id) {
7111                 return this._layers[id];
7112         },
7113
7114         // @method getLayers(): Layer[]
7115         // Returns an array of all the layers added to the group.
7116         getLayers: function () {
7117                 var layers = [];
7118
7119                 for (var i in this._layers) {
7120                         layers.push(this._layers[i]);
7121                 }
7122                 return layers;
7123         },
7124
7125         // @method setZIndex(zIndex: Number): this
7126         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
7127         setZIndex: function (zIndex) {
7128                 return this.invoke('setZIndex', zIndex);
7129         },
7130
7131         // @method getLayerId(layer: Layer): Number
7132         // Returns the internal ID for a layer
7133         getLayerId: function (layer) {
7134                 return L.stamp(layer);
7135         }
7136 });
7137
7138
7139 // @factory L.layerGroup(layers: Layer[])
7140 // Create a layer group, optionally given an initial set of layers.
7141 L.layerGroup = function (layers) {
7142         return new L.LayerGroup(layers);
7143 };
7144
7145
7146
7147 /*
7148  * @class FeatureGroup
7149  * @aka L.FeatureGroup
7150  * @inherits LayerGroup
7151  *
7152  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
7153  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
7154  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
7155  * handler, it will handle events from any of the layers. This includes mouse events
7156  * and custom events.
7157  *  * Has `layeradd` and `layerremove` events
7158  *
7159  * @example
7160  *
7161  * ```js
7162  * L.featureGroup([marker1, marker2, polyline])
7163  *      .bindPopup('Hello world!')
7164  *      .on('click', function() { alert('Clicked on a member of the group!'); })
7165  *      .addTo(map);
7166  * ```
7167  */
7168
7169 L.FeatureGroup = L.LayerGroup.extend({
7170
7171         addLayer: function (layer) {
7172                 if (this.hasLayer(layer)) {
7173                         return this;
7174                 }
7175
7176                 layer.addEventParent(this);
7177
7178                 L.LayerGroup.prototype.addLayer.call(this, layer);
7179
7180                 // @event layeradd: LayerEvent
7181                 // Fired when a layer is added to this `FeatureGroup`
7182                 return this.fire('layeradd', {layer: layer});
7183         },
7184
7185         removeLayer: function (layer) {
7186                 if (!this.hasLayer(layer)) {
7187                         return this;
7188                 }
7189                 if (layer in this._layers) {
7190                         layer = this._layers[layer];
7191                 }
7192
7193                 layer.removeEventParent(this);
7194
7195                 L.LayerGroup.prototype.removeLayer.call(this, layer);
7196
7197                 // @event layerremove: LayerEvent
7198                 // Fired when a layer is removed from this `FeatureGroup`
7199                 return this.fire('layerremove', {layer: layer});
7200         },
7201
7202         // @method setStyle(style: Path options): this
7203         // Sets the given path options to each layer of the group that has a `setStyle` method.
7204         setStyle: function (style) {
7205                 return this.invoke('setStyle', style);
7206         },
7207
7208         // @method bringToFront(): this
7209         // Brings the layer group to the top of all other layers
7210         bringToFront: function () {
7211                 return this.invoke('bringToFront');
7212         },
7213
7214         // @method bringToBack(): this
7215         // Brings the layer group to the top of all other layers
7216         bringToBack: function () {
7217                 return this.invoke('bringToBack');
7218         },
7219
7220         // @method getBounds(): LatLngBounds
7221         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
7222         getBounds: function () {
7223                 var bounds = new L.LatLngBounds();
7224
7225                 for (var id in this._layers) {
7226                         var layer = this._layers[id];
7227                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
7228                 }
7229                 return bounds;
7230         }
7231 });
7232
7233 // @factory L.featureGroup(layers: Layer[])
7234 // Create a feature group, optionally given an initial set of layers.
7235 L.featureGroup = function (layers) {
7236         return new L.FeatureGroup(layers);
7237 };
7238
7239
7240
7241 /*
7242  * @class Renderer
7243  * @inherits Layer
7244  * @aka L.Renderer
7245  *
7246  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
7247  * DOM container of the renderer, its bounds, and its zoom animation.
7248  *
7249  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
7250  * itself can be added or removed to the map. All paths use a renderer, which can
7251  * be implicit (the map will decide the type of renderer and use it automatically)
7252  * or explicit (using the [`renderer`](#path-renderer) option of the path).
7253  *
7254  * Do not use this class directly, use `SVG` and `Canvas` instead.
7255  *
7256  * @event update: Event
7257  * Fired when the renderer updates its bounds, center and zoom, for example when
7258  * its map has moved
7259  */
7260
7261 L.Renderer = L.Layer.extend({
7262
7263         // @section
7264         // @aka Renderer options
7265         options: {
7266                 // @option padding: Number = 0.1
7267                 // How much to extend the clip area around the map view (relative to its size)
7268                 // e.g. 0.1 would be 10% of map view in each direction
7269                 padding: 0.1
7270         },
7271
7272         initialize: function (options) {
7273                 L.setOptions(this, options);
7274                 L.stamp(this);
7275         },
7276
7277         onAdd: function () {
7278                 if (!this._container) {
7279                         this._initContainer(); // defined by renderer implementations
7280
7281                         if (this._zoomAnimated) {
7282                                 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
7283                         }
7284                 }
7285
7286                 this.getPane().appendChild(this._container);
7287                 this._update();
7288         },
7289
7290         onRemove: function () {
7291                 L.DomUtil.remove(this._container);
7292         },
7293
7294         getEvents: function () {
7295                 var events = {
7296                         viewreset: this._reset,
7297                         zoom: this._onZoom,
7298                         moveend: this._update
7299                 };
7300                 if (this._zoomAnimated) {
7301                         events.zoomanim = this._onAnimZoom;
7302                 }
7303                 return events;
7304         },
7305
7306         _onAnimZoom: function (ev) {
7307                 this._updateTransform(ev.center, ev.zoom);
7308         },
7309
7310         _onZoom: function () {
7311                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
7312         },
7313
7314         _updateTransform: function (center, zoom) {
7315                 var scale = this._map.getZoomScale(zoom, this._zoom),
7316                     position = L.DomUtil.getPosition(this._container),
7317                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
7318                     currentCenterPoint = this._map.project(this._center, zoom),
7319                     destCenterPoint = this._map.project(center, zoom),
7320                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
7321
7322                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
7323
7324                 if (L.Browser.any3d) {
7325                         L.DomUtil.setTransform(this._container, topLeftOffset, scale);
7326                 } else {
7327                         L.DomUtil.setPosition(this._container, topLeftOffset);
7328                 }
7329         },
7330
7331         _reset: function () {
7332                 this._update();
7333                 this._updateTransform(this._center, this._zoom);
7334         },
7335
7336         _update: function () {
7337                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
7338                 // Subclasses are responsible of firing the 'update' event.
7339                 var p = this.options.padding,
7340                     size = this._map.getSize(),
7341                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
7342
7343                 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
7344
7345                 this._center = this._map.getCenter();
7346                 this._zoom = this._map.getZoom();
7347         }
7348 });
7349
7350
7351 L.Map.include({
7352         // @namespace Map; @method getRenderer(layer: Path): Renderer
7353         // Returns the instance of `Renderer` that should be used to render the given
7354         // `Path`. It will ensure that the `renderer` options of the map and paths
7355         // are respected, and that the renderers do exist on the map.
7356         getRenderer: function (layer) {
7357                 // @namespace Path; @option renderer: Renderer
7358                 // Use this specific instance of `Renderer` for this path. Takes
7359                 // precedence over the map's [default renderer](#map-renderer).
7360                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
7361
7362                 if (!renderer) {
7363                         // @namespace Map; @option preferCanvas: Boolean = false
7364                         // Whether `Path`s should be rendered on a `Canvas` renderer.
7365                         // By default, all `Path`s are rendered in a `SVG` renderer.
7366                         renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
7367                 }
7368
7369                 if (!this.hasLayer(renderer)) {
7370                         this.addLayer(renderer);
7371                 }
7372                 return renderer;
7373         },
7374
7375         _getPaneRenderer: function (name) {
7376                 if (name === 'overlayPane' || name === undefined) {
7377                         return false;
7378                 }
7379
7380                 var renderer = this._paneRenderers[name];
7381                 if (renderer === undefined) {
7382                         renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
7383                         this._paneRenderers[name] = renderer;
7384                 }
7385                 return renderer;
7386         }
7387 });
7388
7389
7390
7391 /*
7392  * @class Path
7393  * @aka L.Path
7394  * @inherits Interactive layer
7395  *
7396  * An abstract class that contains options and constants shared between vector
7397  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
7398  */
7399
7400 L.Path = L.Layer.extend({
7401
7402         // @section
7403         // @aka Path options
7404         options: {
7405                 // @option stroke: Boolean = true
7406                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
7407                 stroke: true,
7408
7409                 // @option color: String = '#3388ff'
7410                 // Stroke color
7411                 color: '#3388ff',
7412
7413                 // @option weight: Number = 3
7414                 // Stroke width in pixels
7415                 weight: 3,
7416
7417                 // @option opacity: Number = 1.0
7418                 // Stroke opacity
7419                 opacity: 1,
7420
7421                 // @option lineCap: String= 'round'
7422                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
7423                 lineCap: 'round',
7424
7425                 // @option lineJoin: String = 'round'
7426                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
7427                 lineJoin: 'round',
7428
7429                 // @option dashArray: String = null
7430                 // 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).
7431                 dashArray: null,
7432
7433                 // @option dashOffset: String = null
7434                 // 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).
7435                 dashOffset: null,
7436
7437                 // @option fill: Boolean = depends
7438                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
7439                 fill: false,
7440
7441                 // @option fillColor: String = *
7442                 // Fill color. Defaults to the value of the [`color`](#path-color) option
7443                 fillColor: null,
7444
7445                 // @option fillOpacity: Number = 0.2
7446                 // Fill opacity.
7447                 fillOpacity: 0.2,
7448
7449                 // @option fillRule: String = 'evenodd'
7450                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
7451                 fillRule: 'evenodd',
7452
7453                 // className: '',
7454
7455                 // Option inherited from "Interactive layer" abstract class
7456                 interactive: true
7457         },
7458
7459         beforeAdd: function (map) {
7460                 // Renderer is set here because we need to call renderer.getEvents
7461                 // before this.getEvents.
7462                 this._renderer = map.getRenderer(this);
7463         },
7464
7465         onAdd: function () {
7466                 this._renderer._initPath(this);
7467                 this._reset();
7468                 this._renderer._addPath(this);
7469                 this._renderer.on('update', this._update, this);
7470         },
7471
7472         onRemove: function () {
7473                 this._renderer._removePath(this);
7474                 this._renderer.off('update', this._update, this);
7475         },
7476
7477         getEvents: function () {
7478                 return {
7479                         zoomend: this._project,
7480                         viewreset: this._reset
7481                 };
7482         },
7483
7484         // @method redraw(): this
7485         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
7486         redraw: function () {
7487                 if (this._map) {
7488                         this._renderer._updatePath(this);
7489                 }
7490                 return this;
7491         },
7492
7493         // @method setStyle(style: Path options): this
7494         // Changes the appearance of a Path based on the options in the `Path options` object.
7495         setStyle: function (style) {
7496                 L.setOptions(this, style);
7497                 if (this._renderer) {
7498                         this._renderer._updateStyle(this);
7499                 }
7500                 return this;
7501         },
7502
7503         // @method bringToFront(): this
7504         // Brings the layer to the top of all path layers.
7505         bringToFront: function () {
7506                 if (this._renderer) {
7507                         this._renderer._bringToFront(this);
7508                 }
7509                 return this;
7510         },
7511
7512         // @method bringToBack(): this
7513         // Brings the layer to the bottom of all path layers.
7514         bringToBack: function () {
7515                 if (this._renderer) {
7516                         this._renderer._bringToBack(this);
7517                 }
7518                 return this;
7519         },
7520
7521         getElement: function () {
7522                 return this._path;
7523         },
7524
7525         _reset: function () {
7526                 // defined in children classes
7527                 this._project();
7528                 this._update();
7529         },
7530
7531         _clickTolerance: function () {
7532                 // used when doing hit detection for Canvas layers
7533                 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
7534         }
7535 });
7536
7537
7538
7539 /*
7540  * @namespace LineUtil
7541  *
7542  * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
7543  */
7544
7545 L.LineUtil = {
7546
7547         // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
7548         // Improves rendering performance dramatically by lessening the number of points to draw.
7549
7550         // @function simplify(points: Point[], tolerance: Number): Point[]
7551         // Dramatically reduces the number of points in a polyline while retaining
7552         // its shape and returns a new array of simplified points, using the
7553         // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
7554         // Used for a huge performance boost when processing/displaying Leaflet polylines for
7555         // each zoom level and also reducing visual noise. tolerance affects the amount of
7556         // simplification (lesser value means higher quality but slower and with more points).
7557         // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
7558         simplify: function (points, tolerance) {
7559                 if (!tolerance || !points.length) {
7560                         return points.slice();
7561                 }
7562
7563                 var sqTolerance = tolerance * tolerance;
7564
7565                 // stage 1: vertex reduction
7566                 points = this._reducePoints(points, sqTolerance);
7567
7568                 // stage 2: Douglas-Peucker simplification
7569                 points = this._simplifyDP(points, sqTolerance);
7570
7571                 return points;
7572         },
7573
7574         // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
7575         // Returns the distance between point `p` and segment `p1` to `p2`.
7576         pointToSegmentDistance:  function (p, p1, p2) {
7577                 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
7578         },
7579
7580         // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
7581         // Returns the closest point from a point `p` on a segment `p1` to `p2`.
7582         closestPointOnSegment: function (p, p1, p2) {
7583                 return this._sqClosestPointOnSegment(p, p1, p2);
7584         },
7585
7586         // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
7587         _simplifyDP: function (points, sqTolerance) {
7588
7589                 var len = points.length,
7590                     ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
7591                     markers = new ArrayConstructor(len);
7592
7593                 markers[0] = markers[len - 1] = 1;
7594
7595                 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
7596
7597                 var i,
7598                     newPoints = [];
7599
7600                 for (i = 0; i < len; i++) {
7601                         if (markers[i]) {
7602                                 newPoints.push(points[i]);
7603                         }
7604                 }
7605
7606                 return newPoints;
7607         },
7608
7609         _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
7610
7611                 var maxSqDist = 0,
7612                     index, i, sqDist;
7613
7614                 for (i = first + 1; i <= last - 1; i++) {
7615                         sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
7616
7617                         if (sqDist > maxSqDist) {
7618                                 index = i;
7619                                 maxSqDist = sqDist;
7620                         }
7621                 }
7622
7623                 if (maxSqDist > sqTolerance) {
7624                         markers[index] = 1;
7625
7626                         this._simplifyDPStep(points, markers, sqTolerance, first, index);
7627                         this._simplifyDPStep(points, markers, sqTolerance, index, last);
7628                 }
7629         },
7630
7631         // reduce points that are too close to each other to a single point
7632         _reducePoints: function (points, sqTolerance) {
7633                 var reducedPoints = [points[0]];
7634
7635                 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
7636                         if (this._sqDist(points[i], points[prev]) > sqTolerance) {
7637                                 reducedPoints.push(points[i]);
7638                                 prev = i;
7639                         }
7640                 }
7641                 if (prev < len - 1) {
7642                         reducedPoints.push(points[len - 1]);
7643                 }
7644                 return reducedPoints;
7645         },
7646
7647
7648         // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
7649         // Clips the segment a to b by rectangular bounds with the
7650         // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
7651         // (modifying the segment points directly!). Used by Leaflet to only show polyline
7652         // points that are on the screen or near, increasing performance.
7653         clipSegment: function (a, b, bounds, useLastCode, round) {
7654                 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
7655                     codeB = this._getBitCode(b, bounds),
7656
7657                     codeOut, p, newCode;
7658
7659                 // save 2nd code to avoid calculating it on the next segment
7660                 this._lastCode = codeB;
7661
7662                 while (true) {
7663                         // if a,b is inside the clip window (trivial accept)
7664                         if (!(codeA | codeB)) {
7665                                 return [a, b];
7666                         }
7667
7668                         // if a,b is outside the clip window (trivial reject)
7669                         if (codeA & codeB) {
7670                                 return false;
7671                         }
7672
7673                         // other cases
7674                         codeOut = codeA || codeB;
7675                         p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
7676                         newCode = this._getBitCode(p, bounds);
7677
7678                         if (codeOut === codeA) {
7679                                 a = p;
7680                                 codeA = newCode;
7681                         } else {
7682                                 b = p;
7683                                 codeB = newCode;
7684                         }
7685                 }
7686         },
7687
7688         _getEdgeIntersection: function (a, b, code, bounds, round) {
7689                 var dx = b.x - a.x,
7690                     dy = b.y - a.y,
7691                     min = bounds.min,
7692                     max = bounds.max,
7693                     x, y;
7694
7695                 if (code & 8) { // top
7696                         x = a.x + dx * (max.y - a.y) / dy;
7697                         y = max.y;
7698
7699                 } else if (code & 4) { // bottom
7700                         x = a.x + dx * (min.y - a.y) / dy;
7701                         y = min.y;
7702
7703                 } else if (code & 2) { // right
7704                         x = max.x;
7705                         y = a.y + dy * (max.x - a.x) / dx;
7706
7707                 } else if (code & 1) { // left
7708                         x = min.x;
7709                         y = a.y + dy * (min.x - a.x) / dx;
7710                 }
7711
7712                 return new L.Point(x, y, round);
7713         },
7714
7715         _getBitCode: function (p, bounds) {
7716                 var code = 0;
7717
7718                 if (p.x < bounds.min.x) { // left
7719                         code |= 1;
7720                 } else if (p.x > bounds.max.x) { // right
7721                         code |= 2;
7722                 }
7723
7724                 if (p.y < bounds.min.y) { // bottom
7725                         code |= 4;
7726                 } else if (p.y > bounds.max.y) { // top
7727                         code |= 8;
7728                 }
7729
7730                 return code;
7731         },
7732
7733         // square distance (to avoid unnecessary Math.sqrt calls)
7734         _sqDist: function (p1, p2) {
7735                 var dx = p2.x - p1.x,
7736                     dy = p2.y - p1.y;
7737                 return dx * dx + dy * dy;
7738         },
7739
7740         // return closest point on segment or distance to that point
7741         _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
7742                 var x = p1.x,
7743                     y = p1.y,
7744                     dx = p2.x - x,
7745                     dy = p2.y - y,
7746                     dot = dx * dx + dy * dy,
7747                     t;
7748
7749                 if (dot > 0) {
7750                         t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
7751
7752                         if (t > 1) {
7753                                 x = p2.x;
7754                                 y = p2.y;
7755                         } else if (t > 0) {
7756                                 x += dx * t;
7757                                 y += dy * t;
7758                         }
7759                 }
7760
7761                 dx = p.x - x;
7762                 dy = p.y - y;
7763
7764                 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
7765         }
7766 };
7767
7768
7769
7770 /*
7771  * @class Polyline
7772  * @aka L.Polyline
7773  * @inherits Path
7774  *
7775  * A class for drawing polyline overlays on a map. Extends `Path`.
7776  *
7777  * @example
7778  *
7779  * ```js
7780  * // create a red polyline from an array of LatLng points
7781  * var latlngs = [
7782  *      [-122.68, 45.51],
7783  *      [-122.43, 37.77],
7784  *      [-118.2, 34.04]
7785  * ];
7786  *
7787  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
7788  *
7789  * // zoom the map to the polyline
7790  * map.fitBounds(polyline.getBounds());
7791  * ```
7792  *
7793  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
7794  *
7795  * ```js
7796  * // create a red polyline from an array of arrays of LatLng points
7797  * var latlngs = [
7798  *      [[-122.68, 45.51],
7799  *       [-122.43, 37.77],
7800  *       [-118.2, 34.04]],
7801  *      [[-73.91, 40.78],
7802  *       [-87.62, 41.83],
7803  *       [-96.72, 32.76]]
7804  * ];
7805  * ```
7806  */
7807
7808 L.Polyline = L.Path.extend({
7809
7810         // @section
7811         // @aka Polyline options
7812         options: {
7813                 // @option smoothFactor: Number = 1.0
7814                 // How much to simplify the polyline on each zoom level. More means
7815                 // better performance and smoother look, and less means more accurate representation.
7816                 smoothFactor: 1.0,
7817
7818                 // @option noClip: Boolean = false
7819                 // Disable polyline clipping.
7820                 noClip: false
7821         },
7822
7823         initialize: function (latlngs, options) {
7824                 L.setOptions(this, options);
7825                 this._setLatLngs(latlngs);
7826         },
7827
7828         // @method getLatLngs(): LatLng[]
7829         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
7830         getLatLngs: function () {
7831                 return this._latlngs;
7832         },
7833
7834         // @method setLatLngs(latlngs: LatLng[]): this
7835         // Replaces all the points in the polyline with the given array of geographical points.
7836         setLatLngs: function (latlngs) {
7837                 this._setLatLngs(latlngs);
7838                 return this.redraw();
7839         },
7840
7841         // @method isEmpty(): Boolean
7842         // Returns `true` if the Polyline has no LatLngs.
7843         isEmpty: function () {
7844                 return !this._latlngs.length;
7845         },
7846
7847         closestLayerPoint: function (p) {
7848                 var minDistance = Infinity,
7849                     minPoint = null,
7850                     closest = L.LineUtil._sqClosestPointOnSegment,
7851                     p1, p2;
7852
7853                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
7854                         var points = this._parts[j];
7855
7856                         for (var i = 1, len = points.length; i < len; i++) {
7857                                 p1 = points[i - 1];
7858                                 p2 = points[i];
7859
7860                                 var sqDist = closest(p, p1, p2, true);
7861
7862                                 if (sqDist < minDistance) {
7863                                         minDistance = sqDist;
7864                                         minPoint = closest(p, p1, p2);
7865                                 }
7866                         }
7867                 }
7868                 if (minPoint) {
7869                         minPoint.distance = Math.sqrt(minDistance);
7870                 }
7871                 return minPoint;
7872         },
7873
7874         // @method getCenter(): LatLng
7875         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
7876         getCenter: function () {
7877                 // throws error when not yet added to map as this center calculation requires projected coordinates
7878                 if (!this._map) {
7879                         throw new Error('Must add layer to map before using getCenter()');
7880                 }
7881
7882                 var i, halfDist, segDist, dist, p1, p2, ratio,
7883                     points = this._rings[0],
7884                     len = points.length;
7885
7886                 if (!len) { return null; }
7887
7888                 // polyline centroid algorithm; only uses the first ring if there are multiple
7889
7890                 for (i = 0, halfDist = 0; i < len - 1; i++) {
7891                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
7892                 }
7893
7894                 // The line is so small in the current view that all points are on the same pixel.
7895                 if (halfDist === 0) {
7896                         return this._map.layerPointToLatLng(points[0]);
7897                 }
7898
7899                 for (i = 0, dist = 0; i < len - 1; i++) {
7900                         p1 = points[i];
7901                         p2 = points[i + 1];
7902                         segDist = p1.distanceTo(p2);
7903                         dist += segDist;
7904
7905                         if (dist > halfDist) {
7906                                 ratio = (dist - halfDist) / segDist;
7907                                 return this._map.layerPointToLatLng([
7908                                         p2.x - ratio * (p2.x - p1.x),
7909                                         p2.y - ratio * (p2.y - p1.y)
7910                                 ]);
7911                         }
7912                 }
7913         },
7914
7915         // @method getBounds(): LatLngBounds
7916         // Returns the `LatLngBounds` of the path.
7917         getBounds: function () {
7918                 return this._bounds;
7919         },
7920
7921         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
7922         // Adds a given point to the polyline. By default, adds to the first ring of
7923         // the polyline in case of a multi-polyline, but can be overridden by passing
7924         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
7925         addLatLng: function (latlng, latlngs) {
7926                 latlngs = latlngs || this._defaultShape();
7927                 latlng = L.latLng(latlng);
7928                 latlngs.push(latlng);
7929                 this._bounds.extend(latlng);
7930                 return this.redraw();
7931         },
7932
7933         _setLatLngs: function (latlngs) {
7934                 this._bounds = new L.LatLngBounds();
7935                 this._latlngs = this._convertLatLngs(latlngs);
7936         },
7937
7938         _defaultShape: function () {
7939                 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
7940         },
7941
7942         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
7943         _convertLatLngs: function (latlngs) {
7944                 var result = [],
7945                     flat = L.Polyline._flat(latlngs);
7946
7947                 for (var i = 0, len = latlngs.length; i < len; i++) {
7948                         if (flat) {
7949                                 result[i] = L.latLng(latlngs[i]);
7950                                 this._bounds.extend(result[i]);
7951                         } else {
7952                                 result[i] = this._convertLatLngs(latlngs[i]);
7953                         }
7954                 }
7955
7956                 return result;
7957         },
7958
7959         _project: function () {
7960                 var pxBounds = new L.Bounds();
7961                 this._rings = [];
7962                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
7963
7964                 var w = this._clickTolerance(),
7965                     p = new L.Point(w, w);
7966
7967                 if (this._bounds.isValid() && pxBounds.isValid()) {
7968                         pxBounds.min._subtract(p);
7969                         pxBounds.max._add(p);
7970                         this._pxBounds = pxBounds;
7971                 }
7972         },
7973
7974         // recursively turns latlngs into a set of rings with projected coordinates
7975         _projectLatlngs: function (latlngs, result, projectedBounds) {
7976                 var flat = latlngs[0] instanceof L.LatLng,
7977                     len = latlngs.length,
7978                     i, ring;
7979
7980                 if (flat) {
7981                         ring = [];
7982                         for (i = 0; i < len; i++) {
7983                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
7984                                 projectedBounds.extend(ring[i]);
7985                         }
7986                         result.push(ring);
7987                 } else {
7988                         for (i = 0; i < len; i++) {
7989                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
7990                         }
7991                 }
7992         },
7993
7994         // clip polyline by renderer bounds so that we have less to render for performance
7995         _clipPoints: function () {
7996                 var bounds = this._renderer._bounds;
7997
7998                 this._parts = [];
7999                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8000                         return;
8001                 }
8002
8003                 if (this.options.noClip) {
8004                         this._parts = this._rings;
8005                         return;
8006                 }
8007
8008                 var parts = this._parts,
8009                     i, j, k, len, len2, segment, points;
8010
8011                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8012                         points = this._rings[i];
8013
8014                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8015                                 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8016
8017                                 if (!segment) { continue; }
8018
8019                                 parts[k] = parts[k] || [];
8020                                 parts[k].push(segment[0]);
8021
8022                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8023                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8024                                         parts[k].push(segment[1]);
8025                                         k++;
8026                                 }
8027                         }
8028                 }
8029         },
8030
8031         // simplify each clipped part of the polyline for performance
8032         _simplifyPoints: function () {
8033                 var parts = this._parts,
8034                     tolerance = this.options.smoothFactor;
8035
8036                 for (var i = 0, len = parts.length; i < len; i++) {
8037                         parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8038                 }
8039         },
8040
8041         _update: function () {
8042                 if (!this._map) { return; }
8043
8044                 this._clipPoints();
8045                 this._simplifyPoints();
8046                 this._updatePath();
8047         },
8048
8049         _updatePath: function () {
8050                 this._renderer._updatePoly(this);
8051         }
8052 });
8053
8054 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8055 // Instantiates a polyline object given an array of geographical points and
8056 // optionally an options object. You can create a `Polyline` object with
8057 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8058 // of geographic points.
8059 L.polyline = function (latlngs, options) {
8060         return new L.Polyline(latlngs, options);
8061 };
8062
8063 L.Polyline._flat = function (latlngs) {
8064         // true if it's a flat array of latlngs; false if nested
8065         return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8066 };
8067
8068
8069
8070 /*
8071  * @namespace PolyUtil
8072  * Various utility functions for polygon geometries.
8073  */
8074
8075 L.PolyUtil = {};
8076
8077 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8078  * 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)).
8079  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8080  * performance. Note that polygon points needs different algorithm for clipping
8081  * than polyline, so there's a seperate method for it.
8082  */
8083 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8084         var clippedPoints,
8085             edges = [1, 4, 2, 8],
8086             i, j, k,
8087             a, b,
8088             len, edge, p,
8089             lu = L.LineUtil;
8090
8091         for (i = 0, len = points.length; i < len; i++) {
8092                 points[i]._code = lu._getBitCode(points[i], bounds);
8093         }
8094
8095         // for each edge (left, bottom, right, top)
8096         for (k = 0; k < 4; k++) {
8097                 edge = edges[k];
8098                 clippedPoints = [];
8099
8100                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8101                         a = points[i];
8102                         b = points[j];
8103
8104                         // if a is inside the clip window
8105                         if (!(a._code & edge)) {
8106                                 // if b is outside the clip window (a->b goes out of screen)
8107                                 if (b._code & edge) {
8108                                         p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8109                                         p._code = lu._getBitCode(p, bounds);
8110                                         clippedPoints.push(p);
8111                                 }
8112                                 clippedPoints.push(a);
8113
8114                         // else if b is inside the clip window (a->b enters the screen)
8115                         } else if (!(b._code & edge)) {
8116                                 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8117                                 p._code = lu._getBitCode(p, bounds);
8118                                 clippedPoints.push(p);
8119                         }
8120                 }
8121                 points = clippedPoints;
8122         }
8123
8124         return points;
8125 };
8126
8127
8128
8129 /*
8130  * @class Polygon
8131  * @aka L.Polygon
8132  * @inherits Polyline
8133  *
8134  * A class for drawing polygon overlays on a map. Extends `Polyline`.
8135  *
8136  * 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.
8137  *
8138  *
8139  * @example
8140  *
8141  * ```js
8142  * // create a red polygon from an array of LatLng points
8143  * var latlngs = [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]];
8144  *
8145  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
8146  *
8147  * // zoom the map to the polygon
8148  * map.fitBounds(polygon.getBounds());
8149  * ```
8150  *
8151  * 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:
8152  *
8153  * ```js
8154  * var latlngs = [
8155  *   [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8156  *   [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8157  * ];
8158  * ```
8159  *
8160  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
8161  *
8162  * ```js
8163  * var latlngs = [
8164  *   [ // first polygon
8165  *     [[-111.03, 41],[-111.04, 45],[-104.05, 45],[-104.05, 41]], // outer ring
8166  *     [[-108.58,37.29],[-108.58,40.71],[-102.50,40.71],[-102.50,37.29]] // hole
8167  *   ],
8168  *   [ // second polygon
8169  *     [[-109.05, 37],[-109.03, 41],[-102.05, 41],[-102.04, 37],[-109.05, 38]]
8170  *   ]
8171  * ];
8172  * ```
8173  */
8174
8175 L.Polygon = L.Polyline.extend({
8176
8177         options: {
8178                 fill: true
8179         },
8180
8181         isEmpty: function () {
8182                 return !this._latlngs.length || !this._latlngs[0].length;
8183         },
8184
8185         getCenter: function () {
8186                 // throws error when not yet added to map as this center calculation requires projected coordinates
8187                 if (!this._map) {
8188                         throw new Error('Must add layer to map before using getCenter()');
8189                 }
8190
8191                 var i, j, p1, p2, f, area, x, y, center,
8192                     points = this._rings[0],
8193                     len = points.length;
8194
8195                 if (!len) { return null; }
8196
8197                 // polygon centroid algorithm; only uses the first ring if there are multiple
8198
8199                 area = x = y = 0;
8200
8201                 for (i = 0, j = len - 1; i < len; j = i++) {
8202                         p1 = points[i];
8203                         p2 = points[j];
8204
8205                         f = p1.y * p2.x - p2.y * p1.x;
8206                         x += (p1.x + p2.x) * f;
8207                         y += (p1.y + p2.y) * f;
8208                         area += f * 3;
8209                 }
8210
8211                 if (area === 0) {
8212                         // Polygon is so small that all points are on same pixel.
8213                         center = points[0];
8214                 } else {
8215                         center = [x / area, y / area];
8216                 }
8217                 return this._map.layerPointToLatLng(center);
8218         },
8219
8220         _convertLatLngs: function (latlngs) {
8221                 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
8222                     len = result.length;
8223
8224                 // remove last point if it equals first one
8225                 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
8226                         result.pop();
8227                 }
8228                 return result;
8229         },
8230
8231         _setLatLngs: function (latlngs) {
8232                 L.Polyline.prototype._setLatLngs.call(this, latlngs);
8233                 if (L.Polyline._flat(this._latlngs)) {
8234                         this._latlngs = [this._latlngs];
8235                 }
8236         },
8237
8238         _defaultShape: function () {
8239                 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
8240         },
8241
8242         _clipPoints: function () {
8243                 // polygons need a different clipping algorithm so we redefine that
8244
8245                 var bounds = this._renderer._bounds,
8246                     w = this.options.weight,
8247                     p = new L.Point(w, w);
8248
8249                 // increase clip padding by stroke width to avoid stroke on clip edges
8250                 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
8251
8252                 this._parts = [];
8253                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8254                         return;
8255                 }
8256
8257                 if (this.options.noClip) {
8258                         this._parts = this._rings;
8259                         return;
8260                 }
8261
8262                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
8263                         clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
8264                         if (clipped.length) {
8265                                 this._parts.push(clipped);
8266                         }
8267                 }
8268         },
8269
8270         _updatePath: function () {
8271                 this._renderer._updatePoly(this, true);
8272         }
8273 });
8274
8275
8276 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
8277 L.polygon = function (latlngs, options) {
8278         return new L.Polygon(latlngs, options);
8279 };
8280
8281
8282
8283 /*
8284  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
8285  */
8286
8287 /*
8288  * @class Rectangle
8289  * @aka L.Retangle
8290  * @inherits Polygon
8291  *
8292  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
8293  *
8294  * @example
8295  *
8296  * ```js
8297  * // define rectangle geographical bounds
8298  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
8299  *
8300  * // create an orange rectangle
8301  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
8302  *
8303  * // zoom the map to the rectangle bounds
8304  * map.fitBounds(bounds);
8305  * ```
8306  *
8307  */
8308
8309
8310 L.Rectangle = L.Polygon.extend({
8311         initialize: function (latLngBounds, options) {
8312                 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
8313         },
8314
8315         // @method setBounds(latLngBounds: LatLngBounds): this
8316         // Redraws the rectangle with the passed bounds.
8317         setBounds: function (latLngBounds) {
8318                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
8319         },
8320
8321         _boundsToLatLngs: function (latLngBounds) {
8322                 latLngBounds = L.latLngBounds(latLngBounds);
8323                 return [
8324                         latLngBounds.getSouthWest(),
8325                         latLngBounds.getNorthWest(),
8326                         latLngBounds.getNorthEast(),
8327                         latLngBounds.getSouthEast()
8328                 ];
8329         }
8330 });
8331
8332
8333 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
8334 L.rectangle = function (latLngBounds, options) {
8335         return new L.Rectangle(latLngBounds, options);
8336 };
8337
8338
8339
8340 /*
8341  * @class CircleMarker
8342  * @aka L.CircleMarker
8343  * @inherits Path
8344  *
8345  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
8346  */
8347
8348 L.CircleMarker = L.Path.extend({
8349
8350         // @section
8351         // @aka CircleMarker options
8352         options: {
8353                 fill: true,
8354
8355                 // @option radius: Number = 10
8356                 // Radius of the circle marker, in pixels
8357                 radius: 10
8358         },
8359
8360         initialize: function (latlng, options) {
8361                 L.setOptions(this, options);
8362                 this._latlng = L.latLng(latlng);
8363                 this._radius = this.options.radius;
8364         },
8365
8366         // @method setLatLng(latLng: LatLng): this
8367         // Sets the position of a circle marker to a new location.
8368         setLatLng: function (latlng) {
8369                 this._latlng = L.latLng(latlng);
8370                 this.redraw();
8371                 return this.fire('move', {latlng: this._latlng});
8372         },
8373
8374         // @method getLatLng(): LatLng
8375         // Returns the current geographical position of the circle marker
8376         getLatLng: function () {
8377                 return this._latlng;
8378         },
8379
8380         // @method setRadius(radius: Number): this
8381         // Sets the radius of a circle marker. Units are in pixels.
8382         setRadius: function (radius) {
8383                 this.options.radius = this._radius = radius;
8384                 return this.redraw();
8385         },
8386
8387         // @method getRadius(): Number
8388         // Returns the current radius of the circle
8389         getRadius: function () {
8390                 return this._radius;
8391         },
8392
8393         setStyle : function (options) {
8394                 var radius = options && options.radius || this._radius;
8395                 L.Path.prototype.setStyle.call(this, options);
8396                 this.setRadius(radius);
8397                 return this;
8398         },
8399
8400         _project: function () {
8401                 this._point = this._map.latLngToLayerPoint(this._latlng);
8402                 this._updateBounds();
8403         },
8404
8405         _updateBounds: function () {
8406                 var r = this._radius,
8407                     r2 = this._radiusY || r,
8408                     w = this._clickTolerance(),
8409                     p = [r + w, r2 + w];
8410                 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
8411         },
8412
8413         _update: function () {
8414                 if (this._map) {
8415                         this._updatePath();
8416                 }
8417         },
8418
8419         _updatePath: function () {
8420                 this._renderer._updateCircle(this);
8421         },
8422
8423         _empty: function () {
8424                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
8425         }
8426 });
8427
8428
8429 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
8430 // Instantiates a circle marker object given a geographical point, and an optional options object.
8431 L.circleMarker = function (latlng, options) {
8432         return new L.CircleMarker(latlng, options);
8433 };
8434
8435
8436
8437 /*
8438  * @class Circle
8439  * @aka L.Circle
8440  * @inherits CircleMarker
8441  *
8442  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
8443  *
8444  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
8445  *
8446  * @example
8447  *
8448  * ```js
8449  * L.circle([50.5, 30.5], 200).addTo(map);
8450  * ```
8451  */
8452
8453 L.Circle = L.CircleMarker.extend({
8454
8455         initialize: function (latlng, options, legacyOptions) {
8456                 if (typeof options === 'number') {
8457                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
8458                         options = L.extend({}, legacyOptions, {radius: options});
8459                 }
8460                 L.setOptions(this, options);
8461                 this._latlng = L.latLng(latlng);
8462
8463                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
8464
8465                 // @section
8466                 // @aka Circle options
8467                 // @option radius: Number; Radius of the circle, in meters.
8468                 this._mRadius = this.options.radius;
8469         },
8470
8471         // @method setRadius(radius: Number): this
8472         // Sets the radius of a circle. Units are in meters.
8473         setRadius: function (radius) {
8474                 this._mRadius = radius;
8475                 return this.redraw();
8476         },
8477
8478         // @method getRadius(): Number
8479         // Returns the current radius of a circle. Units are in meters.
8480         getRadius: function () {
8481                 return this._mRadius;
8482         },
8483
8484         // @method getBounds(): LatLngBounds
8485         // Returns the `LatLngBounds` of the path.
8486         getBounds: function () {
8487                 var half = [this._radius, this._radiusY || this._radius];
8488
8489                 return new L.LatLngBounds(
8490                         this._map.layerPointToLatLng(this._point.subtract(half)),
8491                         this._map.layerPointToLatLng(this._point.add(half)));
8492         },
8493
8494         setStyle: L.Path.prototype.setStyle,
8495
8496         _project: function () {
8497
8498                 var lng = this._latlng.lng,
8499                     lat = this._latlng.lat,
8500                     map = this._map,
8501                     crs = map.options.crs;
8502
8503                 if (crs.distance === L.CRS.Earth.distance) {
8504                         var d = Math.PI / 180,
8505                             latR = (this._mRadius / L.CRS.Earth.R) / d,
8506                             top = map.project([lat + latR, lng]),
8507                             bottom = map.project([lat - latR, lng]),
8508                             p = top.add(bottom).divideBy(2),
8509                             lat2 = map.unproject(p).lat,
8510                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
8511                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
8512
8513                         if (isNaN(lngR) || lngR === 0) {
8514                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
8515                         }
8516
8517                         this._point = p.subtract(map.getPixelOrigin());
8518                         this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
8519                         this._radiusY = Math.max(Math.round(p.y - top.y), 1);
8520
8521                 } else {
8522                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
8523
8524                         this._point = map.latLngToLayerPoint(this._latlng);
8525                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
8526                 }
8527
8528                 this._updateBounds();
8529         }
8530 });
8531
8532 // @factory L.circle(latlng: LatLng, options?: Circle options)
8533 // Instantiates a circle object given a geographical point, and an options object
8534 // which contains the circle radius.
8535 // @alternative
8536 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
8537 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
8538 // Do not use in new applications or plugins.
8539 L.circle = function (latlng, options, legacyOptions) {
8540         return new L.Circle(latlng, options, legacyOptions);
8541 };
8542
8543
8544
8545 /*
8546  * @class SVG
8547  * @inherits Renderer
8548  * @aka L.SVG
8549  *
8550  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
8551  * Inherits `Renderer`.
8552  *
8553  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
8554  * available in all web browsers, notably Android 2.x and 3.x.
8555  *
8556  * Although SVG is not available on IE7 and IE8, these browsers support
8557  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
8558  * (a now deprecated technology), and the SVG renderer will fall back to VML in
8559  * this case.
8560  *
8561  * @example
8562  *
8563  * Use SVG by default for all paths in the map:
8564  *
8565  * ```js
8566  * var map = L.map('map', {
8567  *      renderer: L.svg()
8568  * });
8569  * ```
8570  *
8571  * Use a SVG renderer with extra padding for specific vector geometries:
8572  *
8573  * ```js
8574  * var map = L.map('map');
8575  * var myRenderer = L.svg({ padding: 0.5 });
8576  * var line = L.polyline( coordinates, { renderer: myRenderer } );
8577  * var circle = L.circle( center, { renderer: myRenderer } );
8578  * ```
8579  */
8580
8581 L.SVG = L.Renderer.extend({
8582
8583         getEvents: function () {
8584                 var events = L.Renderer.prototype.getEvents.call(this);
8585                 events.zoomstart = this._onZoomStart;
8586                 return events;
8587         },
8588
8589         _initContainer: function () {
8590                 this._container = L.SVG.create('svg');
8591
8592                 // makes it possible to click through svg root; we'll reset it back in individual paths
8593                 this._container.setAttribute('pointer-events', 'none');
8594
8595                 this._rootGroup = L.SVG.create('g');
8596                 this._container.appendChild(this._rootGroup);
8597         },
8598
8599         _onZoomStart: function () {
8600                 // Drag-then-pinch interactions might mess up the center and zoom.
8601                 // In this case, the easiest way to prevent this is re-do the renderer
8602                 //   bounds and padding when the zooming starts.
8603                 this._update();
8604         },
8605
8606         _update: function () {
8607                 if (this._map._animatingZoom && this._bounds) { return; }
8608
8609                 L.Renderer.prototype._update.call(this);
8610
8611                 var b = this._bounds,
8612                     size = b.getSize(),
8613                     container = this._container;
8614
8615                 // set size of svg-container if changed
8616                 if (!this._svgSize || !this._svgSize.equals(size)) {
8617                         this._svgSize = size;
8618                         container.setAttribute('width', size.x);
8619                         container.setAttribute('height', size.y);
8620                 }
8621
8622                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
8623                 L.DomUtil.setPosition(container, b.min);
8624                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
8625
8626                 this.fire('update');
8627         },
8628
8629         // methods below are called by vector layers implementations
8630
8631         _initPath: function (layer) {
8632                 var path = layer._path = L.SVG.create('path');
8633
8634                 // @namespace Path
8635                 // @option className: String = null
8636                 // Custom class name set on an element. Only for SVG renderer.
8637                 if (layer.options.className) {
8638                         L.DomUtil.addClass(path, layer.options.className);
8639                 }
8640
8641                 if (layer.options.interactive) {
8642                         L.DomUtil.addClass(path, 'leaflet-interactive');
8643                 }
8644
8645                 this._updateStyle(layer);
8646         },
8647
8648         _addPath: function (layer) {
8649                 this._rootGroup.appendChild(layer._path);
8650                 layer.addInteractiveTarget(layer._path);
8651         },
8652
8653         _removePath: function (layer) {
8654                 L.DomUtil.remove(layer._path);
8655                 layer.removeInteractiveTarget(layer._path);
8656         },
8657
8658         _updatePath: function (layer) {
8659                 layer._project();
8660                 layer._update();
8661         },
8662
8663         _updateStyle: function (layer) {
8664                 var path = layer._path,
8665                     options = layer.options;
8666
8667                 if (!path) { return; }
8668
8669                 if (options.stroke) {
8670                         path.setAttribute('stroke', options.color);
8671                         path.setAttribute('stroke-opacity', options.opacity);
8672                         path.setAttribute('stroke-width', options.weight);
8673                         path.setAttribute('stroke-linecap', options.lineCap);
8674                         path.setAttribute('stroke-linejoin', options.lineJoin);
8675
8676                         if (options.dashArray) {
8677                                 path.setAttribute('stroke-dasharray', options.dashArray);
8678                         } else {
8679                                 path.removeAttribute('stroke-dasharray');
8680                         }
8681
8682                         if (options.dashOffset) {
8683                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
8684                         } else {
8685                                 path.removeAttribute('stroke-dashoffset');
8686                         }
8687                 } else {
8688                         path.setAttribute('stroke', 'none');
8689                 }
8690
8691                 if (options.fill) {
8692                         path.setAttribute('fill', options.fillColor || options.color);
8693                         path.setAttribute('fill-opacity', options.fillOpacity);
8694                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
8695                 } else {
8696                         path.setAttribute('fill', 'none');
8697                 }
8698         },
8699
8700         _updatePoly: function (layer, closed) {
8701                 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
8702         },
8703
8704         _updateCircle: function (layer) {
8705                 var p = layer._point,
8706                     r = layer._radius,
8707                     r2 = layer._radiusY || r,
8708                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
8709
8710                 // drawing a circle with two half-arcs
8711                 var d = layer._empty() ? 'M0 0' :
8712                                 'M' + (p.x - r) + ',' + p.y +
8713                                 arc + (r * 2) + ',0 ' +
8714                                 arc + (-r * 2) + ',0 ';
8715
8716                 this._setPath(layer, d);
8717         },
8718
8719         _setPath: function (layer, path) {
8720                 layer._path.setAttribute('d', path);
8721         },
8722
8723         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
8724         _bringToFront: function (layer) {
8725                 L.DomUtil.toFront(layer._path);
8726         },
8727
8728         _bringToBack: function (layer) {
8729                 L.DomUtil.toBack(layer._path);
8730         }
8731 });
8732
8733
8734 // @namespace SVG; @section
8735 // There are several static functions which can be called without instantiating L.SVG:
8736 L.extend(L.SVG, {
8737         // @function create(name: String): SVGElement
8738         // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
8739         // corresponding to the class name passed. For example, using 'line' will return
8740         // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
8741         create: function (name) {
8742                 return document.createElementNS('http://www.w3.org/2000/svg', name);
8743         },
8744
8745         // @function pointsToPath(rings: Point[], closed: Boolean): String
8746         // Generates a SVG path string for multiple rings, with each ring turning
8747         // into "M..L..L.." instructions
8748         pointsToPath: function (rings, closed) {
8749                 var str = '',
8750                     i, j, len, len2, points, p;
8751
8752                 for (i = 0, len = rings.length; i < len; i++) {
8753                         points = rings[i];
8754
8755                         for (j = 0, len2 = points.length; j < len2; j++) {
8756                                 p = points[j];
8757                                 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
8758                         }
8759
8760                         // closes the ring for polygons; "x" is VML syntax
8761                         str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
8762                 }
8763
8764                 // SVG complains about empty path strings
8765                 return str || 'M0 0';
8766         }
8767 });
8768
8769 // @namespace Browser; @property svg: Boolean
8770 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
8771 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
8772
8773
8774 // @namespace SVG
8775 // @factory L.svg(options?: Renderer options)
8776 // Creates a SVG renderer with the given options.
8777 L.svg = function (options) {
8778         return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
8779 };
8780
8781
8782
8783 /*
8784  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
8785  */
8786
8787 /*
8788  * @class SVG
8789  *
8790  * 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.
8791  *
8792  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
8793  * with old versions of Internet Explorer.
8794  */
8795
8796 // @namespace Browser; @property vml: Boolean
8797 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
8798 L.Browser.vml = !L.Browser.svg && (function () {
8799         try {
8800                 var div = document.createElement('div');
8801                 div.innerHTML = '<v:shape adj="1"/>';
8802
8803                 var shape = div.firstChild;
8804                 shape.style.behavior = 'url(#default#VML)';
8805
8806                 return shape && (typeof shape.adj === 'object');
8807
8808         } catch (e) {
8809                 return false;
8810         }
8811 }());
8812
8813 // redefine some SVG methods to handle VML syntax which is similar but with some differences
8814 L.SVG.include(!L.Browser.vml ? {} : {
8815
8816         _initContainer: function () {
8817                 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
8818         },
8819
8820         _update: function () {
8821                 if (this._map._animatingZoom) { return; }
8822                 L.Renderer.prototype._update.call(this);
8823         },
8824
8825         _initPath: function (layer) {
8826                 var container = layer._container = L.SVG.create('shape');
8827
8828                 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
8829
8830                 container.coordsize = '1 1';
8831
8832                 layer._path = L.SVG.create('path');
8833                 container.appendChild(layer._path);
8834
8835                 this._updateStyle(layer);
8836         },
8837
8838         _addPath: function (layer) {
8839                 var container = layer._container;
8840                 this._container.appendChild(container);
8841
8842                 if (layer.options.interactive) {
8843                         layer.addInteractiveTarget(container);
8844                 }
8845         },
8846
8847         _removePath: function (layer) {
8848                 var container = layer._container;
8849                 L.DomUtil.remove(container);
8850                 layer.removeInteractiveTarget(container);
8851         },
8852
8853         _updateStyle: function (layer) {
8854                 var stroke = layer._stroke,
8855                     fill = layer._fill,
8856                     options = layer.options,
8857                     container = layer._container;
8858
8859                 container.stroked = !!options.stroke;
8860                 container.filled = !!options.fill;
8861
8862                 if (options.stroke) {
8863                         if (!stroke) {
8864                                 stroke = layer._stroke = L.SVG.create('stroke');
8865                         }
8866                         container.appendChild(stroke);
8867                         stroke.weight = options.weight + 'px';
8868                         stroke.color = options.color;
8869                         stroke.opacity = options.opacity;
8870
8871                         if (options.dashArray) {
8872                                 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
8873                                     options.dashArray.join(' ') :
8874                                     options.dashArray.replace(/( *, *)/g, ' ');
8875                         } else {
8876                                 stroke.dashStyle = '';
8877                         }
8878                         stroke.endcap = options.lineCap.replace('butt', 'flat');
8879                         stroke.joinstyle = options.lineJoin;
8880
8881                 } else if (stroke) {
8882                         container.removeChild(stroke);
8883                         layer._stroke = null;
8884                 }
8885
8886                 if (options.fill) {
8887                         if (!fill) {
8888                                 fill = layer._fill = L.SVG.create('fill');
8889                         }
8890                         container.appendChild(fill);
8891                         fill.color = options.fillColor || options.color;
8892                         fill.opacity = options.fillOpacity;
8893
8894                 } else if (fill) {
8895                         container.removeChild(fill);
8896                         layer._fill = null;
8897                 }
8898         },
8899
8900         _updateCircle: function (layer) {
8901                 var p = layer._point.round(),
8902                     r = Math.round(layer._radius),
8903                     r2 = Math.round(layer._radiusY || r);
8904
8905                 this._setPath(layer, layer._empty() ? 'M0 0' :
8906                                 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
8907         },
8908
8909         _setPath: function (layer, path) {
8910                 layer._path.v = path;
8911         },
8912
8913         _bringToFront: function (layer) {
8914                 L.DomUtil.toFront(layer._container);
8915         },
8916
8917         _bringToBack: function (layer) {
8918                 L.DomUtil.toBack(layer._container);
8919         }
8920 });
8921
8922 if (L.Browser.vml) {
8923         L.SVG.create = (function () {
8924                 try {
8925                         document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
8926                         return function (name) {
8927                                 return document.createElement('<lvml:' + name + ' class="lvml">');
8928                         };
8929                 } catch (e) {
8930                         return function (name) {
8931                                 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
8932                         };
8933                 }
8934         })();
8935 }
8936
8937
8938
8939 /*
8940  * @class Canvas
8941  * @inherits Renderer
8942  * @aka L.Canvas
8943  *
8944  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
8945  * Inherits `Renderer`.
8946  *
8947  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
8948  * available in all web browsers, notably IE8, and overlapping geometries might
8949  * not display properly in some edge cases.
8950  *
8951  * @example
8952  *
8953  * Use Canvas by default for all paths in the map:
8954  *
8955  * ```js
8956  * var map = L.map('map', {
8957  *      renderer: L.canvas()
8958  * });
8959  * ```
8960  *
8961  * Use a Canvas renderer with extra padding for specific vector geometries:
8962  *
8963  * ```js
8964  * var map = L.map('map');
8965  * var myRenderer = L.canvas({ padding: 0.5 });
8966  * var line = L.polyline( coordinates, { renderer: myRenderer } );
8967  * var circle = L.circle( center, { renderer: myRenderer } );
8968  * ```
8969  */
8970
8971 L.Canvas = L.Renderer.extend({
8972
8973         onAdd: function () {
8974                 L.Renderer.prototype.onAdd.call(this);
8975
8976                 this._layers = this._layers || {};
8977
8978                 // Redraw vectors since canvas is cleared upon removal,
8979                 // in case of removing the renderer itself from the map.
8980                 this._draw();
8981         },
8982
8983         _initContainer: function () {
8984                 var container = this._container = document.createElement('canvas');
8985
8986                 L.DomEvent
8987                         .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
8988                         .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
8989                         .on(container, 'mouseout', this._handleMouseOut, this);
8990
8991                 this._ctx = container.getContext('2d');
8992         },
8993
8994         _update: function () {
8995                 if (this._map._animatingZoom && this._bounds) { return; }
8996
8997                 this._drawnLayers = {};
8998
8999                 L.Renderer.prototype._update.call(this);
9000
9001                 var b = this._bounds,
9002                     container = this._container,
9003                     size = b.getSize(),
9004                     m = L.Browser.retina ? 2 : 1;
9005
9006                 L.DomUtil.setPosition(container, b.min);
9007
9008                 // set canvas size (also clearing it); use double size on retina
9009                 container.width = m * size.x;
9010                 container.height = m * size.y;
9011                 container.style.width = size.x + 'px';
9012                 container.style.height = size.y + 'px';
9013
9014                 if (L.Browser.retina) {
9015                         this._ctx.scale(2, 2);
9016                 }
9017
9018                 // translate so we use the same path coordinates after canvas element moves
9019                 this._ctx.translate(-b.min.x, -b.min.y);
9020
9021                 // Tell paths to redraw themselves
9022                 this.fire('update');
9023         },
9024
9025         _initPath: function (layer) {
9026                 this._updateDashArray(layer);
9027                 this._layers[L.stamp(layer)] = layer;
9028         },
9029
9030         _addPath: L.Util.falseFn,
9031
9032         _removePath: function (layer) {
9033                 layer._removed = true;
9034                 this._requestRedraw(layer);
9035         },
9036
9037         _updatePath: function (layer) {
9038                 this._redrawBounds = layer._pxBounds;
9039                 this._draw(true);
9040                 layer._project();
9041                 layer._update();
9042                 this._draw();
9043                 this._redrawBounds = null;
9044         },
9045
9046         _updateStyle: function (layer) {
9047                 this._updateDashArray(layer);
9048                 this._requestRedraw(layer);
9049         },
9050
9051         _updateDashArray: function (layer) {
9052                 if (layer.options.dashArray) {
9053                         var parts = layer.options.dashArray.split(','),
9054                             dashArray = [],
9055                             i;
9056                         for (i = 0; i < parts.length; i++) {
9057                                 dashArray.push(Number(parts[i]));
9058                         }
9059                         layer.options._dashArray = dashArray;
9060                 }
9061         },
9062
9063         _requestRedraw: function (layer) {
9064                 if (!this._map) { return; }
9065
9066                 var padding = (layer.options.weight || 0) + 1;
9067                 this._redrawBounds = this._redrawBounds || new L.Bounds();
9068                 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
9069                 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
9070
9071                 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
9072         },
9073
9074         _redraw: function () {
9075                 this._redrawRequest = null;
9076
9077                 this._draw(true); // clear layers in redraw bounds
9078                 this._draw(); // draw layers
9079
9080                 this._redrawBounds = null;
9081         },
9082
9083         _draw: function (clear) {
9084                 this._clear = clear;
9085                 var layer, bounds = this._redrawBounds;
9086                 this._ctx.save();
9087                 if (bounds) {
9088                         this._ctx.beginPath();
9089                         this._ctx.rect(bounds.min.x, bounds.min.y, bounds.max.x - bounds.min.x, bounds.max.y - bounds.min.y);
9090                         this._ctx.clip();
9091                 }
9092
9093                 for (var id in this._layers) {
9094                         layer = this._layers[id];
9095                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
9096                                 layer._updatePath();
9097                         }
9098                         if (clear && layer._removed) {
9099                                 delete layer._removed;
9100                                 delete this._layers[id];
9101                         }
9102                 }
9103                 this._ctx.restore();  // Restore state before clipping.
9104         },
9105
9106         _updatePoly: function (layer, closed) {
9107
9108                 var i, j, len2, p,
9109                     parts = layer._parts,
9110                     len = parts.length,
9111                     ctx = this._ctx;
9112
9113                 if (!len) { return; }
9114
9115                 this._drawnLayers[layer._leaflet_id] = layer;
9116
9117                 ctx.beginPath();
9118
9119                 if (ctx.setLineDash) {
9120                         ctx.setLineDash(layer.options && layer.options._dashArray || []);
9121                 }
9122
9123                 for (i = 0; i < len; i++) {
9124                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
9125                                 p = parts[i][j];
9126                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
9127                         }
9128                         if (closed) {
9129                                 ctx.closePath();
9130                         }
9131                 }
9132
9133                 this._fillStroke(ctx, layer);
9134
9135                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
9136         },
9137
9138         _updateCircle: function (layer) {
9139
9140                 if (layer._empty()) { return; }
9141
9142                 var p = layer._point,
9143                     ctx = this._ctx,
9144                     r = layer._radius,
9145                     s = (layer._radiusY || r) / r;
9146
9147                 this._drawnLayers[layer._leaflet_id] = layer;
9148
9149                 if (s !== 1) {
9150                         ctx.save();
9151                         ctx.scale(1, s);
9152                 }
9153
9154                 ctx.beginPath();
9155                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
9156
9157                 if (s !== 1) {
9158                         ctx.restore();
9159                 }
9160
9161                 this._fillStroke(ctx, layer);
9162         },
9163
9164         _fillStroke: function (ctx, layer) {
9165                 var clear = this._clear,
9166                     options = layer.options;
9167
9168                 ctx.globalCompositeOperation = clear ? 'destination-out' : 'source-over';
9169
9170                 if (options.fill) {
9171                         ctx.globalAlpha = clear ? 1 : options.fillOpacity;
9172                         ctx.fillStyle = options.fillColor || options.color;
9173                         ctx.fill(options.fillRule || 'evenodd');
9174                 }
9175
9176                 if (options.stroke && options.weight !== 0) {
9177                         ctx.globalAlpha = clear ? 1 : options.opacity;
9178
9179                         // if clearing shape, do it with the previously drawn line width
9180                         layer._prevWeight = ctx.lineWidth = clear ? layer._prevWeight + 1 : options.weight;
9181
9182                         ctx.strokeStyle = options.color;
9183                         ctx.lineCap = options.lineCap;
9184                         ctx.lineJoin = options.lineJoin;
9185                         ctx.stroke();
9186                 }
9187         },
9188
9189         // Canvas obviously doesn't have mouse events for individual drawn objects,
9190         // so we emulate that by calculating what's under the mouse on mousemove/click manually
9191
9192         _onClick: function (e) {
9193                 var point = this._map.mouseEventToLayerPoint(e), layers = [], layer;
9194
9195                 for (var id in this._layers) {
9196                         layer = this._layers[id];
9197                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
9198                                 L.DomEvent._fakeStop(e);
9199                                 layers.push(layer);
9200                         }
9201                 }
9202                 if (layers.length)  {
9203                         this._fireEvent(layers, e);
9204                 }
9205         },
9206
9207         _onMouseMove: function (e) {
9208                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
9209
9210                 var point = this._map.mouseEventToLayerPoint(e);
9211                 this._handleMouseOut(e, point);
9212                 this._handleMouseHover(e, point);
9213         },
9214
9215
9216         _handleMouseOut: function (e, point) {
9217                 var layer = this._hoveredLayer;
9218                 if (layer && (e.type === 'mouseout' || !layer._containsPoint(point))) {
9219                         // if we're leaving the layer, fire mouseout
9220                         L.DomUtil.removeClass(this._container, 'leaflet-interactive');
9221                         this._fireEvent([layer], e, 'mouseout');
9222                         this._hoveredLayer = null;
9223                 }
9224         },
9225
9226         _handleMouseHover: function (e, point) {
9227                 var id, layer;
9228
9229                 for (id in this._drawnLayers) {
9230                         layer = this._drawnLayers[id];
9231                         if (layer.options.interactive && layer._containsPoint(point)) {
9232                                 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
9233                                 this._fireEvent([layer], e, 'mouseover');
9234                                 this._hoveredLayer = layer;
9235                         }
9236                 }
9237
9238                 if (this._hoveredLayer) {
9239                         this._fireEvent([this._hoveredLayer], e);
9240                 }
9241         },
9242
9243         _fireEvent: function (layers, e, type) {
9244                 this._map._fireDOMEvent(e, type || e.type, layers);
9245         },
9246
9247         // TODO _bringToFront & _bringToBack, pretty tricky
9248
9249         _bringToFront: L.Util.falseFn,
9250         _bringToBack: L.Util.falseFn
9251 });
9252
9253 // @namespace Browser; @property canvas: Boolean
9254 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9255 L.Browser.canvas = (function () {
9256         return !!document.createElement('canvas').getContext;
9257 }());
9258
9259 // @namespace Canvas
9260 // @factory L.canvas(options?: Renderer options)
9261 // Creates a Canvas renderer with the given options.
9262 L.canvas = function (options) {
9263         return L.Browser.canvas ? new L.Canvas(options) : null;
9264 };
9265
9266 L.Polyline.prototype._containsPoint = function (p, closed) {
9267         var i, j, k, len, len2, part,
9268             w = this._clickTolerance();
9269
9270         if (!this._pxBounds.contains(p)) { return false; }
9271
9272         // hit detection for polylines
9273         for (i = 0, len = this._parts.length; i < len; i++) {
9274                 part = this._parts[i];
9275
9276                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9277                         if (!closed && (j === 0)) { continue; }
9278
9279                         if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
9280                                 return true;
9281                         }
9282                 }
9283         }
9284         return false;
9285 };
9286
9287 L.Polygon.prototype._containsPoint = function (p) {
9288         var inside = false,
9289             part, p1, p2, i, j, k, len, len2;
9290
9291         if (!this._pxBounds.contains(p)) { return false; }
9292
9293         // ray casting algorithm for detecting if point is in polygon
9294         for (i = 0, len = this._parts.length; i < len; i++) {
9295                 part = this._parts[i];
9296
9297                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
9298                         p1 = part[j];
9299                         p2 = part[k];
9300
9301                         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)) {
9302                                 inside = !inside;
9303                         }
9304                 }
9305         }
9306
9307         // also check if it's on polygon stroke
9308         return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
9309 };
9310
9311 L.CircleMarker.prototype._containsPoint = function (p) {
9312         return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
9313 };
9314
9315
9316
9317 /*
9318  * @class GeoJSON
9319  * @aka L.GeoJSON
9320  * @inherits FeatureGroup
9321  *
9322  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
9323  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
9324  *
9325  * @example
9326  *
9327  * ```js
9328  * L.geoJSON(data, {
9329  *      style: function (feature) {
9330  *              return {color: feature.properties.color};
9331  *      }
9332  * }).bindPopup(function (layer) {
9333  *      return layer.feature.properties.description;
9334  * }).addTo(map);
9335  * ```
9336  */
9337
9338 L.GeoJSON = L.FeatureGroup.extend({
9339
9340         /* @section
9341          * @aka GeoJSON options
9342          *
9343          * @option pointToLayer: Function = *
9344          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
9345          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
9346          * The default is to spawn a default `Marker`:
9347          * ```js
9348          * function(geoJsonPoint, latlng) {
9349          *      return L.marker(latlng);
9350          * }
9351          * ```
9352          *
9353          * @option style: Function = *
9354          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
9355          * called internally when data is added.
9356          * The default value is to not override any defaults:
9357          * ```js
9358          * function (geoJsonFeature) {
9359          *      return {}
9360          * }
9361          * ```
9362          *
9363          * @option onEachFeature: Function = *
9364          * A `Function` that will be called once for each created `Feature`, after it has
9365          * been created and styled. Useful for attaching events and popups to features.
9366          * The default is to do nothing with the newly created layers:
9367          * ```js
9368          * function (feature, layer) {}
9369          * ```
9370          *
9371          * @option filter: Function = *
9372          * A `Function` that will be used to decide whether to show a feature or not.
9373          * The default is to show all features:
9374          * ```js
9375          * function (geoJsonFeature) {
9376          *      return true;
9377          * }
9378          * ```
9379          *
9380          * @option coordsToLatLng: Function = *
9381          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
9382          * The default is the `coordsToLatLng` static method.
9383          */
9384
9385         initialize: function (geojson, options) {
9386                 L.setOptions(this, options);
9387
9388                 this._layers = {};
9389
9390                 if (geojson) {
9391                         this.addData(geojson);
9392                 }
9393         },
9394
9395         // @function addData( <GeoJSON> data ): Layer
9396         // Adds a GeoJSON object to the layer.
9397         addData: function (geojson) {
9398                 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
9399                     i, len, feature;
9400
9401                 if (features) {
9402                         for (i = 0, len = features.length; i < len; i++) {
9403                                 // only add this if geometry or geometries are set and not null
9404                                 feature = features[i];
9405                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
9406                                         this.addData(feature);
9407                                 }
9408                         }
9409                         return this;
9410                 }
9411
9412                 var options = this.options;
9413
9414                 if (options.filter && !options.filter(geojson)) { return this; }
9415
9416                 var layer = L.GeoJSON.geometryToLayer(geojson, options);
9417                 if (!layer) {
9418                         return this;
9419                 }
9420                 layer.feature = L.GeoJSON.asFeature(geojson);
9421
9422                 layer.defaultOptions = layer.options;
9423                 this.resetStyle(layer);
9424
9425                 if (options.onEachFeature) {
9426                         options.onEachFeature(geojson, layer);
9427                 }
9428
9429                 return this.addLayer(layer);
9430         },
9431
9432         // @function resetStyle( <Path> layer ): Layer
9433         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
9434         resetStyle: function (layer) {
9435                 // reset any custom styles
9436                 layer.options = L.Util.extend({}, layer.defaultOptions);
9437                 this._setLayerStyle(layer, this.options.style);
9438                 return this;
9439         },
9440
9441         // @function setStyle( <Function> style ): Layer
9442         // Changes styles of GeoJSON vector layers with the given style function.
9443         setStyle: function (style) {
9444                 return this.eachLayer(function (layer) {
9445                         this._setLayerStyle(layer, style);
9446                 }, this);
9447         },
9448
9449         _setLayerStyle: function (layer, style) {
9450                 if (typeof style === 'function') {
9451                         style = style(layer.feature);
9452                 }
9453                 if (layer.setStyle) {
9454                         layer.setStyle(style);
9455                 }
9456         }
9457 });
9458
9459 // @section
9460 // There are several static functions which can be called without instantiating L.GeoJSON:
9461 L.extend(L.GeoJSON, {
9462         // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
9463         // Creates a `Layer` from a given GeoJSON feature. Can use a custom
9464         // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
9465         // functions if provided as options.
9466         geometryToLayer: function (geojson, options) {
9467
9468                 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
9469                     coords = geometry ? geometry.coordinates : null,
9470                     layers = [],
9471                     pointToLayer = options && options.pointToLayer,
9472                     coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
9473                     latlng, latlngs, i, len;
9474
9475                 if (!coords && !geometry) {
9476                         return null;
9477                 }
9478
9479                 switch (geometry.type) {
9480                 case 'Point':
9481                         latlng = coordsToLatLng(coords);
9482                         return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
9483
9484                 case 'MultiPoint':
9485                         for (i = 0, len = coords.length; i < len; i++) {
9486                                 latlng = coordsToLatLng(coords[i]);
9487                                 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
9488                         }
9489                         return new L.FeatureGroup(layers);
9490
9491                 case 'LineString':
9492                 case 'MultiLineString':
9493                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
9494                         return new L.Polyline(latlngs, options);
9495
9496                 case 'Polygon':
9497                 case 'MultiPolygon':
9498                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
9499                         return new L.Polygon(latlngs, options);
9500
9501                 case 'GeometryCollection':
9502                         for (i = 0, len = geometry.geometries.length; i < len; i++) {
9503                                 var layer = this.geometryToLayer({
9504                                         geometry: geometry.geometries[i],
9505                                         type: 'Feature',
9506                                         properties: geojson.properties
9507                                 }, options);
9508
9509                                 if (layer) {
9510                                         layers.push(layer);
9511                                 }
9512                         }
9513                         return new L.FeatureGroup(layers);
9514
9515                 default:
9516                         throw new Error('Invalid GeoJSON object.');
9517                 }
9518         },
9519
9520         // @function coordsToLatLng(coords: Array): LatLng
9521         // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
9522         // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
9523         coordsToLatLng: function (coords) {
9524                 return new L.LatLng(coords[1], coords[0], coords[2]);
9525         },
9526
9527         // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
9528         // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
9529         // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
9530         // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
9531         coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
9532                 var latlngs = [];
9533
9534                 for (var i = 0, len = coords.length, latlng; i < len; i++) {
9535                         latlng = levelsDeep ?
9536                                 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
9537                                 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
9538
9539                         latlngs.push(latlng);
9540                 }
9541
9542                 return latlngs;
9543         },
9544
9545         // @function latLngToCoords(latlng: LatLng): Array
9546         // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
9547         latLngToCoords: function (latlng) {
9548                 return latlng.alt !== undefined ?
9549                                 [latlng.lng, latlng.lat, latlng.alt] :
9550                                 [latlng.lng, latlng.lat];
9551         },
9552
9553         // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
9554         // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
9555         // `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.
9556         latLngsToCoords: function (latlngs, levelsDeep, closed) {
9557                 var coords = [];
9558
9559                 for (var i = 0, len = latlngs.length; i < len; i++) {
9560                         coords.push(levelsDeep ?
9561                                 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
9562                                 L.GeoJSON.latLngToCoords(latlngs[i]));
9563                 }
9564
9565                 if (!levelsDeep && closed) {
9566                         coords.push(coords[0]);
9567                 }
9568
9569                 return coords;
9570         },
9571
9572         getFeature: function (layer, newGeometry) {
9573                 return layer.feature ?
9574                                 L.extend({}, layer.feature, {geometry: newGeometry}) :
9575                                 L.GeoJSON.asFeature(newGeometry);
9576         },
9577
9578         // @function asFeature(geojson: Object): Object
9579         // Normalize GeoJSON geometries/features into GeoJSON features.
9580         asFeature: function (geojson) {
9581                 if (geojson.type === 'Feature') {
9582                         return geojson;
9583                 }
9584
9585                 return {
9586                         type: 'Feature',
9587                         properties: {},
9588                         geometry: geojson
9589                 };
9590         }
9591 });
9592
9593 var PointToGeoJSON = {
9594         toGeoJSON: function () {
9595                 return L.GeoJSON.getFeature(this, {
9596                         type: 'Point',
9597                         coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
9598                 });
9599         }
9600 };
9601
9602 L.Marker.include(PointToGeoJSON);
9603
9604 // @namespace CircleMarker
9605 // @method toGeoJSON(): Object
9606 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
9607 L.Circle.include(PointToGeoJSON);
9608 L.CircleMarker.include(PointToGeoJSON);
9609
9610
9611 // @namespace Polyline
9612 // @method toGeoJSON(): Object
9613 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
9614 L.Polyline.prototype.toGeoJSON = function () {
9615         var multi = !L.Polyline._flat(this._latlngs);
9616
9617         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
9618
9619         return L.GeoJSON.getFeature(this, {
9620                 type: (multi ? 'Multi' : '') + 'LineString',
9621                 coordinates: coords
9622         });
9623 };
9624
9625 // @namespace Polygon
9626 // @method toGeoJSON(): Object
9627 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
9628 L.Polygon.prototype.toGeoJSON = function () {
9629         var holes = !L.Polyline._flat(this._latlngs),
9630             multi = holes && !L.Polyline._flat(this._latlngs[0]);
9631
9632         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
9633
9634         if (!holes) {
9635                 coords = [coords];
9636         }
9637
9638         return L.GeoJSON.getFeature(this, {
9639                 type: (multi ? 'Multi' : '') + 'Polygon',
9640                 coordinates: coords
9641         });
9642 };
9643
9644
9645 // @namespace LayerGroup
9646 L.LayerGroup.include({
9647         toMultiPoint: function () {
9648                 var coords = [];
9649
9650                 this.eachLayer(function (layer) {
9651                         coords.push(layer.toGeoJSON().geometry.coordinates);
9652                 });
9653
9654                 return L.GeoJSON.getFeature(this, {
9655                         type: 'MultiPoint',
9656                         coordinates: coords
9657                 });
9658         },
9659
9660         // @method toGeoJSON(): Object
9661         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
9662         toGeoJSON: function () {
9663
9664                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
9665
9666                 if (type === 'MultiPoint') {
9667                         return this.toMultiPoint();
9668                 }
9669
9670                 var isGeometryCollection = type === 'GeometryCollection',
9671                     jsons = [];
9672
9673                 this.eachLayer(function (layer) {
9674                         if (layer.toGeoJSON) {
9675                                 var json = layer.toGeoJSON();
9676                                 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
9677                         }
9678                 });
9679
9680                 if (isGeometryCollection) {
9681                         return L.GeoJSON.getFeature(this, {
9682                                 geometries: jsons,
9683                                 type: 'GeometryCollection'
9684                         });
9685                 }
9686
9687                 return {
9688                         type: 'FeatureCollection',
9689                         features: jsons
9690                 };
9691         }
9692 });
9693
9694 // @namespace GeoJSON
9695 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
9696 // Creates a GeoJSON layer. Optionally accepts an object in
9697 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
9698 // (you can alternatively add it later with `addData` method) and an `options` object.
9699 L.geoJSON = function (geojson, options) {
9700         return new L.GeoJSON(geojson, options);
9701 };
9702 // Backward compatibility.
9703 L.geoJson = L.geoJSON;
9704
9705
9706
9707 /*
9708  * @namespace DomEvent
9709  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
9710  */
9711
9712 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
9713
9714
9715
9716 var eventsKey = '_leaflet_events';
9717
9718 L.DomEvent = {
9719
9720         // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
9721         // Adds a listener function (`fn`) to a particular DOM event type of the
9722         // element `el`. You can optionally specify the context of the listener
9723         // (object the `this` keyword will point to). You can also pass several
9724         // space-separated types (e.g. `'click dblclick'`).
9725
9726         // @alternative
9727         // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
9728         // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9729         on: function (obj, types, fn, context) {
9730
9731                 if (typeof types === 'object') {
9732                         for (var type in types) {
9733                                 this._on(obj, type, types[type], fn);
9734                         }
9735                 } else {
9736                         types = L.Util.splitWords(types);
9737
9738                         for (var i = 0, len = types.length; i < len; i++) {
9739                                 this._on(obj, types[i], fn, context);
9740                         }
9741                 }
9742
9743                 return this;
9744         },
9745
9746         // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
9747         // Removes a previously added listener function. If no function is specified,
9748         // it will remove all the listeners of that particular DOM event from the element.
9749         // Note that if you passed a custom context to on, you must pass the same
9750         // context to `off` in order to remove the listener.
9751
9752         // @alternative
9753         // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
9754         // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
9755         off: function (obj, types, fn, context) {
9756
9757                 if (typeof types === 'object') {
9758                         for (var type in types) {
9759                                 this._off(obj, type, types[type], fn);
9760                         }
9761                 } else {
9762                         types = L.Util.splitWords(types);
9763
9764                         for (var i = 0, len = types.length; i < len; i++) {
9765                                 this._off(obj, types[i], fn, context);
9766                         }
9767                 }
9768
9769                 return this;
9770         },
9771
9772         _on: function (obj, type, fn, context) {
9773                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
9774
9775                 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
9776
9777                 var handler = function (e) {
9778                         return fn.call(context || obj, e || window.event);
9779                 };
9780
9781                 var originalHandler = handler;
9782
9783                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
9784                         this.addPointerListener(obj, type, handler, id);
9785
9786                 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) {
9787                         this.addDoubleTapListener(obj, handler, id);
9788
9789                 } else if ('addEventListener' in obj) {
9790
9791                         if (type === 'mousewheel') {
9792                                 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9793
9794                         } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
9795                                 handler = function (e) {
9796                                         e = e || window.event;
9797                                         if (L.DomEvent._isExternalTarget(obj, e)) {
9798                                                 originalHandler(e);
9799                                         }
9800                                 };
9801                                 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
9802
9803                         } else {
9804                                 if (type === 'click' && L.Browser.android) {
9805                                         handler = function (e) {
9806                                                 return L.DomEvent._filterClick(e, originalHandler);
9807                                         };
9808                                 }
9809                                 obj.addEventListener(type, handler, false);
9810                         }
9811
9812                 } else if ('attachEvent' in obj) {
9813                         obj.attachEvent('on' + type, handler);
9814                 }
9815
9816                 obj[eventsKey] = obj[eventsKey] || {};
9817                 obj[eventsKey][id] = handler;
9818
9819                 return this;
9820         },
9821
9822         _off: function (obj, type, fn, context) {
9823
9824                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
9825                     handler = obj[eventsKey] && obj[eventsKey][id];
9826
9827                 if (!handler) { return this; }
9828
9829                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
9830                         this.removePointerListener(obj, type, id);
9831
9832                 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
9833                         this.removeDoubleTapListener(obj, id);
9834
9835                 } else if ('removeEventListener' in obj) {
9836
9837                         if (type === 'mousewheel') {
9838                                 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
9839
9840                         } else {
9841                                 obj.removeEventListener(
9842                                         type === 'mouseenter' ? 'mouseover' :
9843                                         type === 'mouseleave' ? 'mouseout' : type, handler, false);
9844                         }
9845
9846                 } else if ('detachEvent' in obj) {
9847                         obj.detachEvent('on' + type, handler);
9848                 }
9849
9850                 obj[eventsKey][id] = null;
9851
9852                 return this;
9853         },
9854
9855         // @function stopPropagation(ev: DOMEvent): this
9856         // Stop the given event from propagation to parent elements. Used inside the listener functions:
9857         // ```js
9858         // L.DomEvent.on(div, 'click', function (ev) {
9859         //      L.DomEvent.stopPropagation(ev);
9860         // });
9861         // ```
9862         stopPropagation: function (e) {
9863
9864                 if (e.stopPropagation) {
9865                         e.stopPropagation();
9866                 } else if (e.originalEvent) {  // In case of Leaflet event.
9867                         e.originalEvent._stopped = true;
9868                 } else {
9869                         e.cancelBubble = true;
9870                 }
9871                 L.DomEvent._skipped(e);
9872
9873                 return this;
9874         },
9875
9876         // @function disableScrollPropagation(el: HTMLElement): this
9877         // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
9878         disableScrollPropagation: function (el) {
9879                 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
9880         },
9881
9882         // @function disableClickPropagation(el: HTMLElement): this
9883         // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
9884         // `'mousedown'` and `'touchstart'` events (plus browser variants).
9885         disableClickPropagation: function (el) {
9886                 var stop = L.DomEvent.stopPropagation;
9887
9888                 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
9889
9890                 return L.DomEvent.on(el, {
9891                         click: L.DomEvent._fakeStop,
9892                         dblclick: stop
9893                 });
9894         },
9895
9896         // @function preventDefault(ev: DOMEvent): this
9897         // Prevents the default action of the DOM Event `ev` from happening (such as
9898         // following a link in the href of the a element, or doing a POST request
9899         // with page reload when a `<form>` is submitted).
9900         // Use it inside listener functions.
9901         preventDefault: function (e) {
9902
9903                 if (e.preventDefault) {
9904                         e.preventDefault();
9905                 } else {
9906                         e.returnValue = false;
9907                 }
9908                 return this;
9909         },
9910
9911         // @function stop(ev): this
9912         // Does `stopPropagation` and `preventDefault` at the same time.
9913         stop: function (e) {
9914                 return L.DomEvent
9915                         .preventDefault(e)
9916                         .stopPropagation(e);
9917         },
9918
9919         // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
9920         // Gets normalized mouse position from a DOM event relative to the
9921         // `container` or to the whole page if not specified.
9922         getMousePosition: function (e, container) {
9923                 if (!container) {
9924                         return new L.Point(e.clientX, e.clientY);
9925                 }
9926
9927                 var rect = container.getBoundingClientRect();
9928
9929                 return new L.Point(
9930                         e.clientX - rect.left - container.clientLeft,
9931                         e.clientY - rect.top - container.clientTop);
9932         },
9933
9934         // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
9935         // and Firefox scrolls device pixels, not CSS pixels
9936         _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
9937                         L.Browser.gecko ? window.devicePixelRatio :
9938                         1,
9939
9940         // @function getWheelDelta(ev: DOMEvent): Number
9941         // Gets normalized wheel delta from a mousewheel DOM event, in vertical
9942         // pixels scrolled (negative if scrolling down).
9943         // Events from pointing devices without precise scrolling are mapped to
9944         // a best guess of 60 pixels.
9945         getWheelDelta: function (e) {
9946                 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
9947                        (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
9948                        (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
9949                        (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
9950                        (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
9951                        e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
9952                        (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
9953                        e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
9954                        0;
9955         },
9956
9957         _skipEvents: {},
9958
9959         _fakeStop: function (e) {
9960                 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
9961                 L.DomEvent._skipEvents[e.type] = true;
9962         },
9963
9964         _skipped: function (e) {
9965                 var skipped = this._skipEvents[e.type];
9966                 // reset when checking, as it's only used in map container and propagates outside of the map
9967                 this._skipEvents[e.type] = false;
9968                 return skipped;
9969         },
9970
9971         // check if element really left/entered the event target (for mouseenter/mouseleave)
9972         _isExternalTarget: function (el, e) {
9973
9974                 var related = e.relatedTarget;
9975
9976                 if (!related) { return true; }
9977
9978                 try {
9979                         while (related && (related !== el)) {
9980                                 related = related.parentNode;
9981                         }
9982                 } catch (err) {
9983                         return false;
9984                 }
9985                 return (related !== el);
9986         },
9987
9988         // this is a horrible workaround for a bug in Android where a single touch triggers two click events
9989         _filterClick: function (e, handler) {
9990                 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
9991                     elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
9992
9993                 // are they closer together than 500ms yet more than 100ms?
9994                 // Android typically triggers them ~300ms apart while multiple listeners
9995                 // on the same event should be triggered far faster;
9996                 // or check if click is simulated on the element, and if it is, reject any non-simulated events
9997
9998                 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
9999                         L.DomEvent.stop(e);
10000                         return;
10001                 }
10002                 L.DomEvent._lastClick = timeStamp;
10003
10004                 handler(e);
10005         }
10006 };
10007
10008 // @function addListener(…): this
10009 // Alias to [`L.DomEvent.on`](#domevent-on)
10010 L.DomEvent.addListener = L.DomEvent.on;
10011
10012 // @function removeListener(…): this
10013 // Alias to [`L.DomEvent.off`](#domevent-off)
10014 L.DomEvent.removeListener = L.DomEvent.off;
10015
10016
10017
10018 /*
10019  * @class Draggable
10020  * @aka L.Draggable
10021  * @inherits Evented
10022  *
10023  * A class for making DOM elements draggable (including touch support).
10024  * Used internally for map and marker dragging. Only works for elements
10025  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10026  *
10027  * @example
10028  * ```js
10029  * var draggable = new L.Draggable(elementToDrag);
10030  * draggable.enable();
10031  * ```
10032  */
10033
10034 L.Draggable = L.Evented.extend({
10035
10036         options: {
10037                 // @option clickTolerance: Number = 3
10038                 // The max number of pixels a user can shift the mouse pointer during a click
10039                 // for it to be considered a valid click (as opposed to a mouse drag).
10040                 clickTolerance: 3
10041         },
10042
10043         statics: {
10044                 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10045                 END: {
10046                         mousedown: 'mouseup',
10047                         touchstart: 'touchend',
10048                         pointerdown: 'touchend',
10049                         MSPointerDown: 'touchend'
10050                 },
10051                 MOVE: {
10052                         mousedown: 'mousemove',
10053                         touchstart: 'touchmove',
10054                         pointerdown: 'touchmove',
10055                         MSPointerDown: 'touchmove'
10056                 }
10057         },
10058
10059         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10060         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10061         initialize: function (element, dragStartTarget, preventOutline) {
10062                 this._element = element;
10063                 this._dragStartTarget = dragStartTarget || element;
10064                 this._preventOutline = preventOutline;
10065         },
10066
10067         // @method enable()
10068         // Enables the dragging ability
10069         enable: function () {
10070                 if (this._enabled) { return; }
10071
10072                 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10073
10074                 this._enabled = true;
10075         },
10076
10077         // @method disable()
10078         // Disables the dragging ability
10079         disable: function () {
10080                 if (!this._enabled) { return; }
10081
10082                 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10083
10084                 this._enabled = false;
10085                 this._moved = false;
10086         },
10087
10088         _onDown: function (e) {
10089                 // Ignore simulated events, since we handle both touch and
10090                 // mouse explicitly; otherwise we risk getting duplicates of
10091                 // touch events, see #4315.
10092                 // Also ignore the event if disabled; this happens in IE11
10093                 // under some circumstances, see #3666.
10094                 if (e._simulated || !this._enabled) { return; }
10095
10096                 this._moved = false;
10097
10098                 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10099
10100                 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches) || !this._enabled) { return; }
10101                 L.Draggable._dragging = true;  // Prevent dragging multiple objects at once.
10102
10103                 if (this._preventOutline) {
10104                         L.DomUtil.preventOutline(this._element);
10105                 }
10106
10107                 L.DomUtil.disableImageDrag();
10108                 L.DomUtil.disableTextSelection();
10109
10110                 if (this._moving) { return; }
10111
10112                 // @event down: Event
10113                 // Fired when a drag is about to start.
10114                 this.fire('down');
10115
10116                 var first = e.touches ? e.touches[0] : e;
10117
10118                 this._startPoint = new L.Point(first.clientX, first.clientY);
10119
10120                 L.DomEvent
10121                         .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10122                         .on(document, L.Draggable.END[e.type], this._onUp, this);
10123         },
10124
10125         _onMove: function (e) {
10126                 // Ignore simulated events, since we handle both touch and
10127                 // mouse explicitly; otherwise we risk getting duplicates of
10128                 // touch events, see #4315.
10129                 // Also ignore the event if disabled; this happens in IE11
10130                 // under some circumstances, see #3666.
10131                 if (e._simulated || !this._enabled) { return; }
10132
10133                 if (e.touches && e.touches.length > 1) {
10134                         this._moved = true;
10135                         return;
10136                 }
10137
10138                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10139                     newPoint = new L.Point(first.clientX, first.clientY),
10140                     offset = newPoint.subtract(this._startPoint);
10141
10142                 if (!offset.x && !offset.y) { return; }
10143                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10144
10145                 L.DomEvent.preventDefault(e);
10146
10147                 if (!this._moved) {
10148                         // @event dragstart: Event
10149                         // Fired when a drag starts
10150                         this.fire('dragstart');
10151
10152                         this._moved = true;
10153                         this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10154
10155                         L.DomUtil.addClass(document.body, 'leaflet-dragging');
10156
10157                         this._lastTarget = e.target || e.srcElement;
10158                         // IE and Edge do not give the <use> element, so fetch it
10159                         // if necessary
10160                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10161                                 this._lastTarget = this._lastTarget.correspondingUseElement;
10162                         }
10163                         L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10164                 }
10165
10166                 this._newPos = this._startPos.add(offset);
10167                 this._moving = true;
10168
10169                 L.Util.cancelAnimFrame(this._animRequest);
10170                 this._lastEvent = e;
10171                 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10172         },
10173
10174         _updatePosition: function () {
10175                 var e = {originalEvent: this._lastEvent};
10176
10177                 // @event predrag: Event
10178                 // Fired continuously during dragging *before* each corresponding
10179                 // update of the element's position.
10180                 this.fire('predrag', e);
10181                 L.DomUtil.setPosition(this._element, this._newPos);
10182
10183                 // @event drag: Event
10184                 // Fired continuously during dragging.
10185                 this.fire('drag', e);
10186         },
10187
10188         _onUp: function (e) {
10189                 // Ignore simulated events, since we handle both touch and
10190                 // mouse explicitly; otherwise we risk getting duplicates of
10191                 // touch events, see #4315.
10192                 // Also ignore the event if disabled; this happens in IE11
10193                 // under some circumstances, see #3666.
10194                 if (e._simulated || !this._enabled) { return; }
10195
10196                 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10197
10198                 if (this._lastTarget) {
10199                         L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10200                         this._lastTarget = null;
10201                 }
10202
10203                 for (var i in L.Draggable.MOVE) {
10204                         L.DomEvent
10205                                 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10206                                 .off(document, L.Draggable.END[i], this._onUp, this);
10207                 }
10208
10209                 L.DomUtil.enableImageDrag();
10210                 L.DomUtil.enableTextSelection();
10211
10212                 if (this._moved && this._moving) {
10213                         // ensure drag is not fired after dragend
10214                         L.Util.cancelAnimFrame(this._animRequest);
10215
10216                         // @event dragend: DragEndEvent
10217                         // Fired when the drag ends.
10218                         this.fire('dragend', {
10219                                 distance: this._newPos.distanceTo(this._startPos)
10220                         });
10221                 }
10222
10223                 this._moving = false;
10224                 L.Draggable._dragging = false;
10225         }
10226 });
10227
10228
10229
10230 /*
10231         L.Handler is a base class for handler classes that are used internally to inject
10232         interaction features like dragging to classes like Map and Marker.
10233 */
10234
10235 // @class Handler
10236 // @aka L.Handler
10237 // Abstract class for map interaction handlers
10238
10239 L.Handler = L.Class.extend({
10240         initialize: function (map) {
10241                 this._map = map;
10242         },
10243
10244         // @method enable(): this
10245         // Enables the handler
10246         enable: function () {
10247                 if (this._enabled) { return this; }
10248
10249                 this._enabled = true;
10250                 this.addHooks();
10251                 return this;
10252         },
10253
10254         // @method disable(): this
10255         // Disables the handler
10256         disable: function () {
10257                 if (!this._enabled) { return this; }
10258
10259                 this._enabled = false;
10260                 this.removeHooks();
10261                 return this;
10262         },
10263
10264         // @method enabled(): Boolean
10265         // Returns `true` if the handler is enabled
10266         enabled: function () {
10267                 return !!this._enabled;
10268         }
10269
10270         // @section Extension methods
10271         // Classes inheriting from `Handler` must implement the two following methods:
10272         // @method addHooks()
10273         // Called when the handler is enabled, should add event hooks.
10274         // @method removeHooks()
10275         // Called when the handler is disabled, should remove the event hooks added previously.
10276 });
10277
10278
10279
10280 /*
10281  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
10282  */
10283
10284 // @namespace Map
10285 // @section Interaction Options
10286 L.Map.mergeOptions({
10287         // @option dragging: Boolean = true
10288         // Whether the map be draggable with mouse/touch or not.
10289         dragging: true,
10290
10291         // @section Panning Inertia Options
10292         // @option inertia: Boolean = *
10293         // If enabled, panning of the map will have an inertia effect where
10294         // the map builds momentum while dragging and continues moving in
10295         // the same direction for some time. Feels especially nice on touch
10296         // devices. Enabled by default unless running on old Android devices.
10297         inertia: !L.Browser.android23,
10298
10299         // @option inertiaDeceleration: Number = 3000
10300         // The rate with which the inertial movement slows down, in pixels/second².
10301         inertiaDeceleration: 3400, // px/s^2
10302
10303         // @option inertiaMaxSpeed: Number = Infinity
10304         // Max speed of the inertial movement, in pixels/second.
10305         inertiaMaxSpeed: Infinity, // px/s
10306
10307         // @option easeLinearity: Number = 0.2
10308         easeLinearity: 0.2,
10309
10310         // TODO refactor, move to CRS
10311         // @option worldCopyJump: Boolean = false
10312         // With this option enabled, the map tracks when you pan to another "copy"
10313         // of the world and seamlessly jumps to the original one so that all overlays
10314         // like markers and vector layers are still visible.
10315         worldCopyJump: false,
10316
10317         // @option maxBoundsViscosity: Number = 0.0
10318         // If `maxBounds` is set, this option will control how solid the bounds
10319         // are when dragging the map around. The default value of `0.0` allows the
10320         // user to drag outside the bounds at normal speed, higher values will
10321         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
10322         // solid, preventing the user from dragging outside the bounds.
10323         maxBoundsViscosity: 0.0
10324 });
10325
10326 L.Map.Drag = L.Handler.extend({
10327         addHooks: function () {
10328                 if (!this._draggable) {
10329                         var map = this._map;
10330
10331                         this._draggable = new L.Draggable(map._mapPane, map._container);
10332
10333                         this._draggable.on({
10334                                 down: this._onDown,
10335                                 dragstart: this._onDragStart,
10336                                 drag: this._onDrag,
10337                                 dragend: this._onDragEnd
10338                         }, this);
10339
10340                         this._draggable.on('predrag', this._onPreDragLimit, this);
10341                         if (map.options.worldCopyJump) {
10342                                 this._draggable.on('predrag', this._onPreDragWrap, this);
10343                                 map.on('zoomend', this._onZoomEnd, this);
10344
10345                                 map.whenReady(this._onZoomEnd, this);
10346                         }
10347                 }
10348                 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
10349                 this._draggable.enable();
10350                 this._positions = [];
10351                 this._times = [];
10352         },
10353
10354         removeHooks: function () {
10355                 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
10356                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
10357                 this._draggable.disable();
10358         },
10359
10360         moved: function () {
10361                 return this._draggable && this._draggable._moved;
10362         },
10363
10364         moving: function () {
10365                 return this._draggable && this._draggable._moving;
10366         },
10367
10368         _onDown: function () {
10369                 this._map._stop();
10370         },
10371
10372         _onDragStart: function () {
10373                 var map = this._map;
10374
10375                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
10376                         var bounds = L.latLngBounds(this._map.options.maxBounds);
10377
10378                         this._offsetLimit = L.bounds(
10379                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
10380                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
10381                                         .add(this._map.getSize()));
10382
10383                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
10384                 } else {
10385                         this._offsetLimit = null;
10386                 }
10387
10388                 map
10389                     .fire('movestart')
10390                     .fire('dragstart');
10391
10392                 if (map.options.inertia) {
10393                         this._positions = [];
10394                         this._times = [];
10395                 }
10396         },
10397
10398         _onDrag: function (e) {
10399                 if (this._map.options.inertia) {
10400                         var time = this._lastTime = +new Date(),
10401                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
10402
10403                         this._positions.push(pos);
10404                         this._times.push(time);
10405
10406                         if (time - this._times[0] > 50) {
10407                                 this._positions.shift();
10408                                 this._times.shift();
10409                         }
10410                 }
10411
10412                 this._map
10413                     .fire('move', e)
10414                     .fire('drag', e);
10415         },
10416
10417         _onZoomEnd: function () {
10418                 var pxCenter = this._map.getSize().divideBy(2),
10419                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
10420
10421                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
10422                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
10423         },
10424
10425         _viscousLimit: function (value, threshold) {
10426                 return value - (value - threshold) * this._viscosity;
10427         },
10428
10429         _onPreDragLimit: function () {
10430                 if (!this._viscosity || !this._offsetLimit) { return; }
10431
10432                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
10433
10434                 var limit = this._offsetLimit;
10435                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
10436                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
10437                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
10438                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
10439
10440                 this._draggable._newPos = this._draggable._startPos.add(offset);
10441         },
10442
10443         _onPreDragWrap: function () {
10444                 // TODO refactor to be able to adjust map pane position after zoom
10445                 var worldWidth = this._worldWidth,
10446                     halfWidth = Math.round(worldWidth / 2),
10447                     dx = this._initialWorldOffset,
10448                     x = this._draggable._newPos.x,
10449                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
10450                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
10451                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
10452
10453                 this._draggable._absPos = this._draggable._newPos.clone();
10454                 this._draggable._newPos.x = newX;
10455         },
10456
10457         _onDragEnd: function (e) {
10458                 var map = this._map,
10459                     options = map.options,
10460
10461                     noInertia = !options.inertia || this._times.length < 2;
10462
10463                 map.fire('dragend', e);
10464
10465                 if (noInertia) {
10466                         map.fire('moveend');
10467
10468                 } else {
10469
10470                         var direction = this._lastPos.subtract(this._positions[0]),
10471                             duration = (this._lastTime - this._times[0]) / 1000,
10472                             ease = options.easeLinearity,
10473
10474                             speedVector = direction.multiplyBy(ease / duration),
10475                             speed = speedVector.distanceTo([0, 0]),
10476
10477                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
10478                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
10479
10480                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
10481                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
10482
10483                         if (!offset.x && !offset.y) {
10484                                 map.fire('moveend');
10485
10486                         } else {
10487                                 offset = map._limitOffset(offset, map.options.maxBounds);
10488
10489                                 L.Util.requestAnimFrame(function () {
10490                                         map.panBy(offset, {
10491                                                 duration: decelerationDuration,
10492                                                 easeLinearity: ease,
10493                                                 noMoveStart: true,
10494                                                 animate: true
10495                                         });
10496                                 });
10497                         }
10498                 }
10499         }
10500 });
10501
10502 // @section Handlers
10503 // @property dragging: Handler
10504 // Map dragging handler (by both mouse and touch).
10505 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
10506
10507
10508
10509 /*
10510  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
10511  */
10512
10513 // @namespace Map
10514 // @section Interaction Options
10515
10516 L.Map.mergeOptions({
10517         // @option doubleClickZoom: Boolean|String = true
10518         // Whether the map can be zoomed in by double clicking on it and
10519         // zoomed out by double clicking while holding shift. If passed
10520         // `'center'`, double-click zoom will zoom to the center of the
10521         //  view regardless of where the mouse was.
10522         doubleClickZoom: true
10523 });
10524
10525 L.Map.DoubleClickZoom = L.Handler.extend({
10526         addHooks: function () {
10527                 this._map.on('dblclick', this._onDoubleClick, this);
10528         },
10529
10530         removeHooks: function () {
10531                 this._map.off('dblclick', this._onDoubleClick, this);
10532         },
10533
10534         _onDoubleClick: function (e) {
10535                 var map = this._map,
10536                     oldZoom = map.getZoom(),
10537                     delta = map.options.zoomDelta,
10538                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
10539
10540                 if (map.options.doubleClickZoom === 'center') {
10541                         map.setZoom(zoom);
10542                 } else {
10543                         map.setZoomAround(e.containerPoint, zoom);
10544                 }
10545         }
10546 });
10547
10548 // @section Handlers
10549 //
10550 // Map properties include interaction handlers that allow you to control
10551 // interaction behavior in runtime, enabling or disabling certain features such
10552 // as dragging or touch zoom (see `Handler` methods). For example:
10553 //
10554 // ```js
10555 // map.doubleClickZoom.disable();
10556 // ```
10557 //
10558 // @property doubleClickZoom: Handler
10559 // Double click zoom handler.
10560 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
10561
10562
10563
10564 /*
10565  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
10566  */
10567
10568 // @namespace Map
10569 // @section Interaction Options
10570 L.Map.mergeOptions({
10571         // @section Mousewheel options
10572         // @option scrollWheelZoom: Boolean|String = true
10573         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
10574         // it will zoom to the center of the view regardless of where the mouse was.
10575         scrollWheelZoom: true,
10576
10577         // @option wheelDebounceTime: Number = 40
10578         // Limits the rate at which a wheel can fire (in milliseconds). By default
10579         // user can't zoom via wheel more often than once per 40 ms.
10580         wheelDebounceTime: 40,
10581
10582         // @option wheelPxPerZoomLevel: Number = 60
10583         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
10584         // mean a change of one full zoom level. Smaller values will make wheel-zooming
10585         // faster (and vice versa).
10586         wheelPxPerZoomLevel: 60
10587 });
10588
10589 L.Map.ScrollWheelZoom = L.Handler.extend({
10590         addHooks: function () {
10591                 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
10592
10593                 this._delta = 0;
10594         },
10595
10596         removeHooks: function () {
10597                 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
10598         },
10599
10600         _onWheelScroll: function (e) {
10601                 var delta = L.DomEvent.getWheelDelta(e);
10602
10603                 var debounce = this._map.options.wheelDebounceTime;
10604
10605                 this._delta += delta;
10606                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
10607
10608                 if (!this._startTime) {
10609                         this._startTime = +new Date();
10610                 }
10611
10612                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
10613
10614                 clearTimeout(this._timer);
10615                 this._timer = setTimeout(L.bind(this._performZoom, this), left);
10616
10617                 L.DomEvent.stop(e);
10618         },
10619
10620         _performZoom: function () {
10621                 var map = this._map,
10622                     zoom = map.getZoom(),
10623                     snap = this._map.options.zoomSnap || 0;
10624
10625                 map._stop(); // stop panning and fly animations if any
10626
10627                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
10628                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
10629                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
10630                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
10631                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
10632
10633                 this._delta = 0;
10634                 this._startTime = null;
10635
10636                 if (!delta) { return; }
10637
10638                 if (map.options.scrollWheelZoom === 'center') {
10639                         map.setZoom(zoom + delta);
10640                 } else {
10641                         map.setZoomAround(this._lastMousePos, zoom + delta);
10642                 }
10643         }
10644 });
10645
10646 // @section Handlers
10647 // @property scrollWheelZoom: Handler
10648 // Scroll wheel zoom handler.
10649 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
10650
10651
10652
10653 /*
10654  * Extends the event handling code with double tap support for mobile browsers.
10655  */
10656
10657 L.extend(L.DomEvent, {
10658
10659         _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
10660         _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
10661
10662         // inspired by Zepto touch code by Thomas Fuchs
10663         addDoubleTapListener: function (obj, handler, id) {
10664                 var last, touch,
10665                     doubleTap = false,
10666                     delay = 250;
10667
10668                 function onTouchStart(e) {
10669                         var count;
10670
10671                         if (L.Browser.pointer) {
10672                                 count = L.DomEvent._pointersCount;
10673                         } else {
10674                                 count = e.touches.length;
10675                         }
10676
10677                         if (count > 1) { return; }
10678
10679                         var now = Date.now(),
10680                             delta = now - (last || now);
10681
10682                         touch = e.touches ? e.touches[0] : e;
10683                         doubleTap = (delta > 0 && delta <= delay);
10684                         last = now;
10685                 }
10686
10687                 function onTouchEnd() {
10688                         if (doubleTap && !touch.cancelBubble) {
10689                                 if (L.Browser.pointer) {
10690                                         // work around .type being readonly with MSPointer* events
10691                                         var newTouch = {},
10692                                             prop, i;
10693
10694                                         for (i in touch) {
10695                                                 prop = touch[i];
10696                                                 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
10697                                         }
10698                                         touch = newTouch;
10699                                 }
10700                                 touch.type = 'dblclick';
10701                                 handler(touch);
10702                                 last = null;
10703                         }
10704                 }
10705
10706                 var pre = '_leaflet_',
10707                     touchstart = this._touchstart,
10708                     touchend = this._touchend;
10709
10710                 obj[pre + touchstart + id] = onTouchStart;
10711                 obj[pre + touchend + id] = onTouchEnd;
10712                 obj[pre + 'dblclick' + id] = handler;
10713
10714                 obj.addEventListener(touchstart, onTouchStart, false);
10715                 obj.addEventListener(touchend, onTouchEnd, false);
10716
10717                 // On some platforms (notably, chrome on win10 + touchscreen + mouse),
10718                 // the browser doesn't fire touchend/pointerup events but does fire
10719                 // native dblclicks. See #4127.
10720                 if (!L.Browser.edge) {
10721                         obj.addEventListener('dblclick', handler, false);
10722                 }
10723
10724                 return this;
10725         },
10726
10727         removeDoubleTapListener: function (obj, id) {
10728                 var pre = '_leaflet_',
10729                     touchstart = obj[pre + this._touchstart + id],
10730                     touchend = obj[pre + this._touchend + id],
10731                     dblclick = obj[pre + 'dblclick' + id];
10732
10733                 obj.removeEventListener(this._touchstart, touchstart, false);
10734                 obj.removeEventListener(this._touchend, touchend, false);
10735                 if (!L.Browser.edge) {
10736                         obj.removeEventListener('dblclick', dblclick, false);
10737                 }
10738
10739                 return this;
10740         }
10741 });
10742
10743
10744
10745 /*
10746  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
10747  */
10748
10749 L.extend(L.DomEvent, {
10750
10751         POINTER_DOWN:   L.Browser.msPointer ? 'MSPointerDown'   : 'pointerdown',
10752         POINTER_MOVE:   L.Browser.msPointer ? 'MSPointerMove'   : 'pointermove',
10753         POINTER_UP:     L.Browser.msPointer ? 'MSPointerUp'     : 'pointerup',
10754         POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
10755         TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
10756
10757         _pointers: {},
10758         _pointersCount: 0,
10759
10760         // Provides a touch events wrapper for (ms)pointer events.
10761         // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
10762
10763         addPointerListener: function (obj, type, handler, id) {
10764
10765                 if (type === 'touchstart') {
10766                         this._addPointerStart(obj, handler, id);
10767
10768                 } else if (type === 'touchmove') {
10769                         this._addPointerMove(obj, handler, id);
10770
10771                 } else if (type === 'touchend') {
10772                         this._addPointerEnd(obj, handler, id);
10773                 }
10774
10775                 return this;
10776         },
10777
10778         removePointerListener: function (obj, type, id) {
10779                 var handler = obj['_leaflet_' + type + id];
10780
10781                 if (type === 'touchstart') {
10782                         obj.removeEventListener(this.POINTER_DOWN, handler, false);
10783
10784                 } else if (type === 'touchmove') {
10785                         obj.removeEventListener(this.POINTER_MOVE, handler, false);
10786
10787                 } else if (type === 'touchend') {
10788                         obj.removeEventListener(this.POINTER_UP, handler, false);
10789                         obj.removeEventListener(this.POINTER_CANCEL, handler, false);
10790                 }
10791
10792                 return this;
10793         },
10794
10795         _addPointerStart: function (obj, handler, id) {
10796                 var onDown = L.bind(function (e) {
10797                         if (e.pointerType !== 'mouse' && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
10798                                 // In IE11, some touch events needs to fire for form controls, or
10799                                 // the controls will stop working. We keep a whitelist of tag names that
10800                                 // need these events. For other target tags, we prevent default on the event.
10801                                 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
10802                                         L.DomEvent.preventDefault(e);
10803                                 } else {
10804                                         return;
10805                                 }
10806                         }
10807
10808                         this._handlePointer(e, handler);
10809                 }, this);
10810
10811                 obj['_leaflet_touchstart' + id] = onDown;
10812                 obj.addEventListener(this.POINTER_DOWN, onDown, false);
10813
10814                 // need to keep track of what pointers and how many are active to provide e.touches emulation
10815                 if (!this._pointerDocListener) {
10816                         var pointerUp = L.bind(this._globalPointerUp, this);
10817
10818                         // we listen documentElement as any drags that end by moving the touch off the screen get fired there
10819                         document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
10820                         document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
10821                         document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
10822                         document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
10823
10824                         this._pointerDocListener = true;
10825                 }
10826         },
10827
10828         _globalPointerDown: function (e) {
10829                 this._pointers[e.pointerId] = e;
10830                 this._pointersCount++;
10831         },
10832
10833         _globalPointerMove: function (e) {
10834                 if (this._pointers[e.pointerId]) {
10835                         this._pointers[e.pointerId] = e;
10836                 }
10837         },
10838
10839         _globalPointerUp: function (e) {
10840                 delete this._pointers[e.pointerId];
10841                 this._pointersCount--;
10842         },
10843
10844         _handlePointer: function (e, handler) {
10845                 e.touches = [];
10846                 for (var i in this._pointers) {
10847                         e.touches.push(this._pointers[i]);
10848                 }
10849                 e.changedTouches = [e];
10850
10851                 handler(e);
10852         },
10853
10854         _addPointerMove: function (obj, handler, id) {
10855                 var onMove = L.bind(function (e) {
10856                         // don't fire touch moves when mouse isn't down
10857                         if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
10858
10859                         this._handlePointer(e, handler);
10860                 }, this);
10861
10862                 obj['_leaflet_touchmove' + id] = onMove;
10863                 obj.addEventListener(this.POINTER_MOVE, onMove, false);
10864         },
10865
10866         _addPointerEnd: function (obj, handler, id) {
10867                 var onUp = L.bind(function (e) {
10868                         this._handlePointer(e, handler);
10869                 }, this);
10870
10871                 obj['_leaflet_touchend' + id] = onUp;
10872                 obj.addEventListener(this.POINTER_UP, onUp, false);
10873                 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
10874         }
10875 });
10876
10877
10878
10879 /*
10880  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
10881  */
10882
10883 // @namespace Map
10884 // @section Interaction Options
10885 L.Map.mergeOptions({
10886         // @section Touch interaction options
10887         // @option touchZoom: Boolean|String = *
10888         // Whether the map can be zoomed by touch-dragging with two fingers. If
10889         // passed `'center'`, it will zoom to the center of the view regardless of
10890         // where the touch events (fingers) were. Enabled for touch-capable web
10891         // browsers except for old Androids.
10892         touchZoom: L.Browser.touch && !L.Browser.android23,
10893
10894         // @option bounceAtZoomLimits: Boolean = true
10895         // Set it to false if you don't want the map to zoom beyond min/max zoom
10896         // and then bounce back when pinch-zooming.
10897         bounceAtZoomLimits: true
10898 });
10899
10900 L.Map.TouchZoom = L.Handler.extend({
10901         addHooks: function () {
10902                 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
10903                 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
10904         },
10905
10906         removeHooks: function () {
10907                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
10908                 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
10909         },
10910
10911         _onTouchStart: function (e) {
10912                 var map = this._map;
10913                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
10914
10915                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
10916                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
10917
10918                 this._centerPoint = map.getSize()._divideBy(2);
10919                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
10920                 if (map.options.touchZoom !== 'center') {
10921                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
10922                 }
10923
10924                 this._startDist = p1.distanceTo(p2);
10925                 this._startZoom = map.getZoom();
10926
10927                 this._moved = false;
10928                 this._zooming = true;
10929
10930                 map._stop();
10931
10932                 L.DomEvent
10933                     .on(document, 'touchmove', this._onTouchMove, this)
10934                     .on(document, 'touchend', this._onTouchEnd, this);
10935
10936                 L.DomEvent.preventDefault(e);
10937         },
10938
10939         _onTouchMove: function (e) {
10940                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
10941
10942                 var map = this._map,
10943                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
10944                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
10945                     scale = p1.distanceTo(p2) / this._startDist;
10946
10947
10948                 this._zoom = map.getScaleZoom(scale, this._startZoom);
10949
10950                 if (!map.options.bounceAtZoomLimits && (
10951                         (this._zoom < map.getMinZoom() && scale < 1) ||
10952                         (this._zoom > map.getMaxZoom() && scale > 1))) {
10953                         this._zoom = map._limitZoom(this._zoom);
10954                 }
10955
10956                 if (map.options.touchZoom === 'center') {
10957                         this._center = this._startLatLng;
10958                         if (scale === 1) { return; }
10959                 } else {
10960                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
10961                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
10962                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
10963                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
10964                 }
10965
10966                 if (!this._moved) {
10967                         map._moveStart(true);
10968                         this._moved = true;
10969                 }
10970
10971                 L.Util.cancelAnimFrame(this._animRequest);
10972
10973                 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
10974                 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
10975
10976                 L.DomEvent.preventDefault(e);
10977         },
10978
10979         _onTouchEnd: function () {
10980                 if (!this._moved || !this._zooming) {
10981                         this._zooming = false;
10982                         return;
10983                 }
10984
10985                 this._zooming = false;
10986                 L.Util.cancelAnimFrame(this._animRequest);
10987
10988                 L.DomEvent
10989                     .off(document, 'touchmove', this._onTouchMove)
10990                     .off(document, 'touchend', this._onTouchEnd);
10991
10992                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
10993                 if (this._map.options.zoomAnimation) {
10994                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
10995                 } else {
10996                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
10997                 }
10998         }
10999 });
11000
11001 // @section Handlers
11002 // @property touchZoom: Handler
11003 // Touch zoom handler.
11004 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11005
11006
11007
11008 /*
11009  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11010  */
11011
11012 // @namespace Map
11013 // @section Interaction Options
11014 L.Map.mergeOptions({
11015         // @section Touch interaction options
11016         // @option tap: Boolean = true
11017         // Enables mobile hacks for supporting instant taps (fixing 200ms click
11018         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11019         tap: true,
11020
11021         // @option tapTolerance: Number = 15
11022         // The max number of pixels a user can shift his finger during touch
11023         // for it to be considered a valid tap.
11024         tapTolerance: 15
11025 });
11026
11027 L.Map.Tap = L.Handler.extend({
11028         addHooks: function () {
11029                 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11030         },
11031
11032         removeHooks: function () {
11033                 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11034         },
11035
11036         _onDown: function (e) {
11037                 if (!e.touches) { return; }
11038
11039                 L.DomEvent.preventDefault(e);
11040
11041                 this._fireClick = true;
11042
11043                 // don't simulate click or track longpress if more than 1 touch
11044                 if (e.touches.length > 1) {
11045                         this._fireClick = false;
11046                         clearTimeout(this._holdTimeout);
11047                         return;
11048                 }
11049
11050                 var first = e.touches[0],
11051                     el = first.target;
11052
11053                 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11054
11055                 // if touching a link, highlight it
11056                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11057                         L.DomUtil.addClass(el, 'leaflet-active');
11058                 }
11059
11060                 // simulate long hold but setting a timeout
11061                 this._holdTimeout = setTimeout(L.bind(function () {
11062                         if (this._isTapValid()) {
11063                                 this._fireClick = false;
11064                                 this._onUp();
11065                                 this._simulateEvent('contextmenu', first);
11066                         }
11067                 }, this), 1000);
11068
11069                 this._simulateEvent('mousedown', first);
11070
11071                 L.DomEvent.on(document, {
11072                         touchmove: this._onMove,
11073                         touchend: this._onUp
11074                 }, this);
11075         },
11076
11077         _onUp: function (e) {
11078                 clearTimeout(this._holdTimeout);
11079
11080                 L.DomEvent.off(document, {
11081                         touchmove: this._onMove,
11082                         touchend: this._onUp
11083                 }, this);
11084
11085                 if (this._fireClick && e && e.changedTouches) {
11086
11087                         var first = e.changedTouches[0],
11088                             el = first.target;
11089
11090                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11091                                 L.DomUtil.removeClass(el, 'leaflet-active');
11092                         }
11093
11094                         this._simulateEvent('mouseup', first);
11095
11096                         // simulate click if the touch didn't move too much
11097                         if (this._isTapValid()) {
11098                                 this._simulateEvent('click', first);
11099                         }
11100                 }
11101         },
11102
11103         _isTapValid: function () {
11104                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11105         },
11106
11107         _onMove: function (e) {
11108                 var first = e.touches[0];
11109                 this._newPos = new L.Point(first.clientX, first.clientY);
11110                 this._simulateEvent('mousemove', first);
11111         },
11112
11113         _simulateEvent: function (type, e) {
11114                 var simulatedEvent = document.createEvent('MouseEvents');
11115
11116                 simulatedEvent._simulated = true;
11117                 e.target._simulatedClick = true;
11118
11119                 simulatedEvent.initMouseEvent(
11120                         type, true, true, window, 1,
11121                         e.screenX, e.screenY,
11122                         e.clientX, e.clientY,
11123                         false, false, false, false, 0, null);
11124
11125                 e.target.dispatchEvent(simulatedEvent);
11126         }
11127 });
11128
11129 // @section Handlers
11130 // @property tap: Handler
11131 // Mobile touch hacks (quick tap and touch hold) handler.
11132 if (L.Browser.touch && !L.Browser.pointer) {
11133         L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11134 }
11135
11136
11137
11138 /*
11139  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11140  * (zoom to a selected bounding box), enabled by default.
11141  */
11142
11143 // @namespace Map
11144 // @section Interaction Options
11145 L.Map.mergeOptions({
11146         // @option boxZoom: Boolean = true
11147         // Whether the map can be zoomed to a rectangular area specified by
11148         // dragging the mouse while pressing the shift key.
11149         boxZoom: true
11150 });
11151
11152 L.Map.BoxZoom = L.Handler.extend({
11153         initialize: function (map) {
11154                 this._map = map;
11155                 this._container = map._container;
11156                 this._pane = map._panes.overlayPane;
11157         },
11158
11159         addHooks: function () {
11160                 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11161         },
11162
11163         removeHooks: function () {
11164                 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11165         },
11166
11167         moved: function () {
11168                 return this._moved;
11169         },
11170
11171         _resetState: function () {
11172                 this._moved = false;
11173         },
11174
11175         _onMouseDown: function (e) {
11176                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11177
11178                 this._resetState();
11179
11180                 L.DomUtil.disableTextSelection();
11181                 L.DomUtil.disableImageDrag();
11182
11183                 this._startPoint = this._map.mouseEventToContainerPoint(e);
11184
11185                 L.DomEvent.on(document, {
11186                         contextmenu: L.DomEvent.stop,
11187                         mousemove: this._onMouseMove,
11188                         mouseup: this._onMouseUp,
11189                         keydown: this._onKeyDown
11190                 }, this);
11191         },
11192
11193         _onMouseMove: function (e) {
11194                 if (!this._moved) {
11195                         this._moved = true;
11196
11197                         this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11198                         L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11199
11200                         this._map.fire('boxzoomstart');
11201                 }
11202
11203                 this._point = this._map.mouseEventToContainerPoint(e);
11204
11205                 var bounds = new L.Bounds(this._point, this._startPoint),
11206                     size = bounds.getSize();
11207
11208                 L.DomUtil.setPosition(this._box, bounds.min);
11209
11210                 this._box.style.width  = size.x + 'px';
11211                 this._box.style.height = size.y + 'px';
11212         },
11213
11214         _finish: function () {
11215                 if (this._moved) {
11216                         L.DomUtil.remove(this._box);
11217                         L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11218                 }
11219
11220                 L.DomUtil.enableTextSelection();
11221                 L.DomUtil.enableImageDrag();
11222
11223                 L.DomEvent.off(document, {
11224                         contextmenu: L.DomEvent.stop,
11225                         mousemove: this._onMouseMove,
11226                         mouseup: this._onMouseUp,
11227                         keydown: this._onKeyDown
11228                 }, this);
11229         },
11230
11231         _onMouseUp: function (e) {
11232                 if ((e.which !== 1) && (e.button !== 1)) { return; }
11233
11234                 this._finish();
11235
11236                 if (!this._moved) { return; }
11237                 // Postpone to next JS tick so internal click event handling
11238                 // still see it as "moved".
11239                 setTimeout(L.bind(this._resetState, this), 0);
11240
11241                 var bounds = new L.LatLngBounds(
11242                         this._map.containerPointToLatLng(this._startPoint),
11243                         this._map.containerPointToLatLng(this._point));
11244
11245                 this._map
11246                         .fitBounds(bounds)
11247                         .fire('boxzoomend', {boxZoomBounds: bounds});
11248         },
11249
11250         _onKeyDown: function (e) {
11251                 if (e.keyCode === 27) {
11252                         this._finish();
11253                 }
11254         }
11255 });
11256
11257 // @section Handlers
11258 // @property boxZoom: Handler
11259 // Box (shift-drag with mouse) zoom handler.
11260 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11261
11262
11263
11264 /*
11265  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11266  */
11267
11268 // @namespace Map
11269 // @section Keyboard Navigation Options
11270 L.Map.mergeOptions({
11271         // @option keyboard: Boolean = true
11272         // Makes the map focusable and allows users to navigate the map with keyboard
11273         // arrows and `+`/`-` keys.
11274         keyboard: true,
11275
11276         // @option keyboardPanDelta: Number = 80
11277         // Amount of pixels to pan when pressing an arrow key.
11278         keyboardPanDelta: 80
11279 });
11280
11281 L.Map.Keyboard = L.Handler.extend({
11282
11283         keyCodes: {
11284                 left:    [37],
11285                 right:   [39],
11286                 down:    [40],
11287                 up:      [38],
11288                 zoomIn:  [187, 107, 61, 171],
11289                 zoomOut: [189, 109, 54, 173]
11290         },
11291
11292         initialize: function (map) {
11293                 this._map = map;
11294
11295                 this._setPanDelta(map.options.keyboardPanDelta);
11296                 this._setZoomDelta(map.options.zoomDelta);
11297         },
11298
11299         addHooks: function () {
11300                 var container = this._map._container;
11301
11302                 // make the container focusable by tabbing
11303                 if (container.tabIndex <= 0) {
11304                         container.tabIndex = '0';
11305                 }
11306
11307                 L.DomEvent.on(container, {
11308                         focus: this._onFocus,
11309                         blur: this._onBlur,
11310                         mousedown: this._onMouseDown
11311                 }, this);
11312
11313                 this._map.on({
11314                         focus: this._addHooks,
11315                         blur: this._removeHooks
11316                 }, this);
11317         },
11318
11319         removeHooks: function () {
11320                 this._removeHooks();
11321
11322                 L.DomEvent.off(this._map._container, {
11323                         focus: this._onFocus,
11324                         blur: this._onBlur,
11325                         mousedown: this._onMouseDown
11326                 }, this);
11327
11328                 this._map.off({
11329                         focus: this._addHooks,
11330                         blur: this._removeHooks
11331                 }, this);
11332         },
11333
11334         _onMouseDown: function () {
11335                 if (this._focused) { return; }
11336
11337                 var body = document.body,
11338                     docEl = document.documentElement,
11339                     top = body.scrollTop || docEl.scrollTop,
11340                     left = body.scrollLeft || docEl.scrollLeft;
11341
11342                 this._map._container.focus();
11343
11344                 window.scrollTo(left, top);
11345         },
11346
11347         _onFocus: function () {
11348                 this._focused = true;
11349                 this._map.fire('focus');
11350         },
11351
11352         _onBlur: function () {
11353                 this._focused = false;
11354                 this._map.fire('blur');
11355         },
11356
11357         _setPanDelta: function (panDelta) {
11358                 var keys = this._panKeys = {},
11359                     codes = this.keyCodes,
11360                     i, len;
11361
11362                 for (i = 0, len = codes.left.length; i < len; i++) {
11363                         keys[codes.left[i]] = [-1 * panDelta, 0];
11364                 }
11365                 for (i = 0, len = codes.right.length; i < len; i++) {
11366                         keys[codes.right[i]] = [panDelta, 0];
11367                 }
11368                 for (i = 0, len = codes.down.length; i < len; i++) {
11369                         keys[codes.down[i]] = [0, panDelta];
11370                 }
11371                 for (i = 0, len = codes.up.length; i < len; i++) {
11372                         keys[codes.up[i]] = [0, -1 * panDelta];
11373                 }
11374         },
11375
11376         _setZoomDelta: function (zoomDelta) {
11377                 var keys = this._zoomKeys = {},
11378                     codes = this.keyCodes,
11379                     i, len;
11380
11381                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
11382                         keys[codes.zoomIn[i]] = zoomDelta;
11383                 }
11384                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
11385                         keys[codes.zoomOut[i]] = -zoomDelta;
11386                 }
11387         },
11388
11389         _addHooks: function () {
11390                 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
11391         },
11392
11393         _removeHooks: function () {
11394                 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
11395         },
11396
11397         _onKeyDown: function (e) {
11398                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
11399
11400                 var key = e.keyCode,
11401                     map = this._map,
11402                     offset;
11403
11404                 if (key in this._panKeys) {
11405
11406                         if (map._panAnim && map._panAnim._inProgress) { return; }
11407
11408                         offset = this._panKeys[key];
11409                         if (e.shiftKey) {
11410                                 offset = L.point(offset).multiplyBy(3);
11411                         }
11412
11413                         map.panBy(offset);
11414
11415                         if (map.options.maxBounds) {
11416                                 map.panInsideBounds(map.options.maxBounds);
11417                         }
11418
11419                 } else if (key in this._zoomKeys) {
11420                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
11421
11422                 } else if (key === 27) {
11423                         map.closePopup();
11424
11425                 } else {
11426                         return;
11427                 }
11428
11429                 L.DomEvent.stop(e);
11430         }
11431 });
11432
11433 // @section Handlers
11434 // @section Handlers
11435 // @property keyboard: Handler
11436 // Keyboard navigation handler.
11437 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
11438
11439
11440
11441 /*
11442  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
11443  */
11444
11445
11446 /* @namespace Marker
11447  * @section Interaction handlers
11448  *
11449  * 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:
11450  *
11451  * ```js
11452  * marker.dragging.disable();
11453  * ```
11454  *
11455  * @property dragging: Handler
11456  * Marker dragging handler (by both mouse and touch).
11457  */
11458
11459 L.Handler.MarkerDrag = L.Handler.extend({
11460         initialize: function (marker) {
11461                 this._marker = marker;
11462         },
11463
11464         addHooks: function () {
11465                 var icon = this._marker._icon;
11466
11467                 if (!this._draggable) {
11468                         this._draggable = new L.Draggable(icon, icon, true);
11469                 }
11470
11471                 this._draggable.on({
11472                         dragstart: this._onDragStart,
11473                         drag: this._onDrag,
11474                         dragend: this._onDragEnd
11475                 }, this).enable();
11476
11477                 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
11478         },
11479
11480         removeHooks: function () {
11481                 this._draggable.off({
11482                         dragstart: this._onDragStart,
11483                         drag: this._onDrag,
11484                         dragend: this._onDragEnd
11485                 }, this).disable();
11486
11487                 if (this._marker._icon) {
11488                         L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
11489                 }
11490         },
11491
11492         moved: function () {
11493                 return this._draggable && this._draggable._moved;
11494         },
11495
11496         _onDragStart: function () {
11497                 // @section Dragging events
11498                 // @event dragstart: Event
11499                 // Fired when the user starts dragging the marker.
11500
11501                 // @event movestart: Event
11502                 // Fired when the marker starts moving (because of dragging).
11503
11504                 this._oldLatLng = this._marker.getLatLng();
11505                 this._marker
11506                     .closePopup()
11507                     .fire('movestart')
11508                     .fire('dragstart');
11509         },
11510
11511         _onDrag: function (e) {
11512                 var marker = this._marker,
11513                     shadow = marker._shadow,
11514                     iconPos = L.DomUtil.getPosition(marker._icon),
11515                     latlng = marker._map.layerPointToLatLng(iconPos);
11516
11517                 // update shadow position
11518                 if (shadow) {
11519                         L.DomUtil.setPosition(shadow, iconPos);
11520                 }
11521
11522                 marker._latlng = latlng;
11523                 e.latlng = latlng;
11524                 e.oldLatLng = this._oldLatLng;
11525
11526                 // @event drag: Event
11527                 // Fired repeatedly while the user drags the marker.
11528                 marker
11529                     .fire('move', e)
11530                     .fire('drag', e);
11531         },
11532
11533         _onDragEnd: function (e) {
11534                 // @event dragend: DragEndEvent
11535                 // Fired when the user stops dragging the marker.
11536
11537                 // @event moveend: Event
11538                 // Fired when the marker stops moving (because of dragging).
11539                 delete this._oldLatLng;
11540                 this._marker
11541                     .fire('moveend')
11542                     .fire('dragend', e);
11543         }
11544 });
11545
11546
11547
11548 /*
11549  * @class Control
11550  * @aka L.Control
11551  *
11552  * L.Control is a base class for implementing map controls. Handles positioning.
11553  * All other controls extend from this class.
11554  */
11555
11556 L.Control = L.Class.extend({
11557         // @section
11558         // @aka Control options
11559         options: {
11560                 // @option position: String = 'topright'
11561                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
11562                 // `'topright'`, `'bottomleft'` or `'bottomright'`
11563                 position: 'topright'
11564         },
11565
11566         initialize: function (options) {
11567                 L.setOptions(this, options);
11568         },
11569
11570         /* @section
11571          * Classes extending L.Control will inherit the following methods:
11572          *
11573          * @method getPosition: string
11574          * Returns the position of the control.
11575          */
11576         getPosition: function () {
11577                 return this.options.position;
11578         },
11579
11580         // @method setPosition(position: string): this
11581         // Sets the position of the control.
11582         setPosition: function (position) {
11583                 var map = this._map;
11584
11585                 if (map) {
11586                         map.removeControl(this);
11587                 }
11588
11589                 this.options.position = position;
11590
11591                 if (map) {
11592                         map.addControl(this);
11593                 }
11594
11595                 return this;
11596         },
11597
11598         // @method getContainer: HTMLElement
11599         // Returns the HTMLElement that contains the control.
11600         getContainer: function () {
11601                 return this._container;
11602         },
11603
11604         // @method addTo(map: Map): this
11605         // Adds the control to the given map.
11606         addTo: function (map) {
11607                 this.remove();
11608                 this._map = map;
11609
11610                 var container = this._container = this.onAdd(map),
11611                     pos = this.getPosition(),
11612                     corner = map._controlCorners[pos];
11613
11614                 L.DomUtil.addClass(container, 'leaflet-control');
11615
11616                 if (pos.indexOf('bottom') !== -1) {
11617                         corner.insertBefore(container, corner.firstChild);
11618                 } else {
11619                         corner.appendChild(container);
11620                 }
11621
11622                 return this;
11623         },
11624
11625         // @method remove: this
11626         // Removes the control from the map it is currently active on.
11627         remove: function () {
11628                 if (!this._map) {
11629                         return this;
11630                 }
11631
11632                 L.DomUtil.remove(this._container);
11633
11634                 if (this.onRemove) {
11635                         this.onRemove(this._map);
11636                 }
11637
11638                 this._map = null;
11639
11640                 return this;
11641         },
11642
11643         _refocusOnMap: function (e) {
11644                 // if map exists and event is not a keyboard event
11645                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
11646                         this._map.getContainer().focus();
11647                 }
11648         }
11649 });
11650
11651 L.control = function (options) {
11652         return new L.Control(options);
11653 };
11654
11655 /* @section Extension methods
11656  * @uninheritable
11657  *
11658  * Every control should extend from `L.Control` and (re-)implement the following methods.
11659  *
11660  * @method onAdd(map: Map): HTMLElement
11661  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
11662  *
11663  * @method onRemove(map: Map)
11664  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
11665  */
11666
11667 /* @namespace Map
11668  * @section Methods for Layers and Controls
11669  */
11670 L.Map.include({
11671         // @method addControl(control: Control): this
11672         // Adds the given control to the map
11673         addControl: function (control) {
11674                 control.addTo(this);
11675                 return this;
11676         },
11677
11678         // @method removeControl(control: Control): this
11679         // Removes the given control from the map
11680         removeControl: function (control) {
11681                 control.remove();
11682                 return this;
11683         },
11684
11685         _initControlPos: function () {
11686                 var corners = this._controlCorners = {},
11687                     l = 'leaflet-',
11688                     container = this._controlContainer =
11689                             L.DomUtil.create('div', l + 'control-container', this._container);
11690
11691                 function createCorner(vSide, hSide) {
11692                         var className = l + vSide + ' ' + l + hSide;
11693
11694                         corners[vSide + hSide] = L.DomUtil.create('div', className, container);
11695                 }
11696
11697                 createCorner('top', 'left');
11698                 createCorner('top', 'right');
11699                 createCorner('bottom', 'left');
11700                 createCorner('bottom', 'right');
11701         },
11702
11703         _clearControlPos: function () {
11704                 L.DomUtil.remove(this._controlContainer);
11705         }
11706 });
11707
11708
11709
11710 /*
11711  * @class Control.Zoom
11712  * @aka L.Control.Zoom
11713  * @inherits Control
11714  *
11715  * 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`.
11716  */
11717
11718 L.Control.Zoom = L.Control.extend({
11719         // @section
11720         // @aka Control.Zoom options
11721         options: {
11722                 position: 'topleft',
11723
11724                 // @option zoomInText: String = '+'
11725                 // The text set on the 'zoom in' button.
11726                 zoomInText: '+',
11727
11728                 // @option zoomInTitle: String = 'Zoom in'
11729                 // The title set on the 'zoom in' button.
11730                 zoomInTitle: 'Zoom in',
11731
11732                 // @option zoomOutText: String = '-'
11733                 // The text set on the 'zoom out' button.
11734                 zoomOutText: '-',
11735
11736                 // @option zoomOutTitle: String = 'Zoom out'
11737                 // The title set on the 'zoom out' button.
11738                 zoomOutTitle: 'Zoom out'
11739         },
11740
11741         onAdd: function (map) {
11742                 var zoomName = 'leaflet-control-zoom',
11743                     container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
11744                     options = this.options;
11745
11746                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
11747                         zoomName + '-in',  container, this._zoomIn);
11748                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
11749                         zoomName + '-out', container, this._zoomOut);
11750
11751                 this._updateDisabled();
11752                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
11753
11754                 return container;
11755         },
11756
11757         onRemove: function (map) {
11758                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
11759         },
11760
11761         disable: function () {
11762                 this._disabled = true;
11763                 this._updateDisabled();
11764                 return this;
11765         },
11766
11767         enable: function () {
11768                 this._disabled = false;
11769                 this._updateDisabled();
11770                 return this;
11771         },
11772
11773         _zoomIn: function (e) {
11774                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
11775                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11776                 }
11777         },
11778
11779         _zoomOut: function (e) {
11780                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
11781                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
11782                 }
11783         },
11784
11785         _createButton: function (html, title, className, container, fn) {
11786                 var link = L.DomUtil.create('a', className, container);
11787                 link.innerHTML = html;
11788                 link.href = '#';
11789                 link.title = title;
11790
11791                 L.DomEvent
11792                     .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
11793                     .on(link, 'click', L.DomEvent.stop)
11794                     .on(link, 'click', fn, this)
11795                     .on(link, 'click', this._refocusOnMap, this);
11796
11797                 return link;
11798         },
11799
11800         _updateDisabled: function () {
11801                 var map = this._map,
11802                     className = 'leaflet-disabled';
11803
11804                 L.DomUtil.removeClass(this._zoomInButton, className);
11805                 L.DomUtil.removeClass(this._zoomOutButton, className);
11806
11807                 if (this._disabled || map._zoom === map.getMinZoom()) {
11808                         L.DomUtil.addClass(this._zoomOutButton, className);
11809                 }
11810                 if (this._disabled || map._zoom === map.getMaxZoom()) {
11811                         L.DomUtil.addClass(this._zoomInButton, className);
11812                 }
11813         }
11814 });
11815
11816 // @namespace Map
11817 // @section Control options
11818 // @option zoomControl: Boolean = true
11819 // Whether a [zoom control](#control-zoom) is added to the map by default.
11820 L.Map.mergeOptions({
11821         zoomControl: true
11822 });
11823
11824 L.Map.addInitHook(function () {
11825         if (this.options.zoomControl) {
11826                 this.zoomControl = new L.Control.Zoom();
11827                 this.addControl(this.zoomControl);
11828         }
11829 });
11830
11831 // @namespace Control.Zoom
11832 // @factory L.control.zoom(options: Control.Zoom options)
11833 // Creates a zoom control
11834 L.control.zoom = function (options) {
11835         return new L.Control.Zoom(options);
11836 };
11837
11838
11839
11840 /*
11841  * @class Control.Attribution
11842  * @aka L.Control.Attribution
11843  * @inherits Control
11844  *
11845  * 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.
11846  */
11847
11848 L.Control.Attribution = L.Control.extend({
11849         // @section
11850         // @aka Control.Attribution options
11851         options: {
11852                 position: 'bottomright',
11853
11854                 // @option prefix: String = 'Leaflet'
11855                 // The HTML text shown before the attributions. Pass `false` to disable.
11856                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
11857         },
11858
11859         initialize: function (options) {
11860                 L.setOptions(this, options);
11861
11862                 this._attributions = {};
11863         },
11864
11865         onAdd: function (map) {
11866                 map.attributionControl = this;
11867                 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
11868                 if (L.DomEvent) {
11869                         L.DomEvent.disableClickPropagation(this._container);
11870                 }
11871
11872                 // TODO ugly, refactor
11873                 for (var i in map._layers) {
11874                         if (map._layers[i].getAttribution) {
11875                                 this.addAttribution(map._layers[i].getAttribution());
11876                         }
11877                 }
11878
11879                 this._update();
11880
11881                 return this._container;
11882         },
11883
11884         // @method setPrefix(prefix: String): this
11885         // Sets the text before the attributions.
11886         setPrefix: function (prefix) {
11887                 this.options.prefix = prefix;
11888                 this._update();
11889                 return this;
11890         },
11891
11892         // @method addAttribution(text: String): this
11893         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
11894         addAttribution: function (text) {
11895                 if (!text) { return this; }
11896
11897                 if (!this._attributions[text]) {
11898                         this._attributions[text] = 0;
11899                 }
11900                 this._attributions[text]++;
11901
11902                 this._update();
11903
11904                 return this;
11905         },
11906
11907         // @method removeAttribution(text: String): this
11908         // Removes an attribution text.
11909         removeAttribution: function (text) {
11910                 if (!text) { return this; }
11911
11912                 if (this._attributions[text]) {
11913                         this._attributions[text]--;
11914                         this._update();
11915                 }
11916
11917                 return this;
11918         },
11919
11920         _update: function () {
11921                 if (!this._map) { return; }
11922
11923                 var attribs = [];
11924
11925                 for (var i in this._attributions) {
11926                         if (this._attributions[i]) {
11927                                 attribs.push(i);
11928                         }
11929                 }
11930
11931                 var prefixAndAttribs = [];
11932
11933                 if (this.options.prefix) {
11934                         prefixAndAttribs.push(this.options.prefix);
11935                 }
11936                 if (attribs.length) {
11937                         prefixAndAttribs.push(attribs.join(', '));
11938                 }
11939
11940                 this._container.innerHTML = prefixAndAttribs.join(' | ');
11941         }
11942 });
11943
11944 // @namespace Map
11945 // @section Control options
11946 // @option attributionControl: Boolean = true
11947 // Whether a [attribution control](#control-attribution) is added to the map by default.
11948 L.Map.mergeOptions({
11949         attributionControl: true
11950 });
11951
11952 L.Map.addInitHook(function () {
11953         if (this.options.attributionControl) {
11954                 new L.Control.Attribution().addTo(this);
11955         }
11956 });
11957
11958 // @namespace Control.Attribution
11959 // @factory L.control.attribution(options: Control.Attribution options)
11960 // Creates an attribution control.
11961 L.control.attribution = function (options) {
11962         return new L.Control.Attribution(options);
11963 };
11964
11965
11966
11967 /*
11968  * @class Control.Scale
11969  * @aka L.Control.Scale
11970  * @inherits Control
11971  *
11972  * 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`.
11973  *
11974  * @example
11975  *
11976  * ```js
11977  * L.control.scale().addTo(map);
11978  * ```
11979  */
11980
11981 L.Control.Scale = L.Control.extend({
11982         // @section
11983         // @aka Control.Scale options
11984         options: {
11985                 position: 'bottomleft',
11986
11987                 // @option maxWidth: Number = 100
11988                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
11989                 maxWidth: 100,
11990
11991                 // @option metric: Boolean = True
11992                 // Whether to show the metric scale line (m/km).
11993                 metric: true,
11994
11995                 // @option imperial: Boolean = True
11996                 // Whether to show the imperial scale line (mi/ft).
11997                 imperial: true
11998
11999                 // @option updateWhenIdle: Boolean = false
12000                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12001         },
12002
12003         onAdd: function (map) {
12004                 var className = 'leaflet-control-scale',
12005                     container = L.DomUtil.create('div', className),
12006                     options = this.options;
12007
12008                 this._addScales(options, className + '-line', container);
12009
12010                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12011                 map.whenReady(this._update, this);
12012
12013                 return container;
12014         },
12015
12016         onRemove: function (map) {
12017                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12018         },
12019
12020         _addScales: function (options, className, container) {
12021                 if (options.metric) {
12022                         this._mScale = L.DomUtil.create('div', className, container);
12023                 }
12024                 if (options.imperial) {
12025                         this._iScale = L.DomUtil.create('div', className, container);
12026                 }
12027         },
12028
12029         _update: function () {
12030                 var map = this._map,
12031                     y = map.getSize().y / 2;
12032
12033                 var maxMeters = map.distance(
12034                                 map.containerPointToLatLng([0, y]),
12035                                 map.containerPointToLatLng([this.options.maxWidth, y]));
12036
12037                 this._updateScales(maxMeters);
12038         },
12039
12040         _updateScales: function (maxMeters) {
12041                 if (this.options.metric && maxMeters) {
12042                         this._updateMetric(maxMeters);
12043                 }
12044                 if (this.options.imperial && maxMeters) {
12045                         this._updateImperial(maxMeters);
12046                 }
12047         },
12048
12049         _updateMetric: function (maxMeters) {
12050                 var meters = this._getRoundNum(maxMeters),
12051                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12052
12053                 this._updateScale(this._mScale, label, meters / maxMeters);
12054         },
12055
12056         _updateImperial: function (maxMeters) {
12057                 var maxFeet = maxMeters * 3.2808399,
12058                     maxMiles, miles, feet;
12059
12060                 if (maxFeet > 5280) {
12061                         maxMiles = maxFeet / 5280;
12062                         miles = this._getRoundNum(maxMiles);
12063                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12064
12065                 } else {
12066                         feet = this._getRoundNum(maxFeet);
12067                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12068                 }
12069         },
12070
12071         _updateScale: function (scale, text, ratio) {
12072                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12073                 scale.innerHTML = text;
12074         },
12075
12076         _getRoundNum: function (num) {
12077                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12078                     d = num / pow10;
12079
12080                 d = d >= 10 ? 10 :
12081                     d >= 5 ? 5 :
12082                     d >= 3 ? 3 :
12083                     d >= 2 ? 2 : 1;
12084
12085                 return pow10 * d;
12086         }
12087 });
12088
12089
12090 // @factory L.control.scale(options?: Control.Scale options)
12091 // Creates an scale control with the given options.
12092 L.control.scale = function (options) {
12093         return new L.Control.Scale(options);
12094 };
12095
12096
12097
12098 /*
12099  * @class Control.Layers
12100  * @aka L.Control.Layers
12101  * @inherits Control
12102  *
12103  * 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`.
12104  *
12105  * @example
12106  *
12107  * ```js
12108  * var baseLayers = {
12109  *      "Mapbox": mapbox,
12110  *      "OpenStreetMap": osm
12111  * };
12112  *
12113  * var overlays = {
12114  *      "Marker": marker,
12115  *      "Roads": roadsLayer
12116  * };
12117  *
12118  * L.control.layers(baseLayers, overlays).addTo(map);
12119  * ```
12120  *
12121  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12122  *
12123  * ```js
12124  * {
12125  *     "<someName1>": layer1,
12126  *     "<someName2>": layer2
12127  * }
12128  * ```
12129  *
12130  * The layer names can contain HTML, which allows you to add additional styling to the items:
12131  *
12132  * ```js
12133  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12134  * ```
12135  */
12136
12137
12138 L.Control.Layers = L.Control.extend({
12139         // @section
12140         // @aka Control.Layers options
12141         options: {
12142                 // @option collapsed: Boolean = true
12143                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12144                 collapsed: true,
12145                 position: 'topright',
12146
12147                 // @option autoZIndex: Boolean = true
12148                 // 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.
12149                 autoZIndex: true,
12150
12151                 // @option hideSingleBase: Boolean = false
12152                 // If `true`, the base layers in the control will be hidden when there is only one.
12153                 hideSingleBase: false
12154         },
12155
12156         initialize: function (baseLayers, overlays, options) {
12157                 L.setOptions(this, options);
12158
12159                 this._layers = [];
12160                 this._lastZIndex = 0;
12161                 this._handlingClick = false;
12162
12163                 for (var i in baseLayers) {
12164                         this._addLayer(baseLayers[i], i);
12165                 }
12166
12167                 for (i in overlays) {
12168                         this._addLayer(overlays[i], i, true);
12169                 }
12170         },
12171
12172         onAdd: function (map) {
12173                 this._initLayout();
12174                 this._update();
12175
12176                 this._map = map;
12177                 map.on('zoomend', this._checkDisabledLayers, this);
12178
12179                 return this._container;
12180         },
12181
12182         onRemove: function () {
12183                 this._map.off('zoomend', this._checkDisabledLayers, this);
12184
12185                 for (var i = 0; i < this._layers.length; i++) {
12186                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
12187                 }
12188         },
12189
12190         // @method addBaseLayer(layer: Layer, name: String): this
12191         // Adds a base layer (radio button entry) with the given name to the control.
12192         addBaseLayer: function (layer, name) {
12193                 this._addLayer(layer, name);
12194                 return (this._map) ? this._update() : this;
12195         },
12196
12197         // @method addOverlay(layer: Layer, name: String): this
12198         // Adds an overlay (checkbox entry) with the given name to the control.
12199         addOverlay: function (layer, name) {
12200                 this._addLayer(layer, name, true);
12201                 return (this._map) ? this._update() : this;
12202         },
12203
12204         // @method removeLayer(layer: Layer): this
12205         // Remove the given layer from the control.
12206         removeLayer: function (layer) {
12207                 layer.off('add remove', this._onLayerChange, this);
12208
12209                 var obj = this._getLayer(L.stamp(layer));
12210                 if (obj) {
12211                         this._layers.splice(this._layers.indexOf(obj), 1);
12212                 }
12213                 return (this._map) ? this._update() : this;
12214         },
12215
12216         // @method expand(): this
12217         // Expand the control container if collapsed.
12218         expand: function () {
12219                 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12220                 this._form.style.height = null;
12221                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12222                 if (acceptableHeight < this._form.clientHeight) {
12223                         L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12224                         this._form.style.height = acceptableHeight + 'px';
12225                 } else {
12226                         L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12227                 }
12228                 this._checkDisabledLayers();
12229                 return this;
12230         },
12231
12232         // @method collapse(): this
12233         // Collapse the control container if expanded.
12234         collapse: function () {
12235                 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12236                 return this;
12237         },
12238
12239         _initLayout: function () {
12240                 var className = 'leaflet-control-layers',
12241                     container = this._container = L.DomUtil.create('div', className);
12242
12243                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
12244                 container.setAttribute('aria-haspopup', true);
12245
12246                 L.DomEvent.disableClickPropagation(container);
12247                 if (!L.Browser.touch) {
12248                         L.DomEvent.disableScrollPropagation(container);
12249                 }
12250
12251                 var form = this._form = L.DomUtil.create('form', className + '-list');
12252
12253                 if (this.options.collapsed) {
12254                         if (!L.Browser.android) {
12255                                 L.DomEvent.on(container, {
12256                                         mouseenter: this.expand,
12257                                         mouseleave: this.collapse
12258                                 }, this);
12259                         }
12260
12261                         var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
12262                         link.href = '#';
12263                         link.title = 'Layers';
12264
12265                         if (L.Browser.touch) {
12266                                 L.DomEvent
12267                                     .on(link, 'click', L.DomEvent.stop)
12268                                     .on(link, 'click', this.expand, this);
12269                         } else {
12270                                 L.DomEvent.on(link, 'focus', this.expand, this);
12271                         }
12272
12273                         // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
12274                         L.DomEvent.on(form, 'click', function () {
12275                                 setTimeout(L.bind(this._onInputClick, this), 0);
12276                         }, this);
12277
12278                         this._map.on('click', this.collapse, this);
12279                         // TODO keyboard accessibility
12280                 } else {
12281                         this.expand();
12282                 }
12283
12284                 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
12285                 this._separator = L.DomUtil.create('div', className + '-separator', form);
12286                 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
12287
12288                 container.appendChild(form);
12289         },
12290
12291         _getLayer: function (id) {
12292                 for (var i = 0; i < this._layers.length; i++) {
12293
12294                         if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
12295                                 return this._layers[i];
12296                         }
12297                 }
12298         },
12299
12300         _addLayer: function (layer, name, overlay) {
12301                 layer.on('add remove', this._onLayerChange, this);
12302
12303                 this._layers.push({
12304                         layer: layer,
12305                         name: name,
12306                         overlay: overlay
12307                 });
12308
12309                 if (this.options.autoZIndex && layer.setZIndex) {
12310                         this._lastZIndex++;
12311                         layer.setZIndex(this._lastZIndex);
12312                 }
12313         },
12314
12315         _update: function () {
12316                 if (!this._container) { return this; }
12317
12318                 L.DomUtil.empty(this._baseLayersList);
12319                 L.DomUtil.empty(this._overlaysList);
12320
12321                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
12322
12323                 for (i = 0; i < this._layers.length; i++) {
12324                         obj = this._layers[i];
12325                         this._addItem(obj);
12326                         overlaysPresent = overlaysPresent || obj.overlay;
12327                         baseLayersPresent = baseLayersPresent || !obj.overlay;
12328                         baseLayersCount += !obj.overlay ? 1 : 0;
12329                 }
12330
12331                 // Hide base layers section if there's only one layer.
12332                 if (this.options.hideSingleBase) {
12333                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
12334                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
12335                 }
12336
12337                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
12338
12339                 return this;
12340         },
12341
12342         _onLayerChange: function (e) {
12343                 if (!this._handlingClick) {
12344                         this._update();
12345                 }
12346
12347                 var obj = this._getLayer(L.stamp(e.target));
12348
12349                 // @namespace Map
12350                 // @section Layer events
12351                 // @event baselayerchange: LayersControlEvent
12352                 // Fired when the base layer is changed through the [layer control](#control-layers).
12353                 // @event overlayadd: LayersControlEvent
12354                 // Fired when an overlay is selected through the [layer control](#control-layers).
12355                 // @event overlayremove: LayersControlEvent
12356                 // Fired when an overlay is deselected through the [layer control](#control-layers).
12357                 // @namespace Control.Layers
12358                 var type = obj.overlay ?
12359                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
12360                         (e.type === 'add' ? 'baselayerchange' : null);
12361
12362                 if (type) {
12363                         this._map.fire(type, obj);
12364                 }
12365         },
12366
12367         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
12368         _createRadioElement: function (name, checked) {
12369
12370                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
12371                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
12372
12373                 var radioFragment = document.createElement('div');
12374                 radioFragment.innerHTML = radioHtml;
12375
12376                 return radioFragment.firstChild;
12377         },
12378
12379         _addItem: function (obj) {
12380                 var label = document.createElement('label'),
12381                     checked = this._map.hasLayer(obj.layer),
12382                     input;
12383
12384                 if (obj.overlay) {
12385                         input = document.createElement('input');
12386                         input.type = 'checkbox';
12387                         input.className = 'leaflet-control-layers-selector';
12388                         input.defaultChecked = checked;
12389                 } else {
12390                         input = this._createRadioElement('leaflet-base-layers', checked);
12391                 }
12392
12393                 input.layerId = L.stamp(obj.layer);
12394
12395                 L.DomEvent.on(input, 'click', this._onInputClick, this);
12396
12397                 var name = document.createElement('span');
12398                 name.innerHTML = ' ' + obj.name;
12399
12400                 // Helps from preventing layer control flicker when checkboxes are disabled
12401                 // https://github.com/Leaflet/Leaflet/issues/2771
12402                 var holder = document.createElement('div');
12403
12404                 label.appendChild(holder);
12405                 holder.appendChild(input);
12406                 holder.appendChild(name);
12407
12408                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
12409                 container.appendChild(label);
12410
12411                 this._checkDisabledLayers();
12412                 return label;
12413         },
12414
12415         _onInputClick: function () {
12416                 var inputs = this._form.getElementsByTagName('input'),
12417                     input, layer, hasLayer;
12418                 var addedLayers = [],
12419                     removedLayers = [];
12420
12421                 this._handlingClick = true;
12422
12423                 for (var i = inputs.length - 1; i >= 0; i--) {
12424                         input = inputs[i];
12425                         layer = this._getLayer(input.layerId).layer;
12426                         hasLayer = this._map.hasLayer(layer);
12427
12428                         if (input.checked && !hasLayer) {
12429                                 addedLayers.push(layer);
12430
12431                         } else if (!input.checked && hasLayer) {
12432                                 removedLayers.push(layer);
12433                         }
12434                 }
12435
12436                 // Bugfix issue 2318: Should remove all old layers before readding new ones
12437                 for (i = 0; i < removedLayers.length; i++) {
12438                         this._map.removeLayer(removedLayers[i]);
12439                 }
12440                 for (i = 0; i < addedLayers.length; i++) {
12441                         this._map.addLayer(addedLayers[i]);
12442                 }
12443
12444                 this._handlingClick = false;
12445
12446                 this._refocusOnMap();
12447         },
12448
12449         _checkDisabledLayers: function () {
12450                 var inputs = this._form.getElementsByTagName('input'),
12451                     input,
12452                     layer,
12453                     zoom = this._map.getZoom();
12454
12455                 for (var i = inputs.length - 1; i >= 0; i--) {
12456                         input = inputs[i];
12457                         layer = this._getLayer(input.layerId).layer;
12458                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
12459                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
12460
12461                 }
12462         },
12463
12464         _expand: function () {
12465                 // Backward compatibility, remove me in 1.1.
12466                 return this.expand();
12467         },
12468
12469         _collapse: function () {
12470                 // Backward compatibility, remove me in 1.1.
12471                 return this.collapse();
12472         }
12473
12474 });
12475
12476
12477 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
12478 // 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.
12479 L.control.layers = function (baseLayers, overlays, options) {
12480         return new L.Control.Layers(baseLayers, overlays, options);
12481 };
12482
12483
12484
12485 /*
12486  * @class PosAnimation
12487  * @aka L.PosAnimation
12488  * @inherits Evented
12489  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
12490  *
12491  * @example
12492  * ```js
12493  * var fx = new L.PosAnimation();
12494  * fx.run(el, [300, 500], 0.5);
12495  * ```
12496  *
12497  * @constructor L.PosAnimation()
12498  * Creates a `PosAnimation` object.
12499  *
12500  */
12501
12502 L.PosAnimation = L.Evented.extend({
12503
12504         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
12505         // Run an animation of a given element to a new position, optionally setting
12506         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
12507         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
12508         // `0.5` by default).
12509         run: function (el, newPos, duration, easeLinearity) {
12510                 this.stop();
12511
12512                 this._el = el;
12513                 this._inProgress = true;
12514                 this._duration = duration || 0.25;
12515                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
12516
12517                 this._startPos = L.DomUtil.getPosition(el);
12518                 this._offset = newPos.subtract(this._startPos);
12519                 this._startTime = +new Date();
12520
12521                 // @event start: Event
12522                 // Fired when the animation starts
12523                 this.fire('start');
12524
12525                 this._animate();
12526         },
12527
12528         // @method stop()
12529         // Stops the animation (if currently running).
12530         stop: function () {
12531                 if (!this._inProgress) { return; }
12532
12533                 this._step(true);
12534                 this._complete();
12535         },
12536
12537         _animate: function () {
12538                 // animation loop
12539                 this._animId = L.Util.requestAnimFrame(this._animate, this);
12540                 this._step();
12541         },
12542
12543         _step: function (round) {
12544                 var elapsed = (+new Date()) - this._startTime,
12545                     duration = this._duration * 1000;
12546
12547                 if (elapsed < duration) {
12548                         this._runFrame(this._easeOut(elapsed / duration), round);
12549                 } else {
12550                         this._runFrame(1);
12551                         this._complete();
12552                 }
12553         },
12554
12555         _runFrame: function (progress, round) {
12556                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
12557                 if (round) {
12558                         pos._round();
12559                 }
12560                 L.DomUtil.setPosition(this._el, pos);
12561
12562                 // @event step: Event
12563                 // Fired continuously during the animation.
12564                 this.fire('step');
12565         },
12566
12567         _complete: function () {
12568                 L.Util.cancelAnimFrame(this._animId);
12569
12570                 this._inProgress = false;
12571                 // @event end: Event
12572                 // Fired when the animation ends.
12573                 this.fire('end');
12574         },
12575
12576         _easeOut: function (t) {
12577                 return 1 - Math.pow(1 - t, this._easeOutPower);
12578         }
12579 });
12580
12581
12582
12583 /*
12584  * Extends L.Map to handle panning animations.
12585  */
12586
12587 L.Map.include({
12588
12589         setView: function (center, zoom, options) {
12590
12591                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
12592                 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
12593                 options = options || {};
12594
12595                 this._stop();
12596
12597                 if (this._loaded && !options.reset && options !== true) {
12598
12599                         if (options.animate !== undefined) {
12600                                 options.zoom = L.extend({animate: options.animate}, options.zoom);
12601                                 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
12602                         }
12603
12604                         // try animating pan or zoom
12605                         var moved = (this._zoom !== zoom) ?
12606                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
12607                                 this._tryAnimatedPan(center, options.pan);
12608
12609                         if (moved) {
12610                                 // prevent resize handler call, the view will refresh after animation anyway
12611                                 clearTimeout(this._sizeTimer);
12612                                 return this;
12613                         }
12614                 }
12615
12616                 // animation didn't start, just reset the map view
12617                 this._resetView(center, zoom);
12618
12619                 return this;
12620         },
12621
12622         panBy: function (offset, options) {
12623                 offset = L.point(offset).round();
12624                 options = options || {};
12625
12626                 if (!offset.x && !offset.y) {
12627                         return this.fire('moveend');
12628                 }
12629                 // If we pan too far, Chrome gets issues with tiles
12630                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
12631                 if (options.animate !== true && !this.getSize().contains(offset)) {
12632                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
12633                         return this;
12634                 }
12635
12636                 if (!this._panAnim) {
12637                         this._panAnim = new L.PosAnimation();
12638
12639                         this._panAnim.on({
12640                                 'step': this._onPanTransitionStep,
12641                                 'end': this._onPanTransitionEnd
12642                         }, this);
12643                 }
12644
12645                 // don't fire movestart if animating inertia
12646                 if (!options.noMoveStart) {
12647                         this.fire('movestart');
12648                 }
12649
12650                 // animate pan unless animate: false specified
12651                 if (options.animate !== false) {
12652                         L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
12653
12654                         var newPos = this._getMapPanePos().subtract(offset).round();
12655                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
12656                 } else {
12657                         this._rawPanBy(offset);
12658                         this.fire('move').fire('moveend');
12659                 }
12660
12661                 return this;
12662         },
12663
12664         _onPanTransitionStep: function () {
12665                 this.fire('move');
12666         },
12667
12668         _onPanTransitionEnd: function () {
12669                 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
12670                 this.fire('moveend');
12671         },
12672
12673         _tryAnimatedPan: function (center, options) {
12674                 // difference between the new and current centers in pixels
12675                 var offset = this._getCenterOffset(center)._floor();
12676
12677                 // don't animate too far unless animate: true specified in options
12678                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
12679
12680                 this.panBy(offset, options);
12681
12682                 return true;
12683         }
12684 });
12685
12686
12687
12688 /*
12689  * Extends L.Map to handle zoom animations.
12690  */
12691
12692 // @namespace Map
12693 // @section Animation Options
12694 L.Map.mergeOptions({
12695         // @option zoomAnimation: Boolean = true
12696         // Whether the map zoom animation is enabled. By default it's enabled
12697         // in all browsers that support CSS3 Transitions except Android.
12698         zoomAnimation: true,
12699
12700         // @option zoomAnimationThreshold: Number = 4
12701         // Won't animate zoom if the zoom difference exceeds this value.
12702         zoomAnimationThreshold: 4
12703 });
12704
12705 var zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera;
12706
12707 if (zoomAnimated) {
12708
12709         L.Map.addInitHook(function () {
12710                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
12711                 this._zoomAnimated = this.options.zoomAnimation;
12712
12713                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
12714                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
12715                 if (this._zoomAnimated) {
12716
12717                         this._createAnimProxy();
12718
12719                         L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
12720                 }
12721         });
12722 }
12723
12724 L.Map.include(!zoomAnimated ? {} : {
12725
12726         _createAnimProxy: function () {
12727
12728                 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
12729                 this._panes.mapPane.appendChild(proxy);
12730
12731                 this.on('zoomanim', function (e) {
12732                         var prop = L.DomUtil.TRANSFORM,
12733                             transform = proxy.style[prop];
12734
12735                         L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
12736
12737                         // workaround for case when transform is the same and so transitionend event is not fired
12738                         if (transform === proxy.style[prop] && this._animatingZoom) {
12739                                 this._onZoomTransitionEnd();
12740                         }
12741                 }, this);
12742
12743                 this.on('load moveend', function () {
12744                         var c = this.getCenter(),
12745                             z = this.getZoom();
12746                         L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
12747                 }, this);
12748         },
12749
12750         _catchTransitionEnd: function (e) {
12751                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
12752                         this._onZoomTransitionEnd();
12753                 }
12754         },
12755
12756         _nothingToAnimate: function () {
12757                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
12758         },
12759
12760         _tryAnimatedZoom: function (center, zoom, options) {
12761
12762                 if (this._animatingZoom) { return true; }
12763
12764                 options = options || {};
12765
12766                 // don't animate if disabled, not supported or zoom difference is too large
12767                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
12768                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
12769
12770                 // offset is the pixel coords of the zoom origin relative to the current center
12771                 var scale = this.getZoomScale(zoom),
12772                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
12773
12774                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
12775                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
12776
12777                 L.Util.requestAnimFrame(function () {
12778                         this
12779                             ._moveStart(true)
12780                             ._animateZoom(center, zoom, true);
12781                 }, this);
12782
12783                 return true;
12784         },
12785
12786         _animateZoom: function (center, zoom, startAnim, noUpdate) {
12787                 if (startAnim) {
12788                         this._animatingZoom = true;
12789
12790                         // remember what center/zoom to set after animation
12791                         this._animateToCenter = center;
12792                         this._animateToZoom = zoom;
12793
12794                         L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
12795                 }
12796
12797                 // @event zoomanim: ZoomAnimEvent
12798                 // Fired on every frame of a zoom animation
12799                 this.fire('zoomanim', {
12800                         center: center,
12801                         zoom: zoom,
12802                         noUpdate: noUpdate
12803                 });
12804
12805                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
12806                 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
12807         },
12808
12809         _onZoomTransitionEnd: function () {
12810                 if (!this._animatingZoom) { return; }
12811
12812                 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
12813
12814                 this._animatingZoom = false;
12815
12816                 this._move(this._animateToCenter, this._animateToZoom);
12817
12818                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
12819                 L.Util.requestAnimFrame(function () {
12820                         this._moveEnd(true);
12821                 }, this);
12822         }
12823 });
12824
12825
12826
12827 // @namespace Map
12828 // @section Methods for modifying map state
12829 L.Map.include({
12830
12831         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
12832         // Sets the view of the map (geographical center and zoom) performing a smooth
12833         // pan-zoom animation.
12834         flyTo: function (targetCenter, targetZoom, options) {
12835
12836                 options = options || {};
12837                 if (options.animate === false || !L.Browser.any3d) {
12838                         return this.setView(targetCenter, targetZoom, options);
12839                 }
12840
12841                 this._stop();
12842
12843                 var from = this.project(this.getCenter()),
12844                     to = this.project(targetCenter),
12845                     size = this.getSize(),
12846                     startZoom = this._zoom;
12847
12848                 targetCenter = L.latLng(targetCenter);
12849                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
12850
12851                 var w0 = Math.max(size.x, size.y),
12852                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
12853                     u1 = (to.distanceTo(from)) || 1,
12854                     rho = 1.42,
12855                     rho2 = rho * rho;
12856
12857                 function r(i) {
12858                         var s1 = i ? -1 : 1,
12859                             s2 = i ? w1 : w0,
12860                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
12861                             b1 = 2 * s2 * rho2 * u1,
12862                             b = t1 / b1,
12863                             sq = Math.sqrt(b * b + 1) - b;
12864
12865                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
12866                             // thus triggering an infinite loop in flyTo
12867                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
12868
12869                         return log;
12870                 }
12871
12872                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
12873                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
12874                 function tanh(n) { return sinh(n) / cosh(n); }
12875
12876                 var r0 = r(0);
12877
12878                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
12879                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
12880
12881                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
12882
12883                 var start = Date.now(),
12884                     S = (r(1) - r0) / rho,
12885                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
12886
12887                 function frame() {
12888                         var t = (Date.now() - start) / duration,
12889                             s = easeOut(t) * S;
12890
12891                         if (t <= 1) {
12892                                 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
12893
12894                                 this._move(
12895                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
12896                                         this.getScaleZoom(w0 / w(s), startZoom),
12897                                         {flyTo: true});
12898
12899                         } else {
12900                                 this
12901                                         ._move(targetCenter, targetZoom)
12902                                         ._moveEnd(true);
12903                         }
12904                 }
12905
12906                 this._moveStart(true);
12907
12908                 frame.call(this);
12909                 return this;
12910         },
12911
12912         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
12913         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
12914         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
12915         flyToBounds: function (bounds, options) {
12916                 var target = this._getBoundsCenterZoom(bounds, options);
12917                 return this.flyTo(target.center, target.zoom, options);
12918         }
12919 });
12920
12921
12922
12923 /*
12924  * Provides L.Map with convenient shortcuts for using browser geolocation features.
12925  */
12926
12927 // @namespace Map
12928
12929 L.Map.include({
12930         // @section Geolocation methods
12931         _defaultLocateOptions: {
12932                 timeout: 10000,
12933                 watch: false
12934                 // setView: false
12935                 // maxZoom: <Number>
12936                 // maximumAge: 0
12937                 // enableHighAccuracy: false
12938         },
12939
12940         // @method locate(options?: Locate options): this
12941         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
12942         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
12943         // and optionally sets the map view to the user's location with respect to
12944         // detection accuracy (or to the world view if geolocation failed).
12945         // Note that, if your page doesn't use HTTPS, this method will fail in
12946         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
12947         // See `Locate options` for more details.
12948         locate: function (options) {
12949
12950                 options = this._locateOptions = L.extend({}, this._defaultLocateOptions, options);
12951
12952                 if (!('geolocation' in navigator)) {
12953                         this._handleGeolocationError({
12954                                 code: 0,
12955                                 message: 'Geolocation not supported.'
12956                         });
12957                         return this;
12958                 }
12959
12960                 var onResponse = L.bind(this._handleGeolocationResponse, this),
12961                     onError = L.bind(this._handleGeolocationError, this);
12962
12963                 if (options.watch) {
12964                         this._locationWatchId =
12965                                 navigator.geolocation.watchPosition(onResponse, onError, options);
12966                 } else {
12967                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
12968                 }
12969                 return this;
12970         },
12971
12972         // @method stopLocate(): this
12973         // Stops watching location previously initiated by `map.locate({watch: true})`
12974         // and aborts resetting the map view if map.locate was called with
12975         // `{setView: true}`.
12976         stopLocate: function () {
12977                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
12978                         navigator.geolocation.clearWatch(this._locationWatchId);
12979                 }
12980                 if (this._locateOptions) {
12981                         this._locateOptions.setView = false;
12982                 }
12983                 return this;
12984         },
12985
12986         _handleGeolocationError: function (error) {
12987                 var c = error.code,
12988                     message = error.message ||
12989                             (c === 1 ? 'permission denied' :
12990                             (c === 2 ? 'position unavailable' : 'timeout'));
12991
12992                 if (this._locateOptions.setView && !this._loaded) {
12993                         this.fitWorld();
12994                 }
12995
12996                 // @section Location events
12997                 // @event locationerror: ErrorEvent
12998                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
12999                 this.fire('locationerror', {
13000                         code: c,
13001                         message: 'Geolocation error: ' + message + '.'
13002                 });
13003         },
13004
13005         _handleGeolocationResponse: function (pos) {
13006                 var lat = pos.coords.latitude,
13007                     lng = pos.coords.longitude,
13008                     latlng = new L.LatLng(lat, lng),
13009                     bounds = latlng.toBounds(pos.coords.accuracy),
13010                     options = this._locateOptions;
13011
13012                 if (options.setView) {
13013                         var zoom = this.getBoundsZoom(bounds);
13014                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
13015                 }
13016
13017                 var data = {
13018                         latlng: latlng,
13019                         bounds: bounds,
13020                         timestamp: pos.timestamp
13021                 };
13022
13023                 for (var i in pos.coords) {
13024                         if (typeof pos.coords[i] === 'number') {
13025                                 data[i] = pos.coords[i];
13026                         }
13027                 }
13028
13029                 // @event locationfound: LocationEvent
13030                 // Fired when geolocation (using the [`locate`](#map-locate) method)
13031                 // went successfully.
13032                 this.fire('locationfound', data);
13033         }
13034 });
13035
13036
13037
13038 }(window, document));
13039 //# sourceMappingURL=leaflet-src.map