]> git.openstreetmap.org Git - rails.git/blob - vendor/assets/leaflet/leaflet.js
Update to leaflet 1.0.3
[rails.git] / vendor / assets / leaflet / leaflet.js
1 /*
2  Leaflet 1.0.3, a JS library for interactive maps. http://leafletjs.com
3  (c) 2010-2016 Vladimir Agafonkin, (c) 2010-2011 CloudMade
4 */
5 (function (window, document, undefined) {
6 var L = {
7         version: "1.0.3"
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         },
520
521         _off: function (type, fn, context) {
522                 var listeners,
523                     i,
524                     len;
525
526                 if (!this._events) { return; }
527
528                 listeners = this._events[type];
529
530                 if (!listeners) {
531                         return;
532                 }
533
534                 if (!fn) {
535                         // Set all removed listeners to noop so they are not called if remove happens in fire
536                         for (i = 0, len = listeners.length; i < len; i++) {
537                                 listeners[i].fn = L.Util.falseFn;
538                         }
539                         // clear all listeners for a type if function isn't specified
540                         delete this._events[type];
541                         return;
542                 }
543
544                 if (context === this) {
545                         context = undefined;
546                 }
547
548                 if (listeners) {
549
550                         // find fn and remove it
551                         for (i = 0, len = listeners.length; i < len; i++) {
552                                 var l = listeners[i];
553                                 if (l.ctx !== context) { continue; }
554                                 if (l.fn === fn) {
555
556                                         // set the removed listener to noop so that's not called if remove happens in fire
557                                         l.fn = L.Util.falseFn;
558
559                                         if (this._firingCount) {
560                                                 /* copy array in case events are being fired */
561                                                 this._events[type] = listeners = listeners.slice();
562                                         }
563                                         listeners.splice(i, 1);
564
565                                         return;
566                                 }
567                         }
568                 }
569         },
570
571         // @method fire(type: String, data?: Object, propagate?: Boolean): this
572         // Fires an event of the specified type. You can optionally provide an data
573         // object — the first argument of the listener function will contain its
574         // properties. The event can optionally be propagated to event parents.
575         fire: function (type, data, propagate) {
576                 if (!this.listens(type, propagate)) { return this; }
577
578                 var event = L.Util.extend({}, data, {type: type, target: this});
579
580                 if (this._events) {
581                         var listeners = this._events[type];
582
583                         if (listeners) {
584                                 this._firingCount = (this._firingCount + 1) || 1;
585                                 for (var i = 0, len = listeners.length; i < len; i++) {
586                                         var l = listeners[i];
587                                         l.fn.call(l.ctx || this, event);
588                                 }
589
590                                 this._firingCount--;
591                         }
592                 }
593
594                 if (propagate) {
595                         // propagate the event to parents (set with addEventParent)
596                         this._propagateEvent(event);
597                 }
598
599                 return this;
600         },
601
602         // @method listens(type: String): Boolean
603         // Returns `true` if a particular event type has any listeners attached to it.
604         listens: function (type, propagate) {
605                 var listeners = this._events && this._events[type];
606                 if (listeners && listeners.length) { return true; }
607
608                 if (propagate) {
609                         // also check parents for listeners if event propagates
610                         for (var id in this._eventParents) {
611                                 if (this._eventParents[id].listens(type, propagate)) { return true; }
612                         }
613                 }
614                 return false;
615         },
616
617         // @method once(…): this
618         // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
619         once: function (types, fn, context) {
620
621                 if (typeof types === 'object') {
622                         for (var type in types) {
623                                 this.once(type, types[type], fn);
624                         }
625                         return this;
626                 }
627
628                 var handler = L.bind(function () {
629                         this
630                             .off(types, fn, context)
631                             .off(types, handler, context);
632                 }, this);
633
634                 // add a listener that's executed once and removed after that
635                 return this
636                     .on(types, fn, context)
637                     .on(types, handler, context);
638         },
639
640         // @method addEventParent(obj: Evented): this
641         // Adds an event parent - an `Evented` that will receive propagated events
642         addEventParent: function (obj) {
643                 this._eventParents = this._eventParents || {};
644                 this._eventParents[L.stamp(obj)] = obj;
645                 return this;
646         },
647
648         // @method removeEventParent(obj: Evented): this
649         // Removes an event parent, so it will stop receiving propagated events
650         removeEventParent: function (obj) {
651                 if (this._eventParents) {
652                         delete this._eventParents[L.stamp(obj)];
653                 }
654                 return this;
655         },
656
657         _propagateEvent: function (e) {
658                 for (var id in this._eventParents) {
659                         this._eventParents[id].fire(e.type, L.extend({layer: e.target}, e), true);
660                 }
661         }
662 });
663
664 var proto = L.Evented.prototype;
665
666 // aliases; we should ditch those eventually
667
668 // @method addEventListener(…): this
669 // Alias to [`on(…)`](#evented-on)
670 proto.addEventListener = proto.on;
671
672 // @method removeEventListener(…): this
673 // Alias to [`off(…)`](#evented-off)
674
675 // @method clearAllEventListeners(…): this
676 // Alias to [`off()`](#evented-off)
677 proto.removeEventListener = proto.clearAllEventListeners = proto.off;
678
679 // @method addOneTimeEventListener(…): this
680 // Alias to [`once(…)`](#evented-once)
681 proto.addOneTimeEventListener = proto.once;
682
683 // @method fireEvent(…): this
684 // Alias to [`fire(…)`](#evented-fire)
685 proto.fireEvent = proto.fire;
686
687 // @method hasEventListeners(…): Boolean
688 // Alias to [`listens(…)`](#evented-listens)
689 proto.hasEventListeners = proto.listens;
690
691 L.Mixin = {Events: proto};
692
693
694
695 /*
696  * @namespace Browser
697  * @aka L.Browser
698  *
699  * A namespace with static properties for browser/feature detection used by Leaflet internally.
700  *
701  * @example
702  *
703  * ```js
704  * if (L.Browser.ielt9) {
705  *   alert('Upgrade your browser, dude!');
706  * }
707  * ```
708  */
709
710 (function () {
711
712         var ua = navigator.userAgent.toLowerCase(),
713             doc = document.documentElement,
714
715             ie = 'ActiveXObject' in window,
716
717             webkit    = ua.indexOf('webkit') !== -1,
718             phantomjs = ua.indexOf('phantom') !== -1,
719             android23 = ua.search('android [23]') !== -1,
720             chrome    = ua.indexOf('chrome') !== -1,
721             gecko     = ua.indexOf('gecko') !== -1  && !webkit && !window.opera && !ie,
722
723             win = navigator.platform.indexOf('Win') === 0,
724
725             mobile = typeof orientation !== 'undefined' || ua.indexOf('mobile') !== -1,
726             msPointer = !window.PointerEvent && window.MSPointerEvent,
727             pointer = window.PointerEvent || msPointer,
728
729             ie3d = ie && ('transition' in doc.style),
730             webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
731             gecko3d = 'MozPerspective' in doc.style,
732             opera12 = 'OTransition' in doc.style;
733
734
735         var touch = !window.L_NO_TOUCH && (pointer || 'ontouchstart' in window ||
736                         (window.DocumentTouch && document instanceof window.DocumentTouch));
737
738         L.Browser = {
739
740                 // @property ie: Boolean
741                 // `true` for all Internet Explorer versions (not Edge).
742                 ie: ie,
743
744                 // @property ielt9: Boolean
745                 // `true` for Internet Explorer versions less than 9.
746                 ielt9: ie && !document.addEventListener,
747
748                 // @property edge: Boolean
749                 // `true` for the Edge web browser.
750                 edge: 'msLaunchUri' in navigator && !('documentMode' in document),
751
752                 // @property webkit: Boolean
753                 // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
754                 webkit: webkit,
755
756                 // @property gecko: Boolean
757                 // `true` for gecko-based browsers like Firefox.
758                 gecko: gecko,
759
760                 // @property android: Boolean
761                 // `true` for any browser running on an Android platform.
762                 android: ua.indexOf('android') !== -1,
763
764                 // @property android23: Boolean
765                 // `true` for browsers running on Android 2 or Android 3.
766                 android23: android23,
767
768                 // @property chrome: Boolean
769                 // `true` for the Chrome browser.
770                 chrome: chrome,
771
772                 // @property safari: Boolean
773                 // `true` for the Safari browser.
774                 safari: !chrome && ua.indexOf('safari') !== -1,
775
776
777                 // @property win: Boolean
778                 // `true` when the browser is running in a Windows platform
779                 win: win,
780
781
782                 // @property ie3d: Boolean
783                 // `true` for all Internet Explorer versions supporting CSS transforms.
784                 ie3d: ie3d,
785
786                 // @property webkit3d: Boolean
787                 // `true` for webkit-based browsers supporting CSS transforms.
788                 webkit3d: webkit3d,
789
790                 // @property gecko3d: Boolean
791                 // `true` for gecko-based browsers supporting CSS transforms.
792                 gecko3d: gecko3d,
793
794                 // @property opera12: Boolean
795                 // `true` for the Opera browser supporting CSS transforms (version 12 or later).
796                 opera12: opera12,
797
798                 // @property any3d: Boolean
799                 // `true` for all browsers supporting CSS transforms.
800                 any3d: !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantomjs,
801
802
803                 // @property mobile: Boolean
804                 // `true` for all browsers running in a mobile device.
805                 mobile: mobile,
806
807                 // @property mobileWebkit: Boolean
808                 // `true` for all webkit-based browsers in a mobile device.
809                 mobileWebkit: mobile && webkit,
810
811                 // @property mobileWebkit3d: Boolean
812                 // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
813                 mobileWebkit3d: mobile && webkit3d,
814
815                 // @property mobileOpera: Boolean
816                 // `true` for the Opera browser in a mobile device.
817                 mobileOpera: mobile && window.opera,
818
819                 // @property mobileGecko: Boolean
820                 // `true` for gecko-based browsers running in a mobile device.
821                 mobileGecko: mobile && gecko,
822
823
824                 // @property touch: Boolean
825                 // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
826                 // This does not necessarily mean that the browser is running in a computer with
827                 // a touchscreen, it only means that the browser is capable of understanding
828                 // touch events.
829                 touch: !!touch,
830
831                 // @property msPointer: Boolean
832                 // `true` for browsers implementing the Microsoft touch events model (notably IE10).
833                 msPointer: !!msPointer,
834
835                 // @property pointer: Boolean
836                 // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
837                 pointer: !!pointer,
838
839
840                 // @property retina: Boolean
841                 // `true` for browsers on a high-resolution "retina" screen.
842                 retina: (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1
843         };
844
845 }());
846
847
848
849 /*
850  * @class Point
851  * @aka L.Point
852  *
853  * Represents a point with `x` and `y` coordinates in pixels.
854  *
855  * @example
856  *
857  * ```js
858  * var point = L.point(200, 300);
859  * ```
860  *
861  * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
862  *
863  * ```js
864  * map.panBy([200, 300]);
865  * map.panBy(L.point(200, 300));
866  * ```
867  */
868
869 L.Point = function (x, y, round) {
870         // @property x: Number; The `x` coordinate of the point
871         this.x = (round ? Math.round(x) : x);
872         // @property y: Number; The `y` coordinate of the point
873         this.y = (round ? Math.round(y) : y);
874 };
875
876 L.Point.prototype = {
877
878         // @method clone(): Point
879         // Returns a copy of the current point.
880         clone: function () {
881                 return new L.Point(this.x, this.y);
882         },
883
884         // @method add(otherPoint: Point): Point
885         // Returns the result of addition of the current and the given points.
886         add: function (point) {
887                 // non-destructive, returns a new point
888                 return this.clone()._add(L.point(point));
889         },
890
891         _add: function (point) {
892                 // destructive, used directly for performance in situations where it's safe to modify existing point
893                 this.x += point.x;
894                 this.y += point.y;
895                 return this;
896         },
897
898         // @method subtract(otherPoint: Point): Point
899         // Returns the result of subtraction of the given point from the current.
900         subtract: function (point) {
901                 return this.clone()._subtract(L.point(point));
902         },
903
904         _subtract: function (point) {
905                 this.x -= point.x;
906                 this.y -= point.y;
907                 return this;
908         },
909
910         // @method divideBy(num: Number): Point
911         // Returns the result of division of the current point by the given number.
912         divideBy: function (num) {
913                 return this.clone()._divideBy(num);
914         },
915
916         _divideBy: function (num) {
917                 this.x /= num;
918                 this.y /= num;
919                 return this;
920         },
921
922         // @method multiplyBy(num: Number): Point
923         // Returns the result of multiplication of the current point by the given number.
924         multiplyBy: function (num) {
925                 return this.clone()._multiplyBy(num);
926         },
927
928         _multiplyBy: function (num) {
929                 this.x *= num;
930                 this.y *= num;
931                 return this;
932         },
933
934         // @method scaleBy(scale: Point): Point
935         // Multiply each coordinate of the current point by each coordinate of
936         // `scale`. In linear algebra terms, multiply the point by the
937         // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
938         // defined by `scale`.
939         scaleBy: function (point) {
940                 return new L.Point(this.x * point.x, this.y * point.y);
941         },
942
943         // @method unscaleBy(scale: Point): Point
944         // Inverse of `scaleBy`. Divide each coordinate of the current point by
945         // each coordinate of `scale`.
946         unscaleBy: function (point) {
947                 return new L.Point(this.x / point.x, this.y / point.y);
948         },
949
950         // @method round(): Point
951         // Returns a copy of the current point with rounded coordinates.
952         round: function () {
953                 return this.clone()._round();
954         },
955
956         _round: function () {
957                 this.x = Math.round(this.x);
958                 this.y = Math.round(this.y);
959                 return this;
960         },
961
962         // @method floor(): Point
963         // Returns a copy of the current point with floored coordinates (rounded down).
964         floor: function () {
965                 return this.clone()._floor();
966         },
967
968         _floor: function () {
969                 this.x = Math.floor(this.x);
970                 this.y = Math.floor(this.y);
971                 return this;
972         },
973
974         // @method ceil(): Point
975         // Returns a copy of the current point with ceiled coordinates (rounded up).
976         ceil: function () {
977                 return this.clone()._ceil();
978         },
979
980         _ceil: function () {
981                 this.x = Math.ceil(this.x);
982                 this.y = Math.ceil(this.y);
983                 return this;
984         },
985
986         // @method distanceTo(otherPoint: Point): Number
987         // Returns the cartesian distance between the current and the given points.
988         distanceTo: function (point) {
989                 point = L.point(point);
990
991                 var x = point.x - this.x,
992                     y = point.y - this.y;
993
994                 return Math.sqrt(x * x + y * y);
995         },
996
997         // @method equals(otherPoint: Point): Boolean
998         // Returns `true` if the given point has the same coordinates.
999         equals: function (point) {
1000                 point = L.point(point);
1001
1002                 return point.x === this.x &&
1003                        point.y === this.y;
1004         },
1005
1006         // @method contains(otherPoint: Point): Boolean
1007         // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
1008         contains: function (point) {
1009                 point = L.point(point);
1010
1011                 return Math.abs(point.x) <= Math.abs(this.x) &&
1012                        Math.abs(point.y) <= Math.abs(this.y);
1013         },
1014
1015         // @method toString(): String
1016         // Returns a string representation of the point for debugging purposes.
1017         toString: function () {
1018                 return 'Point(' +
1019                         L.Util.formatNum(this.x) + ', ' +
1020                         L.Util.formatNum(this.y) + ')';
1021         }
1022 };
1023
1024 // @factory L.point(x: Number, y: Number, round?: Boolean)
1025 // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
1026
1027 // @alternative
1028 // @factory L.point(coords: Number[])
1029 // Expects an array of the form `[x, y]` instead.
1030
1031 // @alternative
1032 // @factory L.point(coords: Object)
1033 // Expects a plain object of the form `{x: Number, y: Number}` instead.
1034 L.point = function (x, y, round) {
1035         if (x instanceof L.Point) {
1036                 return x;
1037         }
1038         if (L.Util.isArray(x)) {
1039                 return new L.Point(x[0], x[1]);
1040         }
1041         if (x === undefined || x === null) {
1042                 return x;
1043         }
1044         if (typeof x === 'object' && 'x' in x && 'y' in x) {
1045                 return new L.Point(x.x, x.y);
1046         }
1047         return new L.Point(x, y, round);
1048 };
1049
1050
1051
1052 /*
1053  * @class Bounds
1054  * @aka L.Bounds
1055  *
1056  * Represents a rectangular area in pixel coordinates.
1057  *
1058  * @example
1059  *
1060  * ```js
1061  * var p1 = L.point(10, 10),
1062  * p2 = L.point(40, 60),
1063  * bounds = L.bounds(p1, p2);
1064  * ```
1065  *
1066  * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1067  *
1068  * ```js
1069  * otherBounds.intersects([[10, 10], [40, 60]]);
1070  * ```
1071  */
1072
1073 L.Bounds = function (a, b) {
1074         if (!a) { return; }
1075
1076         var points = b ? [a, b] : a;
1077
1078         for (var i = 0, len = points.length; i < len; i++) {
1079                 this.extend(points[i]);
1080         }
1081 };
1082
1083 L.Bounds.prototype = {
1084         // @method extend(point: Point): this
1085         // Extends the bounds to contain the given point.
1086         extend: function (point) { // (Point)
1087                 point = L.point(point);
1088
1089                 // @property min: Point
1090                 // The top left corner of the rectangle.
1091                 // @property max: Point
1092                 // The bottom right corner of the rectangle.
1093                 if (!this.min && !this.max) {
1094                         this.min = point.clone();
1095                         this.max = point.clone();
1096                 } else {
1097                         this.min.x = Math.min(point.x, this.min.x);
1098                         this.max.x = Math.max(point.x, this.max.x);
1099                         this.min.y = Math.min(point.y, this.min.y);
1100                         this.max.y = Math.max(point.y, this.max.y);
1101                 }
1102                 return this;
1103         },
1104
1105         // @method getCenter(round?: Boolean): Point
1106         // Returns the center point of the bounds.
1107         getCenter: function (round) {
1108                 return new L.Point(
1109                         (this.min.x + this.max.x) / 2,
1110                         (this.min.y + this.max.y) / 2, round);
1111         },
1112
1113         // @method getBottomLeft(): Point
1114         // Returns the bottom-left point of the bounds.
1115         getBottomLeft: function () {
1116                 return new L.Point(this.min.x, this.max.y);
1117         },
1118
1119         // @method getTopRight(): Point
1120         // Returns the top-right point of the bounds.
1121         getTopRight: function () { // -> Point
1122                 return new L.Point(this.max.x, this.min.y);
1123         },
1124
1125         // @method getSize(): Point
1126         // Returns the size of the given bounds
1127         getSize: function () {
1128                 return this.max.subtract(this.min);
1129         },
1130
1131         // @method contains(otherBounds: Bounds): Boolean
1132         // Returns `true` if the rectangle contains the given one.
1133         // @alternative
1134         // @method contains(point: Point): Boolean
1135         // Returns `true` if the rectangle contains the given point.
1136         contains: function (obj) {
1137                 var min, max;
1138
1139                 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
1140                         obj = L.point(obj);
1141                 } else {
1142                         obj = L.bounds(obj);
1143                 }
1144
1145                 if (obj instanceof L.Bounds) {
1146                         min = obj.min;
1147                         max = obj.max;
1148                 } else {
1149                         min = max = obj;
1150                 }
1151
1152                 return (min.x >= this.min.x) &&
1153                        (max.x <= this.max.x) &&
1154                        (min.y >= this.min.y) &&
1155                        (max.y <= this.max.y);
1156         },
1157
1158         // @method intersects(otherBounds: Bounds): Boolean
1159         // Returns `true` if the rectangle intersects the given bounds. Two bounds
1160         // intersect if they have at least one point in common.
1161         intersects: function (bounds) { // (Bounds) -> Boolean
1162                 bounds = L.bounds(bounds);
1163
1164                 var min = this.min,
1165                     max = this.max,
1166                     min2 = bounds.min,
1167                     max2 = bounds.max,
1168                     xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
1169                     yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
1170
1171                 return xIntersects && yIntersects;
1172         },
1173
1174         // @method overlaps(otherBounds: Bounds): Boolean
1175         // Returns `true` if the rectangle overlaps the given bounds. Two bounds
1176         // overlap if their intersection is an area.
1177         overlaps: function (bounds) { // (Bounds) -> Boolean
1178                 bounds = L.bounds(bounds);
1179
1180                 var min = this.min,
1181                     max = this.max,
1182                     min2 = bounds.min,
1183                     max2 = bounds.max,
1184                     xOverlaps = (max2.x > min.x) && (min2.x < max.x),
1185                     yOverlaps = (max2.y > min.y) && (min2.y < max.y);
1186
1187                 return xOverlaps && yOverlaps;
1188         },
1189
1190         isValid: function () {
1191                 return !!(this.min && this.max);
1192         }
1193 };
1194
1195
1196 // @factory L.bounds(topLeft: Point, bottomRight: Point)
1197 // Creates a Bounds object from two coordinates (usually top-left and bottom-right corners).
1198 // @alternative
1199 // @factory L.bounds(points: Point[])
1200 // Creates a Bounds object from the points it contains
1201 L.bounds = function (a, b) {
1202         if (!a || a instanceof L.Bounds) {
1203                 return a;
1204         }
1205         return new L.Bounds(a, b);
1206 };
1207
1208
1209
1210 /*
1211  * @class Transformation
1212  * @aka L.Transformation
1213  *
1214  * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
1215  * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
1216  * the reverse. Used by Leaflet in its projections code.
1217  *
1218  * @example
1219  *
1220  * ```js
1221  * var transformation = new L.Transformation(2, 5, -1, 10),
1222  *      p = L.point(1, 2),
1223  *      p2 = transformation.transform(p), //  L.point(7, 8)
1224  *      p3 = transformation.untransform(p2); //  L.point(1, 2)
1225  * ```
1226  */
1227
1228
1229 // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
1230 // Creates a `Transformation` object with the given coefficients.
1231 L.Transformation = function (a, b, c, d) {
1232         this._a = a;
1233         this._b = b;
1234         this._c = c;
1235         this._d = d;
1236 };
1237
1238 L.Transformation.prototype = {
1239         // @method transform(point: Point, scale?: Number): Point
1240         // Returns a transformed point, optionally multiplied by the given scale.
1241         // Only accepts actual `L.Point` instances, not arrays.
1242         transform: function (point, scale) { // (Point, Number) -> Point
1243                 return this._transform(point.clone(), scale);
1244         },
1245
1246         // destructive transform (faster)
1247         _transform: function (point, scale) {
1248                 scale = scale || 1;
1249                 point.x = scale * (this._a * point.x + this._b);
1250                 point.y = scale * (this._c * point.y + this._d);
1251                 return point;
1252         },
1253
1254         // @method untransform(point: Point, scale?: Number): Point
1255         // Returns the reverse transformation of the given point, optionally divided
1256         // by the given scale. Only accepts actual `L.Point` instances, not arrays.
1257         untransform: function (point, scale) {
1258                 scale = scale || 1;
1259                 return new L.Point(
1260                         (point.x / scale - this._b) / this._a,
1261                         (point.y / scale - this._d) / this._c);
1262         }
1263 };
1264
1265
1266
1267 /*
1268  * @namespace DomUtil
1269  *
1270  * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
1271  * tree, used by Leaflet internally.
1272  *
1273  * Most functions expecting or returning a `HTMLElement` also work for
1274  * SVG elements. The only difference is that classes refer to CSS classes
1275  * in HTML and SVG classes in SVG.
1276  */
1277
1278 L.DomUtil = {
1279
1280         // @function get(id: String|HTMLElement): HTMLElement
1281         // Returns an element given its DOM id, or returns the element itself
1282         // if it was passed directly.
1283         get: function (id) {
1284                 return typeof id === 'string' ? document.getElementById(id) : id;
1285         },
1286
1287         // @function getStyle(el: HTMLElement, styleAttrib: String): String
1288         // Returns the value for a certain style attribute on an element,
1289         // including computed values or values set through CSS.
1290         getStyle: function (el, style) {
1291
1292                 var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);
1293
1294                 if ((!value || value === 'auto') && document.defaultView) {
1295                         var css = document.defaultView.getComputedStyle(el, null);
1296                         value = css ? css[style] : null;
1297                 }
1298
1299                 return value === 'auto' ? null : value;
1300         },
1301
1302         // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
1303         // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
1304         create: function (tagName, className, container) {
1305
1306                 var el = document.createElement(tagName);
1307                 el.className = className || '';
1308
1309                 if (container) {
1310                         container.appendChild(el);
1311                 }
1312
1313                 return el;
1314         },
1315
1316         // @function remove(el: HTMLElement)
1317         // Removes `el` from its parent element
1318         remove: function (el) {
1319                 var parent = el.parentNode;
1320                 if (parent) {
1321                         parent.removeChild(el);
1322                 }
1323         },
1324
1325         // @function empty(el: HTMLElement)
1326         // Removes all of `el`'s children elements from `el`
1327         empty: function (el) {
1328                 while (el.firstChild) {
1329                         el.removeChild(el.firstChild);
1330                 }
1331         },
1332
1333         // @function toFront(el: HTMLElement)
1334         // Makes `el` the last children of its parent, so it renders in front of the other children.
1335         toFront: function (el) {
1336                 el.parentNode.appendChild(el);
1337         },
1338
1339         // @function toBack(el: HTMLElement)
1340         // Makes `el` the first children of its parent, so it renders back from the other children.
1341         toBack: function (el) {
1342                 var parent = el.parentNode;
1343                 parent.insertBefore(el, parent.firstChild);
1344         },
1345
1346         // @function hasClass(el: HTMLElement, name: String): Boolean
1347         // Returns `true` if the element's class attribute contains `name`.
1348         hasClass: function (el, name) {
1349                 if (el.classList !== undefined) {
1350                         return el.classList.contains(name);
1351                 }
1352                 var className = L.DomUtil.getClass(el);
1353                 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
1354         },
1355
1356         // @function addClass(el: HTMLElement, name: String)
1357         // Adds `name` to the element's class attribute.
1358         addClass: function (el, name) {
1359                 if (el.classList !== undefined) {
1360                         var classes = L.Util.splitWords(name);
1361                         for (var i = 0, len = classes.length; i < len; i++) {
1362                                 el.classList.add(classes[i]);
1363                         }
1364                 } else if (!L.DomUtil.hasClass(el, name)) {
1365                         var className = L.DomUtil.getClass(el);
1366                         L.DomUtil.setClass(el, (className ? className + ' ' : '') + name);
1367                 }
1368         },
1369
1370         // @function removeClass(el: HTMLElement, name: String)
1371         // Removes `name` from the element's class attribute.
1372         removeClass: function (el, name) {
1373                 if (el.classList !== undefined) {
1374                         el.classList.remove(name);
1375                 } else {
1376                         L.DomUtil.setClass(el, L.Util.trim((' ' + L.DomUtil.getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
1377                 }
1378         },
1379
1380         // @function setClass(el: HTMLElement, name: String)
1381         // Sets the element's class.
1382         setClass: function (el, name) {
1383                 if (el.className.baseVal === undefined) {
1384                         el.className = name;
1385                 } else {
1386                         // in case of SVG element
1387                         el.className.baseVal = name;
1388                 }
1389         },
1390
1391         // @function getClass(el: HTMLElement): String
1392         // Returns the element's class.
1393         getClass: function (el) {
1394                 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
1395         },
1396
1397         // @function setOpacity(el: HTMLElement, opacity: Number)
1398         // Set the opacity of an element (including old IE support).
1399         // `opacity` must be a number from `0` to `1`.
1400         setOpacity: function (el, value) {
1401
1402                 if ('opacity' in el.style) {
1403                         el.style.opacity = value;
1404
1405                 } else if ('filter' in el.style) {
1406                         L.DomUtil._setOpacityIE(el, value);
1407                 }
1408         },
1409
1410         _setOpacityIE: function (el, value) {
1411                 var filter = false,
1412                     filterName = 'DXImageTransform.Microsoft.Alpha';
1413
1414                 // filters collection throws an error if we try to retrieve a filter that doesn't exist
1415                 try {
1416                         filter = el.filters.item(filterName);
1417                 } catch (e) {
1418                         // don't set opacity to 1 if we haven't already set an opacity,
1419                         // it isn't needed and breaks transparent pngs.
1420                         if (value === 1) { return; }
1421                 }
1422
1423                 value = Math.round(value * 100);
1424
1425                 if (filter) {
1426                         filter.Enabled = (value !== 100);
1427                         filter.Opacity = value;
1428                 } else {
1429                         el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
1430                 }
1431         },
1432
1433         // @function testProp(props: String[]): String|false
1434         // Goes through the array of style names and returns the first name
1435         // that is a valid style name for an element. If no such name is found,
1436         // it returns false. Useful for vendor-prefixed styles like `transform`.
1437         testProp: function (props) {
1438
1439                 var style = document.documentElement.style;
1440
1441                 for (var i = 0; i < props.length; i++) {
1442                         if (props[i] in style) {
1443                                 return props[i];
1444                         }
1445                 }
1446                 return false;
1447         },
1448
1449         // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
1450         // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
1451         // and optionally scaled by `scale`. Does not have an effect if the
1452         // browser doesn't support 3D CSS transforms.
1453         setTransform: function (el, offset, scale) {
1454                 var pos = offset || new L.Point(0, 0);
1455
1456                 el.style[L.DomUtil.TRANSFORM] =
1457                         (L.Browser.ie3d ?
1458                                 'translate(' + pos.x + 'px,' + pos.y + 'px)' :
1459                                 'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
1460                         (scale ? ' scale(' + scale + ')' : '');
1461         },
1462
1463         // @function setPosition(el: HTMLElement, position: Point)
1464         // Sets the position of `el` to coordinates specified by `position`,
1465         // using CSS translate or top/left positioning depending on the browser
1466         // (used by Leaflet internally to position its layers).
1467         setPosition: function (el, point) { // (HTMLElement, Point[, Boolean])
1468
1469                 /*eslint-disable */
1470                 el._leaflet_pos = point;
1471                 /*eslint-enable */
1472
1473                 if (L.Browser.any3d) {
1474                         L.DomUtil.setTransform(el, point);
1475                 } else {
1476                         el.style.left = point.x + 'px';
1477                         el.style.top = point.y + 'px';
1478                 }
1479         },
1480
1481         // @function getPosition(el: HTMLElement): Point
1482         // Returns the coordinates of an element previously positioned with setPosition.
1483         getPosition: function (el) {
1484                 // this method is only used for elements previously positioned using setPosition,
1485                 // so it's safe to cache the position for performance
1486
1487                 return el._leaflet_pos || new L.Point(0, 0);
1488         }
1489 };
1490
1491
1492 (function () {
1493         // prefix style property names
1494
1495         // @property TRANSFORM: String
1496         // Vendor-prefixed fransform style name (e.g. `'webkitTransform'` for WebKit).
1497         L.DomUtil.TRANSFORM = L.DomUtil.testProp(
1498                         ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
1499
1500
1501         // webkitTransition comes first because some browser versions that drop vendor prefix don't do
1502         // the same for the transitionend event, in particular the Android 4.1 stock browser
1503
1504         // @property TRANSITION: String
1505         // Vendor-prefixed transform style name.
1506         var transition = L.DomUtil.TRANSITION = L.DomUtil.testProp(
1507                         ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
1508
1509         L.DomUtil.TRANSITION_END =
1510                         transition === 'webkitTransition' || transition === 'OTransition' ? transition + 'End' : 'transitionend';
1511
1512         // @function disableTextSelection()
1513         // Prevents the user from generating `selectstart` DOM events, usually generated
1514         // when the user drags the mouse through a page with text. Used internally
1515         // by Leaflet to override the behaviour of any click-and-drag interaction on
1516         // the map. Affects drag interactions on the whole document.
1517
1518         // @function enableTextSelection()
1519         // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
1520         if ('onselectstart' in document) {
1521                 L.DomUtil.disableTextSelection = function () {
1522                         L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
1523                 };
1524                 L.DomUtil.enableTextSelection = function () {
1525                         L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
1526                 };
1527
1528         } else {
1529                 var userSelectProperty = L.DomUtil.testProp(
1530                         ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
1531
1532                 L.DomUtil.disableTextSelection = function () {
1533                         if (userSelectProperty) {
1534                                 var style = document.documentElement.style;
1535                                 this._userSelect = style[userSelectProperty];
1536                                 style[userSelectProperty] = 'none';
1537                         }
1538                 };
1539                 L.DomUtil.enableTextSelection = function () {
1540                         if (userSelectProperty) {
1541                                 document.documentElement.style[userSelectProperty] = this._userSelect;
1542                                 delete this._userSelect;
1543                         }
1544                 };
1545         }
1546
1547         // @function disableImageDrag()
1548         // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
1549         // for `dragstart` DOM events, usually generated when the user drags an image.
1550         L.DomUtil.disableImageDrag = function () {
1551                 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
1552         };
1553
1554         // @function enableImageDrag()
1555         // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
1556         L.DomUtil.enableImageDrag = function () {
1557                 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
1558         };
1559
1560         // @function preventOutline(el: HTMLElement)
1561         // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
1562         // of the element `el` invisible. Used internally by Leaflet to prevent
1563         // focusable elements from displaying an outline when the user performs a
1564         // drag interaction on them.
1565         L.DomUtil.preventOutline = function (element) {
1566                 while (element.tabIndex === -1) {
1567                         element = element.parentNode;
1568                 }
1569                 if (!element || !element.style) { return; }
1570                 L.DomUtil.restoreOutline();
1571                 this._outlineElement = element;
1572                 this._outlineStyle = element.style.outline;
1573                 element.style.outline = 'none';
1574                 L.DomEvent.on(window, 'keydown', L.DomUtil.restoreOutline, this);
1575         };
1576
1577         // @function restoreOutline()
1578         // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
1579         L.DomUtil.restoreOutline = function () {
1580                 if (!this._outlineElement) { return; }
1581                 this._outlineElement.style.outline = this._outlineStyle;
1582                 delete this._outlineElement;
1583                 delete this._outlineStyle;
1584                 L.DomEvent.off(window, 'keydown', L.DomUtil.restoreOutline, this);
1585         };
1586 })();
1587
1588
1589
1590 /* @class LatLng
1591  * @aka L.LatLng
1592  *
1593  * Represents a geographical point with a certain latitude and longitude.
1594  *
1595  * @example
1596  *
1597  * ```
1598  * var latlng = L.latLng(50.5, 30.5);
1599  * ```
1600  *
1601  * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
1602  *
1603  * ```
1604  * map.panTo([50, 30]);
1605  * map.panTo({lon: 30, lat: 50});
1606  * map.panTo({lat: 50, lng: 30});
1607  * map.panTo(L.latLng(50, 30));
1608  * ```
1609  */
1610
1611 L.LatLng = function (lat, lng, alt) {
1612         if (isNaN(lat) || isNaN(lng)) {
1613                 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
1614         }
1615
1616         // @property lat: Number
1617         // Latitude in degrees
1618         this.lat = +lat;
1619
1620         // @property lng: Number
1621         // Longitude in degrees
1622         this.lng = +lng;
1623
1624         // @property alt: Number
1625         // Altitude in meters (optional)
1626         if (alt !== undefined) {
1627                 this.alt = +alt;
1628         }
1629 };
1630
1631 L.LatLng.prototype = {
1632         // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
1633         // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overriden by setting `maxMargin` to a small number.
1634         equals: function (obj, maxMargin) {
1635                 if (!obj) { return false; }
1636
1637                 obj = L.latLng(obj);
1638
1639                 var margin = Math.max(
1640                         Math.abs(this.lat - obj.lat),
1641                         Math.abs(this.lng - obj.lng));
1642
1643                 return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
1644         },
1645
1646         // @method toString(): String
1647         // Returns a string representation of the point (for debugging purposes).
1648         toString: function (precision) {
1649                 return 'LatLng(' +
1650                         L.Util.formatNum(this.lat, precision) + ', ' +
1651                         L.Util.formatNum(this.lng, precision) + ')';
1652         },
1653
1654         // @method distanceTo(otherLatLng: LatLng): Number
1655         // Returns the distance (in meters) to the given `LatLng` calculated using the [Haversine formula](http://en.wikipedia.org/wiki/Haversine_formula).
1656         distanceTo: function (other) {
1657                 return L.CRS.Earth.distance(this, L.latLng(other));
1658         },
1659
1660         // @method wrap(): LatLng
1661         // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
1662         wrap: function () {
1663                 return L.CRS.Earth.wrapLatLng(this);
1664         },
1665
1666         // @method toBounds(sizeInMeters: Number): LatLngBounds
1667         // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
1668         toBounds: function (sizeInMeters) {
1669                 var latAccuracy = 180 * sizeInMeters / 40075017,
1670                     lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);
1671
1672                 return L.latLngBounds(
1673                         [this.lat - latAccuracy, this.lng - lngAccuracy],
1674                         [this.lat + latAccuracy, this.lng + lngAccuracy]);
1675         },
1676
1677         clone: function () {
1678                 return new L.LatLng(this.lat, this.lng, this.alt);
1679         }
1680 };
1681
1682
1683
1684 // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
1685 // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).
1686
1687 // @alternative
1688 // @factory L.latLng(coords: Array): LatLng
1689 // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.
1690
1691 // @alternative
1692 // @factory L.latLng(coords: Object): LatLng
1693 // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.
1694
1695 L.latLng = function (a, b, c) {
1696         if (a instanceof L.LatLng) {
1697                 return a;
1698         }
1699         if (L.Util.isArray(a) && typeof a[0] !== 'object') {
1700                 if (a.length === 3) {
1701                         return new L.LatLng(a[0], a[1], a[2]);
1702                 }
1703                 if (a.length === 2) {
1704                         return new L.LatLng(a[0], a[1]);
1705                 }
1706                 return null;
1707         }
1708         if (a === undefined || a === null) {
1709                 return a;
1710         }
1711         if (typeof a === 'object' && 'lat' in a) {
1712                 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
1713         }
1714         if (b === undefined) {
1715                 return null;
1716         }
1717         return new L.LatLng(a, b, c);
1718 };
1719
1720
1721
1722 /*
1723  * @class LatLngBounds
1724  * @aka L.LatLngBounds
1725  *
1726  * Represents a rectangular geographical area on a map.
1727  *
1728  * @example
1729  *
1730  * ```js
1731  * var corner1 = L.latLng(40.712, -74.227),
1732  * corner2 = L.latLng(40.774, -74.125),
1733  * bounds = L.latLngBounds(corner1, corner2);
1734  * ```
1735  *
1736  * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
1737  *
1738  * ```js
1739  * map.fitBounds([
1740  *      [40.712, -74.227],
1741  *      [40.774, -74.125]
1742  * ]);
1743  * ```
1744  *
1745  * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
1746  */
1747
1748 L.LatLngBounds = function (corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
1749         if (!corner1) { return; }
1750
1751         var latlngs = corner2 ? [corner1, corner2] : corner1;
1752
1753         for (var i = 0, len = latlngs.length; i < len; i++) {
1754                 this.extend(latlngs[i]);
1755         }
1756 };
1757
1758 L.LatLngBounds.prototype = {
1759
1760         // @method extend(latlng: LatLng): this
1761         // Extend the bounds to contain the given point
1762
1763         // @alternative
1764         // @method extend(otherBounds: LatLngBounds): this
1765         // Extend the bounds to contain the given bounds
1766         extend: function (obj) {
1767                 var sw = this._southWest,
1768                     ne = this._northEast,
1769                     sw2, ne2;
1770
1771                 if (obj instanceof L.LatLng) {
1772                         sw2 = obj;
1773                         ne2 = obj;
1774
1775                 } else if (obj instanceof L.LatLngBounds) {
1776                         sw2 = obj._southWest;
1777                         ne2 = obj._northEast;
1778
1779                         if (!sw2 || !ne2) { return this; }
1780
1781                 } else {
1782                         return obj ? this.extend(L.latLng(obj) || L.latLngBounds(obj)) : this;
1783                 }
1784
1785                 if (!sw && !ne) {
1786                         this._southWest = new L.LatLng(sw2.lat, sw2.lng);
1787                         this._northEast = new L.LatLng(ne2.lat, ne2.lng);
1788                 } else {
1789                         sw.lat = Math.min(sw2.lat, sw.lat);
1790                         sw.lng = Math.min(sw2.lng, sw.lng);
1791                         ne.lat = Math.max(ne2.lat, ne.lat);
1792                         ne.lng = Math.max(ne2.lng, ne.lng);
1793                 }
1794
1795                 return this;
1796         },
1797
1798         // @method pad(bufferRatio: Number): LatLngBounds
1799         // Returns bigger bounds created by extending the current bounds by a given percentage in each direction.
1800         pad: function (bufferRatio) {
1801                 var sw = this._southWest,
1802                     ne = this._northEast,
1803                     heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
1804                     widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
1805
1806                 return new L.LatLngBounds(
1807                         new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
1808                         new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
1809         },
1810
1811         // @method getCenter(): LatLng
1812         // Returns the center point of the bounds.
1813         getCenter: function () {
1814                 return new L.LatLng(
1815                         (this._southWest.lat + this._northEast.lat) / 2,
1816                         (this._southWest.lng + this._northEast.lng) / 2);
1817         },
1818
1819         // @method getSouthWest(): LatLng
1820         // Returns the south-west point of the bounds.
1821         getSouthWest: function () {
1822                 return this._southWest;
1823         },
1824
1825         // @method getNorthEast(): LatLng
1826         // Returns the north-east point of the bounds.
1827         getNorthEast: function () {
1828                 return this._northEast;
1829         },
1830
1831         // @method getNorthWest(): LatLng
1832         // Returns the north-west point of the bounds.
1833         getNorthWest: function () {
1834                 return new L.LatLng(this.getNorth(), this.getWest());
1835         },
1836
1837         // @method getSouthEast(): LatLng
1838         // Returns the south-east point of the bounds.
1839         getSouthEast: function () {
1840                 return new L.LatLng(this.getSouth(), this.getEast());
1841         },
1842
1843         // @method getWest(): Number
1844         // Returns the west longitude of the bounds
1845         getWest: function () {
1846                 return this._southWest.lng;
1847         },
1848
1849         // @method getSouth(): Number
1850         // Returns the south latitude of the bounds
1851         getSouth: function () {
1852                 return this._southWest.lat;
1853         },
1854
1855         // @method getEast(): Number
1856         // Returns the east longitude of the bounds
1857         getEast: function () {
1858                 return this._northEast.lng;
1859         },
1860
1861         // @method getNorth(): Number
1862         // Returns the north latitude of the bounds
1863         getNorth: function () {
1864                 return this._northEast.lat;
1865         },
1866
1867         // @method contains(otherBounds: LatLngBounds): Boolean
1868         // Returns `true` if the rectangle contains the given one.
1869
1870         // @alternative
1871         // @method contains (latlng: LatLng): Boolean
1872         // Returns `true` if the rectangle contains the given point.
1873         contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
1874                 if (typeof obj[0] === 'number' || obj instanceof L.LatLng || 'lat' in obj) {
1875                         obj = L.latLng(obj);
1876                 } else {
1877                         obj = L.latLngBounds(obj);
1878                 }
1879
1880                 var sw = this._southWest,
1881                     ne = this._northEast,
1882                     sw2, ne2;
1883
1884                 if (obj instanceof L.LatLngBounds) {
1885                         sw2 = obj.getSouthWest();
1886                         ne2 = obj.getNorthEast();
1887                 } else {
1888                         sw2 = ne2 = obj;
1889                 }
1890
1891                 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
1892                        (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
1893         },
1894
1895         // @method intersects(otherBounds: LatLngBounds): Boolean
1896         // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
1897         intersects: function (bounds) {
1898                 bounds = L.latLngBounds(bounds);
1899
1900                 var sw = this._southWest,
1901                     ne = this._northEast,
1902                     sw2 = bounds.getSouthWest(),
1903                     ne2 = bounds.getNorthEast(),
1904
1905                     latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
1906                     lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
1907
1908                 return latIntersects && lngIntersects;
1909         },
1910
1911         // @method overlaps(otherBounds: Bounds): Boolean
1912         // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
1913         overlaps: function (bounds) {
1914                 bounds = L.latLngBounds(bounds);
1915
1916                 var sw = this._southWest,
1917                     ne = this._northEast,
1918                     sw2 = bounds.getSouthWest(),
1919                     ne2 = bounds.getNorthEast(),
1920
1921                     latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
1922                     lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);
1923
1924                 return latOverlaps && lngOverlaps;
1925         },
1926
1927         // @method toBBoxString(): String
1928         // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
1929         toBBoxString: function () {
1930                 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
1931         },
1932
1933         // @method equals(otherBounds: LatLngBounds): Boolean
1934         // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds.
1935         equals: function (bounds) {
1936                 if (!bounds) { return false; }
1937
1938                 bounds = L.latLngBounds(bounds);
1939
1940                 return this._southWest.equals(bounds.getSouthWest()) &&
1941                        this._northEast.equals(bounds.getNorthEast());
1942         },
1943
1944         // @method isValid(): Boolean
1945         // Returns `true` if the bounds are properly initialized.
1946         isValid: function () {
1947                 return !!(this._southWest && this._northEast);
1948         }
1949 };
1950
1951 // TODO International date line?
1952
1953 // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
1954 // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.
1955
1956 // @alternative
1957 // @factory L.latLngBounds(latlngs: LatLng[])
1958 // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
1959 L.latLngBounds = function (a, b) {
1960         if (a instanceof L.LatLngBounds) {
1961                 return a;
1962         }
1963         return new L.LatLngBounds(a, b);
1964 };
1965
1966
1967
1968 /*
1969  * @namespace Projection
1970  * @section
1971  * Leaflet comes with a set of already defined Projections out of the box:
1972  *
1973  * @projection L.Projection.LonLat
1974  *
1975  * Equirectangular, or Plate Carree projection — the most simple projection,
1976  * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
1977  * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
1978  * `EPSG:3395` and `Simple` CRS.
1979  */
1980
1981 L.Projection = {};
1982
1983 L.Projection.LonLat = {
1984         project: function (latlng) {
1985                 return new L.Point(latlng.lng, latlng.lat);
1986         },
1987
1988         unproject: function (point) {
1989                 return new L.LatLng(point.y, point.x);
1990         },
1991
1992         bounds: L.bounds([-180, -90], [180, 90])
1993 };
1994
1995
1996
1997 /*
1998  * @namespace Projection
1999  * @projection L.Projection.SphericalMercator
2000  *
2001  * Spherical Mercator projection — the most common projection for online maps,
2002  * used by almost all free and commercial tile providers. Assumes that Earth is
2003  * a sphere. Used by the `EPSG:3857` CRS.
2004  */
2005
2006 L.Projection.SphericalMercator = {
2007
2008         R: 6378137,
2009         MAX_LATITUDE: 85.0511287798,
2010
2011         project: function (latlng) {
2012                 var d = Math.PI / 180,
2013                     max = this.MAX_LATITUDE,
2014                     lat = Math.max(Math.min(max, latlng.lat), -max),
2015                     sin = Math.sin(lat * d);
2016
2017                 return new L.Point(
2018                                 this.R * latlng.lng * d,
2019                                 this.R * Math.log((1 + sin) / (1 - sin)) / 2);
2020         },
2021
2022         unproject: function (point) {
2023                 var d = 180 / Math.PI;
2024
2025                 return new L.LatLng(
2026                         (2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
2027                         point.x * d / this.R);
2028         },
2029
2030         bounds: (function () {
2031                 var d = 6378137 * Math.PI;
2032                 return L.bounds([-d, -d], [d, d]);
2033         })()
2034 };
2035
2036
2037
2038 /*
2039  * @class CRS
2040  * @aka L.CRS
2041  * Abstract class that defines coordinate reference systems for projecting
2042  * geographical points into pixel (screen) coordinates and back (and to
2043  * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
2044  * [spatial reference system](http://en.wikipedia.org/wiki/Coordinate_reference_system).
2045  *
2046  * Leaflet defines the most usual CRSs by default. If you want to use a
2047  * CRS not defined by default, take a look at the
2048  * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
2049  */
2050
2051 L.CRS = {
2052         // @method latLngToPoint(latlng: LatLng, zoom: Number): Point
2053         // Projects geographical coordinates into pixel coordinates for a given zoom.
2054         latLngToPoint: function (latlng, zoom) {
2055                 var projectedPoint = this.projection.project(latlng),
2056                     scale = this.scale(zoom);
2057
2058                 return this.transformation._transform(projectedPoint, scale);
2059         },
2060
2061         // @method pointToLatLng(point: Point, zoom: Number): LatLng
2062         // The inverse of `latLngToPoint`. Projects pixel coordinates on a given
2063         // zoom into geographical coordinates.
2064         pointToLatLng: function (point, zoom) {
2065                 var scale = this.scale(zoom),
2066                     untransformedPoint = this.transformation.untransform(point, scale);
2067
2068                 return this.projection.unproject(untransformedPoint);
2069         },
2070
2071         // @method project(latlng: LatLng): Point
2072         // Projects geographical coordinates into coordinates in units accepted for
2073         // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
2074         project: function (latlng) {
2075                 return this.projection.project(latlng);
2076         },
2077
2078         // @method unproject(point: Point): LatLng
2079         // Given a projected coordinate returns the corresponding LatLng.
2080         // The inverse of `project`.
2081         unproject: function (point) {
2082                 return this.projection.unproject(point);
2083         },
2084
2085         // @method scale(zoom: Number): Number
2086         // Returns the scale used when transforming projected coordinates into
2087         // pixel coordinates for a particular zoom. For example, it returns
2088         // `256 * 2^zoom` for Mercator-based CRS.
2089         scale: function (zoom) {
2090                 return 256 * Math.pow(2, zoom);
2091         },
2092
2093         // @method zoom(scale: Number): Number
2094         // Inverse of `scale()`, returns the zoom level corresponding to a scale
2095         // factor of `scale`.
2096         zoom: function (scale) {
2097                 return Math.log(scale / 256) / Math.LN2;
2098         },
2099
2100         // @method getProjectedBounds(zoom: Number): Bounds
2101         // Returns the projection's bounds scaled and transformed for the provided `zoom`.
2102         getProjectedBounds: function (zoom) {
2103                 if (this.infinite) { return null; }
2104
2105                 var b = this.projection.bounds,
2106                     s = this.scale(zoom),
2107                     min = this.transformation.transform(b.min, s),
2108                     max = this.transformation.transform(b.max, s);
2109
2110                 return L.bounds(min, max);
2111         },
2112
2113         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
2114         // Returns the distance between two geographical coordinates.
2115
2116         // @property code: String
2117         // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
2118         //
2119         // @property wrapLng: Number[]
2120         // An array of two numbers defining whether the longitude (horizontal) coordinate
2121         // axis wraps around a given range and how. Defaults to `[-180, 180]` in most
2122         // geographical CRSs. If `undefined`, the longitude axis does not wrap around.
2123         //
2124         // @property wrapLat: Number[]
2125         // Like `wrapLng`, but for the latitude (vertical) axis.
2126
2127         // wrapLng: [min, max],
2128         // wrapLat: [min, max],
2129
2130         // @property infinite: Boolean
2131         // If true, the coordinate space will be unbounded (infinite in both axes)
2132         infinite: false,
2133
2134         // @method wrapLatLng(latlng: LatLng): LatLng
2135         // Returns a `LatLng` where lat and lng has been wrapped according to the
2136         // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
2137         // Only accepts actual `L.LatLng` instances, not arrays.
2138         wrapLatLng: function (latlng) {
2139                 var lng = this.wrapLng ? L.Util.wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
2140                     lat = this.wrapLat ? L.Util.wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
2141                     alt = latlng.alt;
2142
2143                 return L.latLng(lat, lng, alt);
2144         },
2145
2146         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
2147         // Returns a `LatLngBounds` with the same size as the given one, ensuring
2148         // that its center is within the CRS's bounds.
2149         // Only accepts actual `L.LatLngBounds` instances, not arrays.
2150         wrapLatLngBounds: function (bounds) {
2151                 var center = bounds.getCenter(),
2152                     newCenter = this.wrapLatLng(center),
2153                     latShift = center.lat - newCenter.lat,
2154                     lngShift = center.lng - newCenter.lng;
2155
2156                 if (latShift === 0 && lngShift === 0) {
2157                         return bounds;
2158                 }
2159
2160                 var sw = bounds.getSouthWest(),
2161                     ne = bounds.getNorthEast(),
2162                     newSw = L.latLng({lat: sw.lat - latShift, lng: sw.lng - lngShift}),
2163                     newNe = L.latLng({lat: ne.lat - latShift, lng: ne.lng - lngShift});
2164
2165                 return new L.LatLngBounds(newSw, newNe);
2166         }
2167 };
2168
2169
2170
2171 /*
2172  * @namespace CRS
2173  * @crs L.CRS.Simple
2174  *
2175  * A simple CRS that maps longitude and latitude into `x` and `y` directly.
2176  * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
2177  * axis should still be inverted (going from bottom to top). `distance()` returns
2178  * simple euclidean distance.
2179  */
2180
2181 L.CRS.Simple = L.extend({}, L.CRS, {
2182         projection: L.Projection.LonLat,
2183         transformation: new L.Transformation(1, 0, -1, 0),
2184
2185         scale: function (zoom) {
2186                 return Math.pow(2, zoom);
2187         },
2188
2189         zoom: function (scale) {
2190                 return Math.log(scale) / Math.LN2;
2191         },
2192
2193         distance: function (latlng1, latlng2) {
2194                 var dx = latlng2.lng - latlng1.lng,
2195                     dy = latlng2.lat - latlng1.lat;
2196
2197                 return Math.sqrt(dx * dx + dy * dy);
2198         },
2199
2200         infinite: true
2201 });
2202
2203
2204
2205 /*
2206  * @namespace CRS
2207  * @crs L.CRS.Earth
2208  *
2209  * Serves as the base for CRS that are global such that they cover the earth.
2210  * Can only be used as the base for other CRS and cannot be used directly,
2211  * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
2212  * meters.
2213  */
2214
2215 L.CRS.Earth = L.extend({}, L.CRS, {
2216         wrapLng: [-180, 180],
2217
2218         // Mean Earth Radius, as recommended for use by
2219         // the International Union of Geodesy and Geophysics,
2220         // see http://rosettacode.org/wiki/Haversine_formula
2221         R: 6371000,
2222
2223         // distance between two geographical points using spherical law of cosines approximation
2224         distance: function (latlng1, latlng2) {
2225                 var rad = Math.PI / 180,
2226                     lat1 = latlng1.lat * rad,
2227                     lat2 = latlng2.lat * rad,
2228                     a = Math.sin(lat1) * Math.sin(lat2) +
2229                         Math.cos(lat1) * Math.cos(lat2) * Math.cos((latlng2.lng - latlng1.lng) * rad);
2230
2231                 return this.R * Math.acos(Math.min(a, 1));
2232         }
2233 });
2234
2235
2236
2237 /*
2238  * @namespace CRS
2239  * @crs L.CRS.EPSG3857
2240  *
2241  * The most common CRS for online maps, used by almost all free and commercial
2242  * tile providers. Uses Spherical Mercator projection. Set in by default in
2243  * Map's `crs` option.
2244  */
2245
2246 L.CRS.EPSG3857 = L.extend({}, L.CRS.Earth, {
2247         code: 'EPSG:3857',
2248         projection: L.Projection.SphericalMercator,
2249
2250         transformation: (function () {
2251                 var scale = 0.5 / (Math.PI * L.Projection.SphericalMercator.R);
2252                 return new L.Transformation(scale, 0.5, -scale, 0.5);
2253         }())
2254 });
2255
2256 L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2257         code: 'EPSG:900913'
2258 });
2259
2260
2261
2262 /*
2263  * @namespace CRS
2264  * @crs L.CRS.EPSG4326
2265  *
2266  * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
2267  *
2268  * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
2269  * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
2270  * with this CRS, ensure that there are two 256x256 pixel tiles covering the
2271  * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
2272  * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
2273  */
2274
2275 L.CRS.EPSG4326 = L.extend({}, L.CRS.Earth, {
2276         code: 'EPSG:4326',
2277         projection: L.Projection.LonLat,
2278         transformation: new L.Transformation(1 / 180, 1, -1 / 180, 0.5)
2279 });
2280
2281
2282
2283 /*
2284  * @class Map
2285  * @aka L.Map
2286  * @inherits Evented
2287  *
2288  * The central class of the API — it is used to create a map on a page and manipulate it.
2289  *
2290  * @example
2291  *
2292  * ```js
2293  * // initialize the map on the "map" div with a given center and zoom
2294  * var map = L.map('map', {
2295  *      center: [51.505, -0.09],
2296  *      zoom: 13
2297  * });
2298  * ```
2299  *
2300  */
2301
2302 L.Map = L.Evented.extend({
2303
2304         options: {
2305                 // @section Map State Options
2306                 // @option crs: CRS = L.CRS.EPSG3857
2307                 // The [Coordinate Reference System](#crs) to use. Don't change this if you're not
2308                 // sure what it means.
2309                 crs: L.CRS.EPSG3857,
2310
2311                 // @option center: LatLng = undefined
2312                 // Initial geographic center of the map
2313                 center: undefined,
2314
2315                 // @option zoom: Number = undefined
2316                 // Initial map zoom level
2317                 zoom: undefined,
2318
2319                 // @option minZoom: Number = undefined
2320                 // Minimum zoom level of the map. Overrides any `minZoom` option set on map layers.
2321                 minZoom: undefined,
2322
2323                 // @option maxZoom: Number = undefined
2324                 // Maximum zoom level of the map. Overrides any `maxZoom` option set on map layers.
2325                 maxZoom: undefined,
2326
2327                 // @option layers: Layer[] = []
2328                 // Array of layers that will be added to the map initially
2329                 layers: [],
2330
2331                 // @option maxBounds: LatLngBounds = null
2332                 // When this option is set, the map restricts the view to the given
2333                 // geographical bounds, bouncing the user back if the user tries to pan
2334                 // outside the view. To set the restriction dynamically, use
2335                 // [`setMaxBounds`](#map-setmaxbounds) method.
2336                 maxBounds: undefined,
2337
2338                 // @option renderer: Renderer = *
2339                 // The default method for drawing vector layers on the map. `L.SVG`
2340                 // or `L.Canvas` by default depending on browser support.
2341                 renderer: undefined,
2342
2343
2344                 // @section Animation Options
2345                 // @option zoomAnimation: Boolean = true
2346                 // Whether the map zoom animation is enabled. By default it's enabled
2347                 // in all browsers that support CSS3 Transitions except Android.
2348                 zoomAnimation: true,
2349
2350                 // @option zoomAnimationThreshold: Number = 4
2351                 // Won't animate zoom if the zoom difference exceeds this value.
2352                 zoomAnimationThreshold: 4,
2353
2354                 // @option fadeAnimation: Boolean = true
2355                 // Whether the tile fade animation is enabled. By default it's enabled
2356                 // in all browsers that support CSS3 Transitions except Android.
2357                 fadeAnimation: true,
2358
2359                 // @option markerZoomAnimation: Boolean = true
2360                 // Whether markers animate their zoom with the zoom animation, if disabled
2361                 // they will disappear for the length of the animation. By default it's
2362                 // enabled in all browsers that support CSS3 Transitions except Android.
2363                 markerZoomAnimation: true,
2364
2365                 // @option transform3DLimit: Number = 2^23
2366                 // Defines the maximum size of a CSS translation transform. The default
2367                 // value should not be changed unless a web browser positions layers in
2368                 // the wrong place after doing a large `panBy`.
2369                 transform3DLimit: 8388608, // Precision limit of a 32-bit float
2370
2371                 // @section Interaction Options
2372                 // @option zoomSnap: Number = 1
2373                 // Forces the map's zoom level to always be a multiple of this, particularly
2374                 // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
2375                 // By default, the zoom level snaps to the nearest integer; lower values
2376                 // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
2377                 // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
2378                 zoomSnap: 1,
2379
2380                 // @option zoomDelta: Number = 1
2381                 // Controls how much the map's zoom level will change after a
2382                 // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
2383                 // or `-` on the keyboard, or using the [zoom controls](#control-zoom).
2384                 // Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
2385                 zoomDelta: 1,
2386
2387                 // @option trackResize: Boolean = true
2388                 // Whether the map automatically handles browser window resize to update itself.
2389                 trackResize: true
2390         },
2391
2392         initialize: function (id, options) { // (HTMLElement or String, Object)
2393                 options = L.setOptions(this, options);
2394
2395                 this._initContainer(id);
2396                 this._initLayout();
2397
2398                 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2399                 this._onResize = L.bind(this._onResize, this);
2400
2401                 this._initEvents();
2402
2403                 if (options.maxBounds) {
2404                         this.setMaxBounds(options.maxBounds);
2405                 }
2406
2407                 if (options.zoom !== undefined) {
2408                         this._zoom = this._limitZoom(options.zoom);
2409                 }
2410
2411                 if (options.center && options.zoom !== undefined) {
2412                         this.setView(L.latLng(options.center), options.zoom, {reset: true});
2413                 }
2414
2415                 this._handlers = [];
2416                 this._layers = {};
2417                 this._zoomBoundLayers = {};
2418                 this._sizeChanged = true;
2419
2420                 this.callInitHooks();
2421
2422                 // don't animate on browsers without hardware-accelerated transitions or old Android/Opera
2423                 this._zoomAnimated = L.DomUtil.TRANSITION && L.Browser.any3d && !L.Browser.mobileOpera &&
2424                                 this.options.zoomAnimation;
2425
2426                 // zoom transitions run with the same duration for all layers, so if one of transitionend events
2427                 // happens after starting zoom animation (propagating to the map pane), we know that it ended globally
2428                 if (this._zoomAnimated) {
2429                         this._createAnimProxy();
2430                         L.DomEvent.on(this._proxy, L.DomUtil.TRANSITION_END, this._catchTransitionEnd, this);
2431                 }
2432
2433                 this._addLayers(this.options.layers);
2434         },
2435
2436
2437         // @section Methods for modifying map state
2438
2439         // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
2440         // Sets the view of the map (geographical center and zoom) with the given
2441         // animation options.
2442         setView: function (center, zoom, options) {
2443
2444                 zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
2445                 center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds);
2446                 options = options || {};
2447
2448                 this._stop();
2449
2450                 if (this._loaded && !options.reset && options !== true) {
2451
2452                         if (options.animate !== undefined) {
2453                                 options.zoom = L.extend({animate: options.animate}, options.zoom);
2454                                 options.pan = L.extend({animate: options.animate, duration: options.duration}, options.pan);
2455                         }
2456
2457                         // try animating pan or zoom
2458                         var moved = (this._zoom !== zoom) ?
2459                                 this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
2460                                 this._tryAnimatedPan(center, options.pan);
2461
2462                         if (moved) {
2463                                 // prevent resize handler call, the view will refresh after animation anyway
2464                                 clearTimeout(this._sizeTimer);
2465                                 return this;
2466                         }
2467                 }
2468
2469                 // animation didn't start, just reset the map view
2470                 this._resetView(center, zoom);
2471
2472                 return this;
2473         },
2474
2475         // @method setZoom(zoom: Number, options: Zoom/pan options): this
2476         // Sets the zoom of the map.
2477         setZoom: function (zoom, options) {
2478                 if (!this._loaded) {
2479                         this._zoom = zoom;
2480                         return this;
2481                 }
2482                 return this.setView(this.getCenter(), zoom, {zoom: options});
2483         },
2484
2485         // @method zoomIn(delta?: Number, options?: Zoom options): this
2486         // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2487         zoomIn: function (delta, options) {
2488                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2489                 return this.setZoom(this._zoom + delta, options);
2490         },
2491
2492         // @method zoomOut(delta?: Number, options?: Zoom options): this
2493         // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
2494         zoomOut: function (delta, options) {
2495                 delta = delta || (L.Browser.any3d ? this.options.zoomDelta : 1);
2496                 return this.setZoom(this._zoom - delta, options);
2497         },
2498
2499         // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
2500         // Zooms the map while keeping a specified geographical point on the map
2501         // stationary (e.g. used internally for scroll zoom and double-click zoom).
2502         // @alternative
2503         // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
2504         // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
2505         setZoomAround: function (latlng, zoom, options) {
2506                 var scale = this.getZoomScale(zoom),
2507                     viewHalf = this.getSize().divideBy(2),
2508                     containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
2509
2510                     centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
2511                     newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
2512
2513                 return this.setView(newCenter, zoom, {zoom: options});
2514         },
2515
2516         _getBoundsCenterZoom: function (bounds, options) {
2517
2518                 options = options || {};
2519                 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
2520
2521                 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
2522                     paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
2523
2524                     zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
2525
2526                 zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;
2527
2528                 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
2529
2530                     swPoint = this.project(bounds.getSouthWest(), zoom),
2531                     nePoint = this.project(bounds.getNorthEast(), zoom),
2532                     center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
2533
2534                 return {
2535                         center: center,
2536                         zoom: zoom
2537                 };
2538         },
2539
2540         // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
2541         // Sets a map view that contains the given geographical bounds with the
2542         // maximum zoom level possible.
2543         fitBounds: function (bounds, options) {
2544
2545                 bounds = L.latLngBounds(bounds);
2546
2547                 if (!bounds.isValid()) {
2548                         throw new Error('Bounds are not valid.');
2549                 }
2550
2551                 var target = this._getBoundsCenterZoom(bounds, options);
2552                 return this.setView(target.center, target.zoom, options);
2553         },
2554
2555         // @method fitWorld(options?: fitBounds options): this
2556         // Sets a map view that mostly contains the whole world with the maximum
2557         // zoom level possible.
2558         fitWorld: function (options) {
2559                 return this.fitBounds([[-90, -180], [90, 180]], options);
2560         },
2561
2562         // @method panTo(latlng: LatLng, options?: Pan options): this
2563         // Pans the map to a given center.
2564         panTo: function (center, options) { // (LatLng)
2565                 return this.setView(center, this._zoom, {pan: options});
2566         },
2567
2568         // @method panBy(offset: Point): this
2569         // Pans the map by a given number of pixels (animated).
2570         panBy: function (offset, options) {
2571                 offset = L.point(offset).round();
2572                 options = options || {};
2573
2574                 if (!offset.x && !offset.y) {
2575                         return this.fire('moveend');
2576                 }
2577                 // If we pan too far, Chrome gets issues with tiles
2578                 // and makes them disappear or appear in the wrong place (slightly offset) #2602
2579                 if (options.animate !== true && !this.getSize().contains(offset)) {
2580                         this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
2581                         return this;
2582                 }
2583
2584                 if (!this._panAnim) {
2585                         this._panAnim = new L.PosAnimation();
2586
2587                         this._panAnim.on({
2588                                 'step': this._onPanTransitionStep,
2589                                 'end': this._onPanTransitionEnd
2590                         }, this);
2591                 }
2592
2593                 // don't fire movestart if animating inertia
2594                 if (!options.noMoveStart) {
2595                         this.fire('movestart');
2596                 }
2597
2598                 // animate pan unless animate: false specified
2599                 if (options.animate !== false) {
2600                         L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim');
2601
2602                         var newPos = this._getMapPanePos().subtract(offset).round();
2603                         this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
2604                 } else {
2605                         this._rawPanBy(offset);
2606                         this.fire('move').fire('moveend');
2607                 }
2608
2609                 return this;
2610         },
2611
2612         // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
2613         // Sets the view of the map (geographical center and zoom) performing a smooth
2614         // pan-zoom animation.
2615         flyTo: function (targetCenter, targetZoom, options) {
2616
2617                 options = options || {};
2618                 if (options.animate === false || !L.Browser.any3d) {
2619                         return this.setView(targetCenter, targetZoom, options);
2620                 }
2621
2622                 this._stop();
2623
2624                 var from = this.project(this.getCenter()),
2625                     to = this.project(targetCenter),
2626                     size = this.getSize(),
2627                     startZoom = this._zoom;
2628
2629                 targetCenter = L.latLng(targetCenter);
2630                 targetZoom = targetZoom === undefined ? startZoom : targetZoom;
2631
2632                 var w0 = Math.max(size.x, size.y),
2633                     w1 = w0 * this.getZoomScale(startZoom, targetZoom),
2634                     u1 = (to.distanceTo(from)) || 1,
2635                     rho = 1.42,
2636                     rho2 = rho * rho;
2637
2638                 function r(i) {
2639                         var s1 = i ? -1 : 1,
2640                             s2 = i ? w1 : w0,
2641                             t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
2642                             b1 = 2 * s2 * rho2 * u1,
2643                             b = t1 / b1,
2644                             sq = Math.sqrt(b * b + 1) - b;
2645
2646                             // workaround for floating point precision bug when sq = 0, log = -Infinite,
2647                             // thus triggering an infinite loop in flyTo
2648                             var log = sq < 0.000000001 ? -18 : Math.log(sq);
2649
2650                         return log;
2651                 }
2652
2653                 function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
2654                 function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
2655                 function tanh(n) { return sinh(n) / cosh(n); }
2656
2657                 var r0 = r(0);
2658
2659                 function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
2660                 function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }
2661
2662                 function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }
2663
2664                 var start = Date.now(),
2665                     S = (r(1) - r0) / rho,
2666                     duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;
2667
2668                 function frame() {
2669                         var t = (Date.now() - start) / duration,
2670                             s = easeOut(t) * S;
2671
2672                         if (t <= 1) {
2673                                 this._flyToFrame = L.Util.requestAnimFrame(frame, this);
2674
2675                                 this._move(
2676                                         this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
2677                                         this.getScaleZoom(w0 / w(s), startZoom),
2678                                         {flyTo: true});
2679
2680                         } else {
2681                                 this
2682                                         ._move(targetCenter, targetZoom)
2683                                         ._moveEnd(true);
2684                         }
2685                 }
2686
2687                 this._moveStart(true);
2688
2689                 frame.call(this);
2690                 return this;
2691         },
2692
2693         // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
2694         // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
2695         // but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
2696         flyToBounds: function (bounds, options) {
2697                 var target = this._getBoundsCenterZoom(bounds, options);
2698                 return this.flyTo(target.center, target.zoom, options);
2699         },
2700
2701         // @method setMaxBounds(bounds: Bounds): this
2702         // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
2703         setMaxBounds: function (bounds) {
2704                 bounds = L.latLngBounds(bounds);
2705
2706                 if (!bounds.isValid()) {
2707                         this.options.maxBounds = null;
2708                         return this.off('moveend', this._panInsideMaxBounds);
2709                 } else if (this.options.maxBounds) {
2710                         this.off('moveend', this._panInsideMaxBounds);
2711                 }
2712
2713                 this.options.maxBounds = bounds;
2714
2715                 if (this._loaded) {
2716                         this._panInsideMaxBounds();
2717                 }
2718
2719                 return this.on('moveend', this._panInsideMaxBounds);
2720         },
2721
2722         // @method setMinZoom(zoom: Number): this
2723         // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
2724         setMinZoom: function (zoom) {
2725                 this.options.minZoom = zoom;
2726
2727                 if (this._loaded && this.getZoom() < this.options.minZoom) {
2728                         return this.setZoom(zoom);
2729                 }
2730
2731                 return this;
2732         },
2733
2734         // @method setMaxZoom(zoom: Number): this
2735         // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
2736         setMaxZoom: function (zoom) {
2737                 this.options.maxZoom = zoom;
2738
2739                 if (this._loaded && (this.getZoom() > this.options.maxZoom)) {
2740                         return this.setZoom(zoom);
2741                 }
2742
2743                 return this;
2744         },
2745
2746         // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
2747         // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
2748         panInsideBounds: function (bounds, options) {
2749                 this._enforcingBounds = true;
2750                 var center = this.getCenter(),
2751                     newCenter = this._limitCenter(center, this._zoom, L.latLngBounds(bounds));
2752
2753                 if (!center.equals(newCenter)) {
2754                         this.panTo(newCenter, options);
2755                 }
2756
2757                 this._enforcingBounds = false;
2758                 return this;
2759         },
2760
2761         // @method invalidateSize(options: Zoom/Pan options): this
2762         // Checks if the map container size changed and updates the map if so —
2763         // call it after you've changed the map size dynamically, also animating
2764         // pan by default. If `options.pan` is `false`, panning will not occur.
2765         // If `options.debounceMoveend` is `true`, it will delay `moveend` event so
2766         // that it doesn't happen often even if the method is called many
2767         // times in a row.
2768
2769         // @alternative
2770         // @method invalidateSize(animate: Boolean): this
2771         // Checks if the map container size changed and updates the map if so —
2772         // call it after you've changed the map size dynamically, also animating
2773         // pan by default.
2774         invalidateSize: function (options) {
2775                 if (!this._loaded) { return this; }
2776
2777                 options = L.extend({
2778                         animate: false,
2779                         pan: true
2780                 }, options === true ? {animate: true} : options);
2781
2782                 var oldSize = this.getSize();
2783                 this._sizeChanged = true;
2784                 this._lastCenter = null;
2785
2786                 var newSize = this.getSize(),
2787                     oldCenter = oldSize.divideBy(2).round(),
2788                     newCenter = newSize.divideBy(2).round(),
2789                     offset = oldCenter.subtract(newCenter);
2790
2791                 if (!offset.x && !offset.y) { return this; }
2792
2793                 if (options.animate && options.pan) {
2794                         this.panBy(offset);
2795
2796                 } else {
2797                         if (options.pan) {
2798                                 this._rawPanBy(offset);
2799                         }
2800
2801                         this.fire('move');
2802
2803                         if (options.debounceMoveend) {
2804                                 clearTimeout(this._sizeTimer);
2805                                 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
2806                         } else {
2807                                 this.fire('moveend');
2808                         }
2809                 }
2810
2811                 // @section Map state change events
2812                 // @event resize: ResizeEvent
2813                 // Fired when the map is resized.
2814                 return this.fire('resize', {
2815                         oldSize: oldSize,
2816                         newSize: newSize
2817                 });
2818         },
2819
2820         // @section Methods for modifying map state
2821         // @method stop(): this
2822         // Stops the currently running `panTo` or `flyTo` animation, if any.
2823         stop: function () {
2824                 this.setZoom(this._limitZoom(this._zoom));
2825                 if (!this.options.zoomSnap) {
2826                         this.fire('viewreset');
2827                 }
2828                 return this._stop();
2829         },
2830
2831         // @section Geolocation methods
2832         // @method locate(options?: Locate options): this
2833         // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
2834         // event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
2835         // and optionally sets the map view to the user's location with respect to
2836         // detection accuracy (or to the world view if geolocation failed).
2837         // Note that, if your page doesn't use HTTPS, this method will fail in
2838         // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
2839         // See `Locate options` for more details.
2840         locate: function (options) {
2841
2842                 options = this._locateOptions = L.extend({
2843                         timeout: 10000,
2844                         watch: false
2845                         // setView: false
2846                         // maxZoom: <Number>
2847                         // maximumAge: 0
2848                         // enableHighAccuracy: false
2849                 }, options);
2850
2851                 if (!('geolocation' in navigator)) {
2852                         this._handleGeolocationError({
2853                                 code: 0,
2854                                 message: 'Geolocation not supported.'
2855                         });
2856                         return this;
2857                 }
2858
2859                 var onResponse = L.bind(this._handleGeolocationResponse, this),
2860                     onError = L.bind(this._handleGeolocationError, this);
2861
2862                 if (options.watch) {
2863                         this._locationWatchId =
2864                                 navigator.geolocation.watchPosition(onResponse, onError, options);
2865                 } else {
2866                         navigator.geolocation.getCurrentPosition(onResponse, onError, options);
2867                 }
2868                 return this;
2869         },
2870
2871         // @method stopLocate(): this
2872         // Stops watching location previously initiated by `map.locate({watch: true})`
2873         // and aborts resetting the map view if map.locate was called with
2874         // `{setView: true}`.
2875         stopLocate: function () {
2876                 if (navigator.geolocation && navigator.geolocation.clearWatch) {
2877                         navigator.geolocation.clearWatch(this._locationWatchId);
2878                 }
2879                 if (this._locateOptions) {
2880                         this._locateOptions.setView = false;
2881                 }
2882                 return this;
2883         },
2884
2885         _handleGeolocationError: function (error) {
2886                 var c = error.code,
2887                     message = error.message ||
2888                             (c === 1 ? 'permission denied' :
2889                             (c === 2 ? 'position unavailable' : 'timeout'));
2890
2891                 if (this._locateOptions.setView && !this._loaded) {
2892                         this.fitWorld();
2893                 }
2894
2895                 // @section Location events
2896                 // @event locationerror: ErrorEvent
2897                 // Fired when geolocation (using the [`locate`](#map-locate) method) failed.
2898                 this.fire('locationerror', {
2899                         code: c,
2900                         message: 'Geolocation error: ' + message + '.'
2901                 });
2902         },
2903
2904         _handleGeolocationResponse: function (pos) {
2905                 var lat = pos.coords.latitude,
2906                     lng = pos.coords.longitude,
2907                     latlng = new L.LatLng(lat, lng),
2908                     bounds = latlng.toBounds(pos.coords.accuracy),
2909                     options = this._locateOptions;
2910
2911                 if (options.setView) {
2912                         var zoom = this.getBoundsZoom(bounds);
2913                         this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
2914                 }
2915
2916                 var data = {
2917                         latlng: latlng,
2918                         bounds: bounds,
2919                         timestamp: pos.timestamp
2920                 };
2921
2922                 for (var i in pos.coords) {
2923                         if (typeof pos.coords[i] === 'number') {
2924                                 data[i] = pos.coords[i];
2925                         }
2926                 }
2927
2928                 // @event locationfound: LocationEvent
2929                 // Fired when geolocation (using the [`locate`](#map-locate) method)
2930                 // went successfully.
2931                 this.fire('locationfound', data);
2932         },
2933
2934         // TODO handler.addTo
2935         // TODO Appropiate docs section?
2936         // @section Other Methods
2937         // @method addHandler(name: String, HandlerClass: Function): this
2938         // Adds a new `Handler` to the map, given its name and constructor function.
2939         addHandler: function (name, HandlerClass) {
2940                 if (!HandlerClass) { return this; }
2941
2942                 var handler = this[name] = new HandlerClass(this);
2943
2944                 this._handlers.push(handler);
2945
2946                 if (this.options[name]) {
2947                         handler.enable();
2948                 }
2949
2950                 return this;
2951         },
2952
2953         // @method remove(): this
2954         // Destroys the map and clears all related event listeners.
2955         remove: function () {
2956
2957                 this._initEvents(true);
2958
2959                 if (this._containerId !== this._container._leaflet_id) {
2960                         throw new Error('Map container is being reused by another instance');
2961                 }
2962
2963                 try {
2964                         // throws error in IE6-8
2965                         delete this._container._leaflet_id;
2966                         delete this._containerId;
2967                 } catch (e) {
2968                         /*eslint-disable */
2969                         this._container._leaflet_id = undefined;
2970                         /*eslint-enable */
2971                         this._containerId = undefined;
2972                 }
2973
2974                 L.DomUtil.remove(this._mapPane);
2975
2976                 if (this._clearControlPos) {
2977                         this._clearControlPos();
2978                 }
2979
2980                 this._clearHandlers();
2981
2982                 if (this._loaded) {
2983                         // @section Map state change events
2984                         // @event unload: Event
2985                         // Fired when the map is destroyed with [remove](#map-remove) method.
2986                         this.fire('unload');
2987                 }
2988
2989                 for (var i in this._layers) {
2990                         this._layers[i].remove();
2991                 }
2992
2993                 return this;
2994         },
2995
2996         // @section Other Methods
2997         // @method createPane(name: String, container?: HTMLElement): HTMLElement
2998         // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
2999         // then returns it. The pane is created as a children of `container`, or
3000         // as a children of the main map pane if not set.
3001         createPane: function (name, container) {
3002                 var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
3003                     pane = L.DomUtil.create('div', className, container || this._mapPane);
3004
3005                 if (name) {
3006                         this._panes[name] = pane;
3007                 }
3008                 return pane;
3009         },
3010
3011         // @section Methods for Getting Map State
3012
3013         // @method getCenter(): LatLng
3014         // Returns the geographical center of the map view
3015         getCenter: function () {
3016                 this._checkIfLoaded();
3017
3018                 if (this._lastCenter && !this._moved()) {
3019                         return this._lastCenter;
3020                 }
3021                 return this.layerPointToLatLng(this._getCenterLayerPoint());
3022         },
3023
3024         // @method getZoom(): Number
3025         // Returns the current zoom level of the map view
3026         getZoom: function () {
3027                 return this._zoom;
3028         },
3029
3030         // @method getBounds(): LatLngBounds
3031         // Returns the geographical bounds visible in the current map view
3032         getBounds: function () {
3033                 var bounds = this.getPixelBounds(),
3034                     sw = this.unproject(bounds.getBottomLeft()),
3035                     ne = this.unproject(bounds.getTopRight());
3036
3037                 return new L.LatLngBounds(sw, ne);
3038         },
3039
3040         // @method getMinZoom(): Number
3041         // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
3042         getMinZoom: function () {
3043                 return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
3044         },
3045
3046         // @method getMaxZoom(): Number
3047         // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
3048         getMaxZoom: function () {
3049                 return this.options.maxZoom === undefined ?
3050                         (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3051                         this.options.maxZoom;
3052         },
3053
3054         // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean): Number
3055         // Returns the maximum zoom level on which the given bounds fit to the map
3056         // view in its entirety. If `inside` (optional) is set to `true`, the method
3057         // instead returns the minimum zoom level on which the map view fits into
3058         // the given bounds in its entirety.
3059         getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3060                 bounds = L.latLngBounds(bounds);
3061                 padding = L.point(padding || [0, 0]);
3062
3063                 var zoom = this.getZoom() || 0,
3064                     min = this.getMinZoom(),
3065                     max = this.getMaxZoom(),
3066                     nw = bounds.getNorthWest(),
3067                     se = bounds.getSouthEast(),
3068                     size = this.getSize().subtract(padding),
3069                     boundsSize = L.bounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
3070                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3071
3072                 var scale = Math.min(size.x / boundsSize.x, size.y / boundsSize.y);
3073                 zoom = this.getScaleZoom(scale, zoom);
3074
3075                 if (snap) {
3076                         zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
3077                         zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
3078                 }
3079
3080                 return Math.max(min, Math.min(max, zoom));
3081         },
3082
3083         // @method getSize(): Point
3084         // Returns the current size of the map container (in pixels).
3085         getSize: function () {
3086                 if (!this._size || this._sizeChanged) {
3087                         this._size = new L.Point(
3088                                 this._container.clientWidth || 0,
3089                                 this._container.clientHeight || 0);
3090
3091                         this._sizeChanged = false;
3092                 }
3093                 return this._size.clone();
3094         },
3095
3096         // @method getPixelBounds(): Bounds
3097         // Returns the bounds of the current map view in projected pixel
3098         // coordinates (sometimes useful in layer and overlay implementations).
3099         getPixelBounds: function (center, zoom) {
3100                 var topLeftPoint = this._getTopLeftPoint(center, zoom);
3101                 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3102         },
3103
3104         // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
3105         // the map pane? "left point of the map layer" can be confusing, specially
3106         // since there can be negative offsets.
3107         // @method getPixelOrigin(): Point
3108         // Returns the projected pixel coordinates of the top left point of
3109         // the map layer (useful in custom layer and overlay implementations).
3110         getPixelOrigin: function () {
3111                 this._checkIfLoaded();
3112                 return this._pixelOrigin;
3113         },
3114
3115         // @method getPixelWorldBounds(zoom?: Number): Bounds
3116         // Returns the world's bounds in pixel coordinates for zoom level `zoom`.
3117         // If `zoom` is omitted, the map's current zoom level is used.
3118         getPixelWorldBounds: function (zoom) {
3119                 return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
3120         },
3121
3122         // @section Other Methods
3123
3124         // @method getPane(pane: String|HTMLElement): HTMLElement
3125         // Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
3126         getPane: function (pane) {
3127                 return typeof pane === 'string' ? this._panes[pane] : pane;
3128         },
3129
3130         // @method getPanes(): Object
3131         // Returns a plain object containing the names of all [panes](#map-pane) as keys and
3132         // the panes as values.
3133         getPanes: function () {
3134                 return this._panes;
3135         },
3136
3137         // @method getContainer: HTMLElement
3138         // Returns the HTML element that contains the map.
3139         getContainer: function () {
3140                 return this._container;
3141         },
3142
3143
3144         // @section Conversion Methods
3145
3146         // @method getZoomScale(toZoom: Number, fromZoom: Number): Number
3147         // Returns the scale factor to be applied to a map transition from zoom level
3148         // `fromZoom` to `toZoom`. Used internally to help with zoom animations.
3149         getZoomScale: function (toZoom, fromZoom) {
3150                 // TODO replace with universal implementation after refactoring projections
3151                 var crs = this.options.crs;
3152                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3153                 return crs.scale(toZoom) / crs.scale(fromZoom);
3154         },
3155
3156         // @method getScaleZoom(scale: Number, fromZoom: Number): Number
3157         // Returns the zoom level that the map would end up at, if it is at `fromZoom`
3158         // level and everything is scaled by a factor of `scale`. Inverse of
3159         // [`getZoomScale`](#map-getZoomScale).
3160         getScaleZoom: function (scale, fromZoom) {
3161                 var crs = this.options.crs;
3162                 fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
3163                 var zoom = crs.zoom(scale * crs.scale(fromZoom));
3164                 return isNaN(zoom) ? Infinity : zoom;
3165         },
3166
3167         // @method project(latlng: LatLng, zoom: Number): Point
3168         // Projects a geographical coordinate `LatLng` according to the projection
3169         // of the map's CRS, then scales it according to `zoom` and the CRS's
3170         // `Transformation`. The result is pixel coordinate relative to
3171         // the CRS origin.
3172         project: function (latlng, zoom) {
3173                 zoom = zoom === undefined ? this._zoom : zoom;
3174                 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
3175         },
3176
3177         // @method unproject(point: Point, zoom: Number): LatLng
3178         // Inverse of [`project`](#map-project).
3179         unproject: function (point, zoom) {
3180                 zoom = zoom === undefined ? this._zoom : zoom;
3181                 return this.options.crs.pointToLatLng(L.point(point), zoom);
3182         },
3183
3184         // @method layerPointToLatLng(point: Point): LatLng
3185         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3186         // returns the corresponding geographical coordinate (for the current zoom level).
3187         layerPointToLatLng: function (point) {
3188                 var projectedPoint = L.point(point).add(this.getPixelOrigin());
3189                 return this.unproject(projectedPoint);
3190         },
3191
3192         // @method latLngToLayerPoint(latlng: LatLng): Point
3193         // Given a geographical coordinate, returns the corresponding pixel coordinate
3194         // relative to the [origin pixel](#map-getpixelorigin).
3195         latLngToLayerPoint: function (latlng) {
3196                 var projectedPoint = this.project(L.latLng(latlng))._round();
3197                 return projectedPoint._subtract(this.getPixelOrigin());
3198         },
3199
3200         // @method wrapLatLng(latlng: LatLng): LatLng
3201         // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
3202         // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
3203         // CRS's bounds.
3204         // By default this means longitude is wrapped around the dateline so its
3205         // value is between -180 and +180 degrees.
3206         wrapLatLng: function (latlng) {
3207                 return this.options.crs.wrapLatLng(L.latLng(latlng));
3208         },
3209
3210         // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
3211         // Returns a `LatLngBounds` with the same size as the given one, ensuring that
3212         // its center is within the CRS's bounds.
3213         // By default this means the center longitude is wrapped around the dateline so its
3214         // value is between -180 and +180 degrees, and the majority of the bounds
3215         // overlaps the CRS's bounds.
3216         wrapLatLngBounds: function (latlng) {
3217                 return this.options.crs.wrapLatLngBounds(L.latLngBounds(latlng));
3218         },
3219
3220         // @method distance(latlng1: LatLng, latlng2: LatLng): Number
3221         // Returns the distance between two geographical coordinates according to
3222         // the map's CRS. By default this measures distance in meters.
3223         distance: function (latlng1, latlng2) {
3224                 return this.options.crs.distance(L.latLng(latlng1), L.latLng(latlng2));
3225         },
3226
3227         // @method containerPointToLayerPoint(point: Point): Point
3228         // Given a pixel coordinate relative to the map container, returns the corresponding
3229         // pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
3230         containerPointToLayerPoint: function (point) { // (Point)
3231                 return L.point(point).subtract(this._getMapPanePos());
3232         },
3233
3234         // @method layerPointToContainerPoint(point: Point): Point
3235         // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
3236         // returns the corresponding pixel coordinate relative to the map container.
3237         layerPointToContainerPoint: function (point) { // (Point)
3238                 return L.point(point).add(this._getMapPanePos());
3239         },
3240
3241         // @method containerPointToLatLng(point: Point): LatLng
3242         // Given a pixel coordinate relative to the map container, returns
3243         // the corresponding geographical coordinate (for the current zoom level).
3244         containerPointToLatLng: function (point) {
3245                 var layerPoint = this.containerPointToLayerPoint(L.point(point));
3246                 return this.layerPointToLatLng(layerPoint);
3247         },
3248
3249         // @method latLngToContainerPoint(latlng: LatLng): Point
3250         // Given a geographical coordinate, returns the corresponding pixel coordinate
3251         // relative to the map container.
3252         latLngToContainerPoint: function (latlng) {
3253                 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
3254         },
3255
3256         // @method mouseEventToContainerPoint(ev: MouseEvent): Point
3257         // Given a MouseEvent object, returns the pixel coordinate relative to the
3258         // map container where the event took place.
3259         mouseEventToContainerPoint: function (e) {
3260                 return L.DomEvent.getMousePosition(e, this._container);
3261         },
3262
3263         // @method mouseEventToLayerPoint(ev: MouseEvent): Point
3264         // Given a MouseEvent object, returns the pixel coordinate relative to
3265         // the [origin pixel](#map-getpixelorigin) where the event took place.
3266         mouseEventToLayerPoint: function (e) {
3267                 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3268         },
3269
3270         // @method mouseEventToLatLng(ev: MouseEvent): LatLng
3271         // Given a MouseEvent object, returns geographical coordinate where the
3272         // event took place.
3273         mouseEventToLatLng: function (e) { // (MouseEvent)
3274                 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3275         },
3276
3277
3278         // map initialization methods
3279
3280         _initContainer: function (id) {
3281                 var container = this._container = L.DomUtil.get(id);
3282
3283                 if (!container) {
3284                         throw new Error('Map container not found.');
3285                 } else if (container._leaflet_id) {
3286                         throw new Error('Map container is already initialized.');
3287                 }
3288
3289                 L.DomEvent.addListener(container, 'scroll', this._onScroll, this);
3290                 this._containerId = L.Util.stamp(container);
3291         },
3292
3293         _initLayout: function () {
3294                 var container = this._container;
3295
3296                 this._fadeAnimated = this.options.fadeAnimation && L.Browser.any3d;
3297
3298                 L.DomUtil.addClass(container, 'leaflet-container' +
3299                         (L.Browser.touch ? ' leaflet-touch' : '') +
3300                         (L.Browser.retina ? ' leaflet-retina' : '') +
3301                         (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
3302                         (L.Browser.safari ? ' leaflet-safari' : '') +
3303                         (this._fadeAnimated ? ' leaflet-fade-anim' : ''));
3304
3305                 var position = L.DomUtil.getStyle(container, 'position');
3306
3307                 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3308                         container.style.position = 'relative';
3309                 }
3310
3311                 this._initPanes();
3312
3313                 if (this._initControlPos) {
3314                         this._initControlPos();
3315                 }
3316         },
3317
3318         _initPanes: function () {
3319                 var panes = this._panes = {};
3320                 this._paneRenderers = {};
3321
3322                 // @section
3323                 //
3324                 // Panes are DOM elements used to control the ordering of layers on the map. You
3325                 // can access panes with [`map.getPane`](#map-getpane) or
3326                 // [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
3327                 // [`map.createPane`](#map-createpane) method.
3328                 //
3329                 // Every map has the following default panes that differ only in zIndex.
3330                 //
3331                 // @pane mapPane: HTMLElement = 'auto'
3332                 // Pane that contains all other map panes
3333
3334                 this._mapPane = this.createPane('mapPane', this._container);
3335                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3336
3337                 // @pane tilePane: HTMLElement = 200
3338                 // Pane for `GridLayer`s and `TileLayer`s
3339                 this.createPane('tilePane');
3340                 // @pane overlayPane: HTMLElement = 400
3341                 // Pane for vector overlays (`Path`s), like `Polyline`s and `Polygon`s
3342                 this.createPane('shadowPane');
3343                 // @pane shadowPane: HTMLElement = 500
3344                 // Pane for overlay shadows (e.g. `Marker` shadows)
3345                 this.createPane('overlayPane');
3346                 // @pane markerPane: HTMLElement = 600
3347                 // Pane for `Icon`s of `Marker`s
3348                 this.createPane('markerPane');
3349                 // @pane tooltipPane: HTMLElement = 650
3350                 // Pane for tooltip.
3351                 this.createPane('tooltipPane');
3352                 // @pane popupPane: HTMLElement = 700
3353                 // Pane for `Popup`s.
3354                 this.createPane('popupPane');
3355
3356                 if (!this.options.markerZoomAnimation) {
3357                         L.DomUtil.addClass(panes.markerPane, 'leaflet-zoom-hide');
3358                         L.DomUtil.addClass(panes.shadowPane, 'leaflet-zoom-hide');
3359                 }
3360         },
3361
3362
3363         // private methods that modify map state
3364
3365         // @section Map state change events
3366         _resetView: function (center, zoom) {
3367                 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3368
3369                 var loading = !this._loaded;
3370                 this._loaded = true;
3371                 zoom = this._limitZoom(zoom);
3372
3373                 this.fire('viewprereset');
3374
3375                 var zoomChanged = this._zoom !== zoom;
3376                 this
3377                         ._moveStart(zoomChanged)
3378                         ._move(center, zoom)
3379                         ._moveEnd(zoomChanged);
3380
3381                 // @event viewreset: Event
3382                 // Fired when the map needs to redraw its content (this usually happens
3383                 // on map zoom or load). Very useful for creating custom overlays.
3384                 this.fire('viewreset');
3385
3386                 // @event load: Event
3387                 // Fired when the map is initialized (when its center and zoom are set
3388                 // for the first time).
3389                 if (loading) {
3390                         this.fire('load');
3391                 }
3392         },
3393
3394         _moveStart: function (zoomChanged) {
3395                 // @event zoomstart: Event
3396                 // Fired when the map zoom is about to change (e.g. before zoom animation).
3397                 // @event movestart: Event
3398                 // Fired when the view of the map starts changing (e.g. user starts dragging the map).
3399                 if (zoomChanged) {
3400                         this.fire('zoomstart');
3401                 }
3402                 return this.fire('movestart');
3403         },
3404
3405         _move: function (center, zoom, data) {
3406                 if (zoom === undefined) {
3407                         zoom = this._zoom;
3408                 }
3409                 var zoomChanged = this._zoom !== zoom;
3410
3411                 this._zoom = zoom;
3412                 this._lastCenter = center;
3413                 this._pixelOrigin = this._getNewPixelOrigin(center);
3414
3415                 // @event zoom: Event
3416                 // Fired repeatedly during any change in zoom level, including zoom
3417                 // and fly animations.
3418                 if (zoomChanged || (data && data.pinch)) {      // Always fire 'zoom' if pinching because #3530
3419                         this.fire('zoom', data);
3420                 }
3421
3422                 // @event move: Event
3423                 // Fired repeatedly during any movement of the map, including pan and
3424                 // fly animations.
3425                 return this.fire('move', data);
3426         },
3427
3428         _moveEnd: function (zoomChanged) {
3429                 // @event zoomend: Event
3430                 // Fired when the map has changed, after any animations.
3431                 if (zoomChanged) {
3432                         this.fire('zoomend');
3433                 }
3434
3435                 // @event moveend: Event
3436                 // Fired when the center of the map stops changing (e.g. user stopped
3437                 // dragging the map).
3438                 return this.fire('moveend');
3439         },
3440
3441         _stop: function () {
3442                 L.Util.cancelAnimFrame(this._flyToFrame);
3443                 if (this._panAnim) {
3444                         this._panAnim.stop();
3445                 }
3446                 return this;
3447         },
3448
3449         _rawPanBy: function (offset) {
3450                 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3451         },
3452
3453         _getZoomSpan: function () {
3454                 return this.getMaxZoom() - this.getMinZoom();
3455         },
3456
3457         _panInsideMaxBounds: function () {
3458                 if (!this._enforcingBounds) {
3459                         this.panInsideBounds(this.options.maxBounds);
3460                 }
3461         },
3462
3463         _checkIfLoaded: function () {
3464                 if (!this._loaded) {
3465                         throw new Error('Set map center and zoom first.');
3466                 }
3467         },
3468
3469         // DOM event handling
3470
3471         // @section Interaction events
3472         _initEvents: function (remove) {
3473                 if (!L.DomEvent) { return; }
3474
3475                 this._targets = {};
3476                 this._targets[L.stamp(this._container)] = this;
3477
3478                 var onOff = remove ? 'off' : 'on';
3479
3480                 // @event click: MouseEvent
3481                 // Fired when the user clicks (or taps) the map.
3482                 // @event dblclick: MouseEvent
3483                 // Fired when the user double-clicks (or double-taps) the map.
3484                 // @event mousedown: MouseEvent
3485                 // Fired when the user pushes the mouse button on the map.
3486                 // @event mouseup: MouseEvent
3487                 // Fired when the user releases the mouse button on the map.
3488                 // @event mouseover: MouseEvent
3489                 // Fired when the mouse enters the map.
3490                 // @event mouseout: MouseEvent
3491                 // Fired when the mouse leaves the map.
3492                 // @event mousemove: MouseEvent
3493                 // Fired while the mouse moves over the map.
3494                 // @event contextmenu: MouseEvent
3495                 // Fired when the user pushes the right mouse button on the map, prevents
3496                 // default browser context menu from showing if there are listeners on
3497                 // this event. Also fired on mobile when the user holds a single touch
3498                 // for a second (also called long press).
3499                 // @event keypress: KeyboardEvent
3500                 // Fired when the user presses a key from the keyboard while the map is focused.
3501                 L.DomEvent[onOff](this._container, 'click dblclick mousedown mouseup ' +
3502                         'mouseover mouseout mousemove contextmenu keypress', this._handleDOMEvent, this);
3503
3504                 if (this.options.trackResize) {
3505                         L.DomEvent[onOff](window, 'resize', this._onResize, this);
3506                 }
3507
3508                 if (L.Browser.any3d && this.options.transform3DLimit) {
3509                         this[onOff]('moveend', this._onMoveEnd);
3510                 }
3511         },
3512
3513         _onResize: function () {
3514                 L.Util.cancelAnimFrame(this._resizeRequest);
3515                 this._resizeRequest = L.Util.requestAnimFrame(
3516                         function () { this.invalidateSize({debounceMoveend: true}); }, this);
3517         },
3518
3519         _onScroll: function () {
3520                 this._container.scrollTop  = 0;
3521                 this._container.scrollLeft = 0;
3522         },
3523
3524         _onMoveEnd: function () {
3525                 var pos = this._getMapPanePos();
3526                 if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
3527                         // https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
3528                         // a pixel offset on very high values, see: http://jsfiddle.net/dg6r5hhb/
3529                         this._resetView(this.getCenter(), this.getZoom());
3530                 }
3531         },
3532
3533         _findEventTargets: function (e, type) {
3534                 var targets = [],
3535                     target,
3536                     isHover = type === 'mouseout' || type === 'mouseover',
3537                     src = e.target || e.srcElement,
3538                     dragging = false;
3539
3540                 while (src) {
3541                         target = this._targets[L.stamp(src)];
3542                         if (target && (type === 'click' || type === 'preclick') && !e._simulated && this._draggableMoved(target)) {
3543                                 // Prevent firing click after you just dragged an object.
3544                                 dragging = true;
3545                                 break;
3546                         }
3547                         if (target && target.listens(type, true)) {
3548                                 if (isHover && !L.DomEvent._isExternalTarget(src, e)) { break; }
3549                                 targets.push(target);
3550                                 if (isHover) { break; }
3551                         }
3552                         if (src === this._container) { break; }
3553                         src = src.parentNode;
3554                 }
3555                 if (!targets.length && !dragging && !isHover && L.DomEvent._isExternalTarget(src, e)) {
3556                         targets = [this];
3557                 }
3558                 return targets;
3559         },
3560
3561         _handleDOMEvent: function (e) {
3562                 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3563
3564                 var type = e.type === 'keypress' && e.keyCode === 13 ? 'click' : e.type;
3565
3566                 if (type === 'mousedown') {
3567                         // prevents outline when clicking on keyboard-focusable element
3568                         L.DomUtil.preventOutline(e.target || e.srcElement);
3569                 }
3570
3571                 this._fireDOMEvent(e, type);
3572         },
3573
3574         _fireDOMEvent: function (e, type, targets) {
3575
3576                 if (e.type === 'click') {
3577                         // Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
3578                         // @event preclick: MouseEvent
3579                         // Fired before mouse click on the map (sometimes useful when you
3580                         // want something to happen on click before any existing click
3581                         // handlers start running).
3582                         var synth = L.Util.extend({}, e);
3583                         synth.type = 'preclick';
3584                         this._fireDOMEvent(synth, synth.type, targets);
3585                 }
3586
3587                 if (e._stopped) { return; }
3588
3589                 // Find the layer the event is propagating from and its parents.
3590                 targets = (targets || []).concat(this._findEventTargets(e, type));
3591
3592                 if (!targets.length) { return; }
3593
3594                 var target = targets[0];
3595                 if (type === 'contextmenu' && target.listens(type, true)) {
3596                         L.DomEvent.preventDefault(e);
3597                 }
3598
3599                 var data = {
3600                         originalEvent: e
3601                 };
3602
3603                 if (e.type !== 'keypress') {
3604                         var isMarker = target instanceof L.Marker;
3605                         data.containerPoint = isMarker ?
3606                                         this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
3607                         data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
3608                         data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
3609                 }
3610
3611                 for (var i = 0; i < targets.length; i++) {
3612                         targets[i].fire(type, data, true);
3613                         if (data.originalEvent._stopped ||
3614                                 (targets[i].options.nonBubblingEvents && L.Util.indexOf(targets[i].options.nonBubblingEvents, type) !== -1)) { return; }
3615                 }
3616         },
3617
3618         _draggableMoved: function (obj) {
3619                 obj = obj.dragging && obj.dragging.enabled() ? obj : this;
3620                 return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
3621         },
3622
3623         _clearHandlers: function () {
3624                 for (var i = 0, len = this._handlers.length; i < len; i++) {
3625                         this._handlers[i].disable();
3626                 }
3627         },
3628
3629         // @section Other Methods
3630
3631         // @method whenReady(fn: Function, context?: Object): this
3632         // Runs the given function `fn` when the map gets initialized with
3633         // a view (center and zoom) and at least one layer, or immediately
3634         // if it's already initialized, optionally passing a function context.
3635         whenReady: function (callback, context) {
3636                 if (this._loaded) {
3637                         callback.call(context || this, {target: this});
3638                 } else {
3639                         this.on('load', callback, context);
3640                 }
3641                 return this;
3642         },
3643
3644
3645         // private methods for getting map state
3646
3647         _getMapPanePos: function () {
3648                 return L.DomUtil.getPosition(this._mapPane) || new L.Point(0, 0);
3649         },
3650
3651         _moved: function () {
3652                 var pos = this._getMapPanePos();
3653                 return pos && !pos.equals([0, 0]);
3654         },
3655
3656         _getTopLeftPoint: function (center, zoom) {
3657                 var pixelOrigin = center && zoom !== undefined ?
3658                         this._getNewPixelOrigin(center, zoom) :
3659                         this.getPixelOrigin();
3660                 return pixelOrigin.subtract(this._getMapPanePos());
3661         },
3662
3663         _getNewPixelOrigin: function (center, zoom) {
3664                 var viewHalf = this.getSize()._divideBy(2);
3665                 return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
3666         },
3667
3668         _latLngToNewLayerPoint: function (latlng, zoom, center) {
3669                 var topLeft = this._getNewPixelOrigin(center, zoom);
3670                 return this.project(latlng, zoom)._subtract(topLeft);
3671         },
3672
3673         _latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
3674                 var topLeft = this._getNewPixelOrigin(center, zoom);
3675                 return L.bounds([
3676                         this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
3677                         this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
3678                         this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
3679                         this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
3680                 ]);
3681         },
3682
3683         // layer point of the current center
3684         _getCenterLayerPoint: function () {
3685                 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3686         },
3687
3688         // offset of the specified place to the current center in pixels
3689         _getCenterOffset: function (latlng) {
3690                 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3691         },
3692
3693         // adjust center for view to get inside bounds
3694         _limitCenter: function (center, zoom, bounds) {
3695
3696                 if (!bounds) { return center; }
3697
3698                 var centerPoint = this.project(center, zoom),
3699                     viewHalf = this.getSize().divideBy(2),
3700                     viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3701                     offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3702
3703                 // If offset is less than a pixel, ignore.
3704                 // This prevents unstable projections from getting into
3705                 // an infinite loop of tiny offsets.
3706                 if (offset.round().equals([0, 0])) {
3707                         return center;
3708                 }
3709
3710                 return this.unproject(centerPoint.add(offset), zoom);
3711         },
3712
3713         // adjust offset for view to get inside bounds
3714         _limitOffset: function (offset, bounds) {
3715                 if (!bounds) { return offset; }
3716
3717                 var viewBounds = this.getPixelBounds(),
3718                     newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3719
3720                 return offset.add(this._getBoundsOffset(newBounds, bounds));
3721         },
3722
3723         // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3724         _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3725                 var projectedMaxBounds = L.bounds(
3726                         this.project(maxBounds.getNorthEast(), zoom),
3727                         this.project(maxBounds.getSouthWest(), zoom)
3728                     ),
3729                     minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
3730                     maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),
3731
3732                     dx = this._rebound(minOffset.x, -maxOffset.x),
3733                     dy = this._rebound(minOffset.y, -maxOffset.y);
3734
3735                 return new L.Point(dx, dy);
3736         },
3737
3738         _rebound: function (left, right) {
3739                 return left + right > 0 ?
3740                         Math.round(left - right) / 2 :
3741                         Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3742         },
3743
3744         _limitZoom: function (zoom) {
3745                 var min = this.getMinZoom(),
3746                     max = this.getMaxZoom(),
3747                     snap = L.Browser.any3d ? this.options.zoomSnap : 1;
3748                 if (snap) {
3749                         zoom = Math.round(zoom / snap) * snap;
3750                 }
3751                 return Math.max(min, Math.min(max, zoom));
3752         },
3753
3754         _onPanTransitionStep: function () {
3755                 this.fire('move');
3756         },
3757
3758         _onPanTransitionEnd: function () {
3759                 L.DomUtil.removeClass(this._mapPane, 'leaflet-pan-anim');
3760                 this.fire('moveend');
3761         },
3762
3763         _tryAnimatedPan: function (center, options) {
3764                 // difference between the new and current centers in pixels
3765                 var offset = this._getCenterOffset(center)._floor();
3766
3767                 // don't animate too far unless animate: true specified in options
3768                 if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }
3769
3770                 this.panBy(offset, options);
3771
3772                 return true;
3773         },
3774
3775         _createAnimProxy: function () {
3776
3777                 var proxy = this._proxy = L.DomUtil.create('div', 'leaflet-proxy leaflet-zoom-animated');
3778                 this._panes.mapPane.appendChild(proxy);
3779
3780                 this.on('zoomanim', function (e) {
3781                         var prop = L.DomUtil.TRANSFORM,
3782                             transform = proxy.style[prop];
3783
3784                         L.DomUtil.setTransform(proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));
3785
3786                         // workaround for case when transform is the same and so transitionend event is not fired
3787                         if (transform === proxy.style[prop] && this._animatingZoom) {
3788                                 this._onZoomTransitionEnd();
3789                         }
3790                 }, this);
3791
3792                 this.on('load moveend', function () {
3793                         var c = this.getCenter(),
3794                             z = this.getZoom();
3795                         L.DomUtil.setTransform(proxy, this.project(c, z), this.getZoomScale(z, 1));
3796                 }, this);
3797         },
3798
3799         _catchTransitionEnd: function (e) {
3800                 if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
3801                         this._onZoomTransitionEnd();
3802                 }
3803         },
3804
3805         _nothingToAnimate: function () {
3806                 return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
3807         },
3808
3809         _tryAnimatedZoom: function (center, zoom, options) {
3810
3811                 if (this._animatingZoom) { return true; }
3812
3813                 options = options || {};
3814
3815                 // don't animate if disabled, not supported or zoom difference is too large
3816                 if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
3817                         Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }
3818
3819                 // offset is the pixel coords of the zoom origin relative to the current center
3820                 var scale = this.getZoomScale(zoom),
3821                     offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);
3822
3823                 // don't animate if the zoom origin isn't within one screen from the current center, unless forced
3824                 if (options.animate !== true && !this.getSize().contains(offset)) { return false; }
3825
3826                 L.Util.requestAnimFrame(function () {
3827                         this
3828                             ._moveStart(true)
3829                             ._animateZoom(center, zoom, true);
3830                 }, this);
3831
3832                 return true;
3833         },
3834
3835         _animateZoom: function (center, zoom, startAnim, noUpdate) {
3836                 if (startAnim) {
3837                         this._animatingZoom = true;
3838
3839                         // remember what center/zoom to set after animation
3840                         this._animateToCenter = center;
3841                         this._animateToZoom = zoom;
3842
3843                         L.DomUtil.addClass(this._mapPane, 'leaflet-zoom-anim');
3844                 }
3845
3846                 // @event zoomanim: ZoomAnimEvent
3847                 // Fired on every frame of a zoom animation
3848                 this.fire('zoomanim', {
3849                         center: center,
3850                         zoom: zoom,
3851                         noUpdate: noUpdate
3852                 });
3853
3854                 // Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
3855                 setTimeout(L.bind(this._onZoomTransitionEnd, this), 250);
3856         },
3857
3858         _onZoomTransitionEnd: function () {
3859                 if (!this._animatingZoom) { return; }
3860
3861                 L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim');
3862
3863                 this._animatingZoom = false;
3864
3865                 this._move(this._animateToCenter, this._animateToZoom);
3866
3867                 // This anim frame should prevent an obscure iOS webkit tile loading race condition.
3868                 L.Util.requestAnimFrame(function () {
3869                         this._moveEnd(true);
3870                 }, this);
3871         }
3872 });
3873
3874 // @section
3875
3876 // @factory L.map(id: String, options?: Map options)
3877 // Instantiates a map object given the DOM ID of a `<div>` element
3878 // and optionally an object literal with `Map options`.
3879 //
3880 // @alternative
3881 // @factory L.map(el: HTMLElement, options?: Map options)
3882 // Instantiates a map object given an instance of a `<div>` HTML element
3883 // and optionally an object literal with `Map options`.
3884 L.map = function (id, options) {
3885         return new L.Map(id, options);
3886 };
3887
3888
3889
3890
3891 /*
3892  * @class Layer
3893  * @inherits Evented
3894  * @aka L.Layer
3895  * @aka ILayer
3896  *
3897  * A set of methods from the Layer base class that all Leaflet layers use.
3898  * Inherits all methods, options and events from `L.Evented`.
3899  *
3900  * @example
3901  *
3902  * ```js
3903  * var layer = L.Marker(latlng).addTo(map);
3904  * layer.addTo(map);
3905  * layer.remove();
3906  * ```
3907  *
3908  * @event add: Event
3909  * Fired after the layer is added to a map
3910  *
3911  * @event remove: Event
3912  * Fired after the layer is removed from a map
3913  */
3914
3915
3916 L.Layer = L.Evented.extend({
3917
3918         // Classes extending `L.Layer` will inherit the following options:
3919         options: {
3920                 // @option pane: String = 'overlayPane'
3921                 // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
3922                 pane: 'overlayPane',
3923                 nonBubblingEvents: [],  // Array of events that should not be bubbled to DOM parents (like the map),
3924
3925                 // @option attribution: String = null
3926                 // String to be shown in the attribution control, describes the layer data, e.g. "© Mapbox".
3927                 attribution: null
3928         },
3929
3930         /* @section
3931          * Classes extending `L.Layer` will inherit the following methods:
3932          *
3933          * @method addTo(map: Map): this
3934          * Adds the layer to the given map
3935          */
3936         addTo: function (map) {
3937                 map.addLayer(this);
3938                 return this;
3939         },
3940
3941         // @method remove: this
3942         // Removes the layer from the map it is currently active on.
3943         remove: function () {
3944                 return this.removeFrom(this._map || this._mapToAdd);
3945         },
3946
3947         // @method removeFrom(map: Map): this
3948         // Removes the layer from the given map
3949         removeFrom: function (obj) {
3950                 if (obj) {
3951                         obj.removeLayer(this);
3952                 }
3953                 return this;
3954         },
3955
3956         // @method getPane(name? : String): HTMLElement
3957         // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
3958         getPane: function (name) {
3959                 return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
3960         },
3961
3962         addInteractiveTarget: function (targetEl) {
3963                 this._map._targets[L.stamp(targetEl)] = this;
3964                 return this;
3965         },
3966
3967         removeInteractiveTarget: function (targetEl) {
3968                 delete this._map._targets[L.stamp(targetEl)];
3969                 return this;
3970         },
3971
3972         // @method getAttribution: String
3973         // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
3974         getAttribution: function () {
3975                 return this.options.attribution;
3976         },
3977
3978         _layerAdd: function (e) {
3979                 var map = e.target;
3980
3981                 // check in case layer gets added and then removed before the map is ready
3982                 if (!map.hasLayer(this)) { return; }
3983
3984                 this._map = map;
3985                 this._zoomAnimated = map._zoomAnimated;
3986
3987                 if (this.getEvents) {
3988                         var events = this.getEvents();
3989                         map.on(events, this);
3990                         this.once('remove', function () {
3991                                 map.off(events, this);
3992                         }, this);
3993                 }
3994
3995                 this.onAdd(map);
3996
3997                 if (this.getAttribution && map.attributionControl) {
3998                         map.attributionControl.addAttribution(this.getAttribution());
3999                 }
4000
4001                 this.fire('add');
4002                 map.fire('layeradd', {layer: this});
4003         }
4004 });
4005
4006 /* @section Extension methods
4007  * @uninheritable
4008  *
4009  * Every layer should extend from `L.Layer` and (re-)implement the following methods.
4010  *
4011  * @method onAdd(map: Map): this
4012  * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
4013  *
4014  * @method onRemove(map: Map): this
4015  * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
4016  *
4017  * @method getEvents(): Object
4018  * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
4019  *
4020  * @method getAttribution(): String
4021  * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
4022  *
4023  * @method beforeAdd(map: Map): this
4024  * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
4025  */
4026
4027
4028 /* @namespace Map
4029  * @section Layer events
4030  *
4031  * @event layeradd: LayerEvent
4032  * Fired when a new layer is added to the map.
4033  *
4034  * @event layerremove: LayerEvent
4035  * Fired when some layer is removed from the map
4036  *
4037  * @section Methods for Layers and Controls
4038  */
4039 L.Map.include({
4040         // @method addLayer(layer: Layer): this
4041         // Adds the given layer to the map
4042         addLayer: function (layer) {
4043                 var id = L.stamp(layer);
4044                 if (this._layers[id]) { return this; }
4045                 this._layers[id] = layer;
4046
4047                 layer._mapToAdd = this;
4048
4049                 if (layer.beforeAdd) {
4050                         layer.beforeAdd(this);
4051                 }
4052
4053                 this.whenReady(layer._layerAdd, layer);
4054
4055                 return this;
4056         },
4057
4058         // @method removeLayer(layer: Layer): this
4059         // Removes the given layer from the map.
4060         removeLayer: function (layer) {
4061                 var id = L.stamp(layer);
4062
4063                 if (!this._layers[id]) { return this; }
4064
4065                 if (this._loaded) {
4066                         layer.onRemove(this);
4067                 }
4068
4069                 if (layer.getAttribution && this.attributionControl) {
4070                         this.attributionControl.removeAttribution(layer.getAttribution());
4071                 }
4072
4073                 delete this._layers[id];
4074
4075                 if (this._loaded) {
4076                         this.fire('layerremove', {layer: layer});
4077                         layer.fire('remove');
4078                 }
4079
4080                 layer._map = layer._mapToAdd = null;
4081
4082                 return this;
4083         },
4084
4085         // @method hasLayer(layer: Layer): Boolean
4086         // Returns `true` if the given layer is currently added to the map
4087         hasLayer: function (layer) {
4088                 return !!layer && (L.stamp(layer) in this._layers);
4089         },
4090
4091         /* @method eachLayer(fn: Function, context?: Object): this
4092          * Iterates over the layers of the map, optionally specifying context of the iterator function.
4093          * ```
4094          * map.eachLayer(function(layer){
4095          *     layer.bindPopup('Hello');
4096          * });
4097          * ```
4098          */
4099         eachLayer: function (method, context) {
4100                 for (var i in this._layers) {
4101                         method.call(context, this._layers[i]);
4102                 }
4103                 return this;
4104         },
4105
4106         _addLayers: function (layers) {
4107                 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
4108
4109                 for (var i = 0, len = layers.length; i < len; i++) {
4110                         this.addLayer(layers[i]);
4111                 }
4112         },
4113
4114         _addZoomLimit: function (layer) {
4115                 if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
4116                         this._zoomBoundLayers[L.stamp(layer)] = layer;
4117                         this._updateZoomLevels();
4118                 }
4119         },
4120
4121         _removeZoomLimit: function (layer) {
4122                 var id = L.stamp(layer);
4123
4124                 if (this._zoomBoundLayers[id]) {
4125                         delete this._zoomBoundLayers[id];
4126                         this._updateZoomLevels();
4127                 }
4128         },
4129
4130         _updateZoomLevels: function () {
4131                 var minZoom = Infinity,
4132                     maxZoom = -Infinity,
4133                     oldZoomSpan = this._getZoomSpan();
4134
4135                 for (var i in this._zoomBoundLayers) {
4136                         var options = this._zoomBoundLayers[i].options;
4137
4138                         minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
4139                         maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
4140                 }
4141
4142                 this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
4143                 this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;
4144
4145                 // @section Map state change events
4146                 // @event zoomlevelschange: Event
4147                 // Fired when the number of zoomlevels on the map is changed due
4148                 // to adding or removing a layer.
4149                 if (oldZoomSpan !== this._getZoomSpan()) {
4150                         this.fire('zoomlevelschange');
4151                 }
4152
4153                 if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
4154                         this.setZoom(this._layersMaxZoom);
4155                 }
4156                 if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
4157                         this.setZoom(this._layersMinZoom);
4158                 }
4159         }
4160 });
4161
4162
4163
4164 /*
4165  * @namespace DomEvent
4166  * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
4167  */
4168
4169 // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.
4170
4171
4172
4173 var eventsKey = '_leaflet_events';
4174
4175 L.DomEvent = {
4176
4177         // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
4178         // Adds a listener function (`fn`) to a particular DOM event type of the
4179         // element `el`. You can optionally specify the context of the listener
4180         // (object the `this` keyword will point to). You can also pass several
4181         // space-separated types (e.g. `'click dblclick'`).
4182
4183         // @alternative
4184         // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
4185         // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4186         on: function (obj, types, fn, context) {
4187
4188                 if (typeof types === 'object') {
4189                         for (var type in types) {
4190                                 this._on(obj, type, types[type], fn);
4191                         }
4192                 } else {
4193                         types = L.Util.splitWords(types);
4194
4195                         for (var i = 0, len = types.length; i < len; i++) {
4196                                 this._on(obj, types[i], fn, context);
4197                         }
4198                 }
4199
4200                 return this;
4201         },
4202
4203         // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
4204         // Removes a previously added listener function. If no function is specified,
4205         // it will remove all the listeners of that particular DOM event from the element.
4206         // Note that if you passed a custom context to on, you must pass the same
4207         // context to `off` in order to remove the listener.
4208
4209         // @alternative
4210         // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
4211         // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
4212         off: function (obj, types, fn, context) {
4213
4214                 if (typeof types === 'object') {
4215                         for (var type in types) {
4216                                 this._off(obj, type, types[type], fn);
4217                         }
4218                 } else {
4219                         types = L.Util.splitWords(types);
4220
4221                         for (var i = 0, len = types.length; i < len; i++) {
4222                                 this._off(obj, types[i], fn, context);
4223                         }
4224                 }
4225
4226                 return this;
4227         },
4228
4229         _on: function (obj, type, fn, context) {
4230                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : '');
4231
4232                 if (obj[eventsKey] && obj[eventsKey][id]) { return this; }
4233
4234                 var handler = function (e) {
4235                         return fn.call(context || obj, e || window.event);
4236                 };
4237
4238                 var originalHandler = handler;
4239
4240                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4241                         this.addPointerListener(obj, type, handler, id);
4242
4243                 } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener &&
4244                            !(L.Browser.pointer && L.Browser.chrome)) {
4245                         // Chrome >55 does not need the synthetic dblclicks from addDoubleTapListener
4246                         // See #5180
4247                         this.addDoubleTapListener(obj, handler, id);
4248
4249                 } else if ('addEventListener' in obj) {
4250
4251                         if (type === 'mousewheel') {
4252                                 obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4253
4254                         } else if ((type === 'mouseenter') || (type === 'mouseleave')) {
4255                                 handler = function (e) {
4256                                         e = e || window.event;
4257                                         if (L.DomEvent._isExternalTarget(obj, e)) {
4258                                                 originalHandler(e);
4259                                         }
4260                                 };
4261                                 obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false);
4262
4263                         } else {
4264                                 if (type === 'click' && L.Browser.android) {
4265                                         handler = function (e) {
4266                                                 return L.DomEvent._filterClick(e, originalHandler);
4267                                         };
4268                                 }
4269                                 obj.addEventListener(type, handler, false);
4270                         }
4271
4272                 } else if ('attachEvent' in obj) {
4273                         obj.attachEvent('on' + type, handler);
4274                 }
4275
4276                 obj[eventsKey] = obj[eventsKey] || {};
4277                 obj[eventsKey][id] = handler;
4278
4279                 return this;
4280         },
4281
4282         _off: function (obj, type, fn, context) {
4283
4284                 var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''),
4285                     handler = obj[eventsKey] && obj[eventsKey][id];
4286
4287                 if (!handler) { return this; }
4288
4289                 if (L.Browser.pointer && type.indexOf('touch') === 0) {
4290                         this.removePointerListener(obj, type, id);
4291
4292                 } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) {
4293                         this.removeDoubleTapListener(obj, id);
4294
4295                 } else if ('removeEventListener' in obj) {
4296
4297                         if (type === 'mousewheel') {
4298                                 obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false);
4299
4300                         } else {
4301                                 obj.removeEventListener(
4302                                         type === 'mouseenter' ? 'mouseover' :
4303                                         type === 'mouseleave' ? 'mouseout' : type, handler, false);
4304                         }
4305
4306                 } else if ('detachEvent' in obj) {
4307                         obj.detachEvent('on' + type, handler);
4308                 }
4309
4310                 obj[eventsKey][id] = null;
4311
4312                 return this;
4313         },
4314
4315         // @function stopPropagation(ev: DOMEvent): this
4316         // Stop the given event from propagation to parent elements. Used inside the listener functions:
4317         // ```js
4318         // L.DomEvent.on(div, 'click', function (ev) {
4319         //      L.DomEvent.stopPropagation(ev);
4320         // });
4321         // ```
4322         stopPropagation: function (e) {
4323
4324                 if (e.stopPropagation) {
4325                         e.stopPropagation();
4326                 } else if (e.originalEvent) {  // In case of Leaflet event.
4327                         e.originalEvent._stopped = true;
4328                 } else {
4329                         e.cancelBubble = true;
4330                 }
4331                 L.DomEvent._skipped(e);
4332
4333                 return this;
4334         },
4335
4336         // @function disableScrollPropagation(el: HTMLElement): this
4337         // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants).
4338         disableScrollPropagation: function (el) {
4339                 return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation);
4340         },
4341
4342         // @function disableClickPropagation(el: HTMLElement): this
4343         // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`,
4344         // `'mousedown'` and `'touchstart'` events (plus browser variants).
4345         disableClickPropagation: function (el) {
4346                 var stop = L.DomEvent.stopPropagation;
4347
4348                 L.DomEvent.on(el, L.Draggable.START.join(' '), stop);
4349
4350                 return L.DomEvent.on(el, {
4351                         click: L.DomEvent._fakeStop,
4352                         dblclick: stop
4353                 });
4354         },
4355
4356         // @function preventDefault(ev: DOMEvent): this
4357         // Prevents the default action of the DOM Event `ev` from happening (such as
4358         // following a link in the href of the a element, or doing a POST request
4359         // with page reload when a `<form>` is submitted).
4360         // Use it inside listener functions.
4361         preventDefault: function (e) {
4362
4363                 if (e.preventDefault) {
4364                         e.preventDefault();
4365                 } else {
4366                         e.returnValue = false;
4367                 }
4368                 return this;
4369         },
4370
4371         // @function stop(ev): this
4372         // Does `stopPropagation` and `preventDefault` at the same time.
4373         stop: function (e) {
4374                 return L.DomEvent
4375                         .preventDefault(e)
4376                         .stopPropagation(e);
4377         },
4378
4379         // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
4380         // Gets normalized mouse position from a DOM event relative to the
4381         // `container` or to the whole page if not specified.
4382         getMousePosition: function (e, container) {
4383                 if (!container) {
4384                         return new L.Point(e.clientX, e.clientY);
4385                 }
4386
4387                 var rect = container.getBoundingClientRect();
4388
4389                 return new L.Point(
4390                         e.clientX - rect.left - container.clientLeft,
4391                         e.clientY - rect.top - container.clientTop);
4392         },
4393
4394         // Chrome on Win scrolls double the pixels as in other platforms (see #4538),
4395         // and Firefox scrolls device pixels, not CSS pixels
4396         _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 :
4397                         L.Browser.gecko ? window.devicePixelRatio :
4398                         1,
4399
4400         // @function getWheelDelta(ev: DOMEvent): Number
4401         // Gets normalized wheel delta from a mousewheel DOM event, in vertical
4402         // pixels scrolled (negative if scrolling down).
4403         // Events from pointing devices without precise scrolling are mapped to
4404         // a best guess of 60 pixels.
4405         getWheelDelta: function (e) {
4406                 return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
4407                        (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels
4408                        (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
4409                        (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
4410                        (e.deltaX || e.deltaZ) ? 0 :     // Skip horizontal/depth wheel events
4411                        e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
4412                        (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
4413                        e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
4414                        0;
4415         },
4416
4417         _skipEvents: {},
4418
4419         _fakeStop: function (e) {
4420                 // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e)
4421                 L.DomEvent._skipEvents[e.type] = true;
4422         },
4423
4424         _skipped: function (e) {
4425                 var skipped = this._skipEvents[e.type];
4426                 // reset when checking, as it's only used in map container and propagates outside of the map
4427                 this._skipEvents[e.type] = false;
4428                 return skipped;
4429         },
4430
4431         // check if element really left/entered the event target (for mouseenter/mouseleave)
4432         _isExternalTarget: function (el, e) {
4433
4434                 var related = e.relatedTarget;
4435
4436                 if (!related) { return true; }
4437
4438                 try {
4439                         while (related && (related !== el)) {
4440                                 related = related.parentNode;
4441                         }
4442                 } catch (err) {
4443                         return false;
4444                 }
4445                 return (related !== el);
4446         },
4447
4448         // this is a horrible workaround for a bug in Android where a single touch triggers two click events
4449         _filterClick: function (e, handler) {
4450                 var timeStamp = (e.timeStamp || (e.originalEvent && e.originalEvent.timeStamp)),
4451                     elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
4452
4453                 // are they closer together than 500ms yet more than 100ms?
4454                 // Android typically triggers them ~300ms apart while multiple listeners
4455                 // on the same event should be triggered far faster;
4456                 // or check if click is simulated on the element, and if it is, reject any non-simulated events
4457
4458                 if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
4459                         L.DomEvent.stop(e);
4460                         return;
4461                 }
4462                 L.DomEvent._lastClick = timeStamp;
4463
4464                 handler(e);
4465         }
4466 };
4467
4468 // @function addListener(…): this
4469 // Alias to [`L.DomEvent.on`](#domevent-on)
4470 L.DomEvent.addListener = L.DomEvent.on;
4471
4472 // @function removeListener(…): this
4473 // Alias to [`L.DomEvent.off`](#domevent-off)
4474 L.DomEvent.removeListener = L.DomEvent.off;
4475
4476
4477
4478 /*
4479  * @class PosAnimation
4480  * @aka L.PosAnimation
4481  * @inherits Evented
4482  * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
4483  *
4484  * @example
4485  * ```js
4486  * var fx = new L.PosAnimation();
4487  * fx.run(el, [300, 500], 0.5);
4488  * ```
4489  *
4490  * @constructor L.PosAnimation()
4491  * Creates a `PosAnimation` object.
4492  *
4493  */
4494
4495 L.PosAnimation = L.Evented.extend({
4496
4497         // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
4498         // Run an animation of a given element to a new position, optionally setting
4499         // duration in seconds (`0.25` by default) and easing linearity factor (3rd
4500         // argument of the [cubic bezier curve](http://cubic-bezier.com/#0,0,.5,1),
4501         // `0.5` by default).
4502         run: function (el, newPos, duration, easeLinearity) {
4503                 this.stop();
4504
4505                 this._el = el;
4506                 this._inProgress = true;
4507                 this._duration = duration || 0.25;
4508                 this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);
4509
4510                 this._startPos = L.DomUtil.getPosition(el);
4511                 this._offset = newPos.subtract(this._startPos);
4512                 this._startTime = +new Date();
4513
4514                 // @event start: Event
4515                 // Fired when the animation starts
4516                 this.fire('start');
4517
4518                 this._animate();
4519         },
4520
4521         // @method stop()
4522         // Stops the animation (if currently running).
4523         stop: function () {
4524                 if (!this._inProgress) { return; }
4525
4526                 this._step(true);
4527                 this._complete();
4528         },
4529
4530         _animate: function () {
4531                 // animation loop
4532                 this._animId = L.Util.requestAnimFrame(this._animate, this);
4533                 this._step();
4534         },
4535
4536         _step: function (round) {
4537                 var elapsed = (+new Date()) - this._startTime,
4538                     duration = this._duration * 1000;
4539
4540                 if (elapsed < duration) {
4541                         this._runFrame(this._easeOut(elapsed / duration), round);
4542                 } else {
4543                         this._runFrame(1);
4544                         this._complete();
4545                 }
4546         },
4547
4548         _runFrame: function (progress, round) {
4549                 var pos = this._startPos.add(this._offset.multiplyBy(progress));
4550                 if (round) {
4551                         pos._round();
4552                 }
4553                 L.DomUtil.setPosition(this._el, pos);
4554
4555                 // @event step: Event
4556                 // Fired continuously during the animation.
4557                 this.fire('step');
4558         },
4559
4560         _complete: function () {
4561                 L.Util.cancelAnimFrame(this._animId);
4562
4563                 this._inProgress = false;
4564                 // @event end: Event
4565                 // Fired when the animation ends.
4566                 this.fire('end');
4567         },
4568
4569         _easeOut: function (t) {
4570                 return 1 - Math.pow(1 - t, this._easeOutPower);
4571         }
4572 });
4573
4574
4575
4576 /*
4577  * @namespace Projection
4578  * @projection L.Projection.Mercator
4579  *
4580  * Elliptical Mercator projection — more complex than Spherical Mercator. Takes into account that Earth is a geoid, not a perfect sphere. Used by the EPSG:3395 CRS.
4581  */
4582
4583 L.Projection.Mercator = {
4584         R: 6378137,
4585         R_MINOR: 6356752.314245179,
4586
4587         bounds: L.bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),
4588
4589         project: function (latlng) {
4590                 var d = Math.PI / 180,
4591                     r = this.R,
4592                     y = latlng.lat * d,
4593                     tmp = this.R_MINOR / r,
4594                     e = Math.sqrt(1 - tmp * tmp),
4595                     con = e * Math.sin(y);
4596
4597                 var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
4598                 y = -r * Math.log(Math.max(ts, 1E-10));
4599
4600                 return new L.Point(latlng.lng * d * r, y);
4601         },
4602
4603         unproject: function (point) {
4604                 var d = 180 / Math.PI,
4605                     r = this.R,
4606                     tmp = this.R_MINOR / r,
4607                     e = Math.sqrt(1 - tmp * tmp),
4608                     ts = Math.exp(-point.y / r),
4609                     phi = Math.PI / 2 - 2 * Math.atan(ts);
4610
4611                 for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
4612                         con = e * Math.sin(phi);
4613                         con = Math.pow((1 - con) / (1 + con), e / 2);
4614                         dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
4615                         phi += dphi;
4616                 }
4617
4618                 return new L.LatLng(phi * d, point.x * d / r);
4619         }
4620 };
4621
4622
4623
4624 /*
4625  * @namespace CRS
4626  * @crs L.CRS.EPSG3395
4627  *
4628  * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
4629  */
4630
4631 L.CRS.EPSG3395 = L.extend({}, L.CRS.Earth, {
4632         code: 'EPSG:3395',
4633         projection: L.Projection.Mercator,
4634
4635         transformation: (function () {
4636                 var scale = 0.5 / (Math.PI * L.Projection.Mercator.R);
4637                 return new L.Transformation(scale, 0.5, -scale, 0.5);
4638         }())
4639 });
4640
4641
4642
4643 /*
4644  * @class GridLayer
4645  * @inherits Layer
4646  * @aka L.GridLayer
4647  *
4648  * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
4649  * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
4650  *
4651  *
4652  * @section Synchronous usage
4653  * @example
4654  *
4655  * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
4656  *
4657  * ```js
4658  * var CanvasLayer = L.GridLayer.extend({
4659  *     createTile: function(coords){
4660  *         // create a <canvas> element for drawing
4661  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4662  *
4663  *         // setup tile width and height according to the options
4664  *         var size = this.getTileSize();
4665  *         tile.width = size.x;
4666  *         tile.height = size.y;
4667  *
4668  *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
4669  *         var ctx = tile.getContext('2d');
4670  *
4671  *         // return the tile so it can be rendered on screen
4672  *         return tile;
4673  *     }
4674  * });
4675  * ```
4676  *
4677  * @section Asynchronous usage
4678  * @example
4679  *
4680  * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
4681  *
4682  * ```js
4683  * var CanvasLayer = L.GridLayer.extend({
4684  *     createTile: function(coords, done){
4685  *         var error;
4686  *
4687  *         // create a <canvas> element for drawing
4688  *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4689  *
4690  *         // setup tile width and height according to the options
4691  *         var size = this.getTileSize();
4692  *         tile.width = size.x;
4693  *         tile.height = size.y;
4694  *
4695  *         // draw something asynchronously and pass the tile to the done() callback
4696  *         setTimeout(function() {
4697  *             done(error, tile);
4698  *         }, 1000);
4699  *
4700  *         return tile;
4701  *     }
4702  * });
4703  * ```
4704  *
4705  * @section
4706  */
4707
4708
4709 L.GridLayer = L.Layer.extend({
4710
4711         // @section
4712         // @aka GridLayer options
4713         options: {
4714                 // @option tileSize: Number|Point = 256
4715                 // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
4716                 tileSize: 256,
4717
4718                 // @option opacity: Number = 1.0
4719                 // Opacity of the tiles. Can be used in the `createTile()` function.
4720                 opacity: 1,
4721
4722                 // @option updateWhenIdle: Boolean = depends
4723                 // If `false`, new tiles are loaded during panning, otherwise only after it (for better performance). `true` by default on mobile browsers, otherwise `false`.
4724                 updateWhenIdle: L.Browser.mobile,
4725
4726                 // @option updateWhenZooming: Boolean = true
4727                 // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
4728                 updateWhenZooming: true,
4729
4730                 // @option updateInterval: Number = 200
4731                 // Tiles will not update more than once every `updateInterval` milliseconds when panning.
4732                 updateInterval: 200,
4733
4734                 // @option zIndex: Number = 1
4735                 // The explicit zIndex of the tile layer.
4736                 zIndex: 1,
4737
4738                 // @option bounds: LatLngBounds = undefined
4739                 // If set, tiles will only be loaded inside the set `LatLngBounds`.
4740                 bounds: null,
4741
4742                 // @option minZoom: Number = 0
4743                 // The minimum zoom level that tiles will be loaded at. By default the entire map.
4744                 minZoom: 0,
4745
4746                 // @option maxZoom: Number = undefined
4747                 // The maximum zoom level that tiles will be loaded at.
4748                 maxZoom: undefined,
4749
4750                 // @option noWrap: Boolean = false
4751                 // Whether the layer is wrapped around the antimeridian. If `true`, the
4752                 // GridLayer will only be displayed once at low zoom levels. Has no
4753                 // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
4754                 // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
4755                 // tiles outside the CRS limits.
4756                 noWrap: false,
4757
4758                 // @option pane: String = 'tilePane'
4759                 // `Map pane` where the grid layer will be added.
4760                 pane: 'tilePane',
4761
4762                 // @option className: String = ''
4763                 // A custom class name to assign to the tile layer. Empty by default.
4764                 className: '',
4765
4766                 // @option keepBuffer: Number = 2
4767                 // When panning the map, keep this many rows and columns of tiles before unloading them.
4768                 keepBuffer: 2
4769         },
4770
4771         initialize: function (options) {
4772                 L.setOptions(this, options);
4773         },
4774
4775         onAdd: function () {
4776                 this._initContainer();
4777
4778                 this._levels = {};
4779                 this._tiles = {};
4780
4781                 this._resetView();
4782                 this._update();
4783         },
4784
4785         beforeAdd: function (map) {
4786                 map._addZoomLimit(this);
4787         },
4788
4789         onRemove: function (map) {
4790                 this._removeAllTiles();
4791                 L.DomUtil.remove(this._container);
4792                 map._removeZoomLimit(this);
4793                 this._container = null;
4794                 this._tileZoom = null;
4795         },
4796
4797         // @method bringToFront: this
4798         // Brings the tile layer to the top of all tile layers.
4799         bringToFront: function () {
4800                 if (this._map) {
4801                         L.DomUtil.toFront(this._container);
4802                         this._setAutoZIndex(Math.max);
4803                 }
4804                 return this;
4805         },
4806
4807         // @method bringToBack: this
4808         // Brings the tile layer to the bottom of all tile layers.
4809         bringToBack: function () {
4810                 if (this._map) {
4811                         L.DomUtil.toBack(this._container);
4812                         this._setAutoZIndex(Math.min);
4813                 }
4814                 return this;
4815         },
4816
4817         // @method getContainer: HTMLElement
4818         // Returns the HTML element that contains the tiles for this layer.
4819         getContainer: function () {
4820                 return this._container;
4821         },
4822
4823         // @method setOpacity(opacity: Number): this
4824         // Changes the [opacity](#gridlayer-opacity) of the grid layer.
4825         setOpacity: function (opacity) {
4826                 this.options.opacity = opacity;
4827                 this._updateOpacity();
4828                 return this;
4829         },
4830
4831         // @method setZIndex(zIndex: Number): this
4832         // Changes the [zIndex](#gridlayer-zindex) of the grid layer.
4833         setZIndex: function (zIndex) {
4834                 this.options.zIndex = zIndex;
4835                 this._updateZIndex();
4836
4837                 return this;
4838         },
4839
4840         // @method isLoading: Boolean
4841         // Returns `true` if any tile in the grid layer has not finished loading.
4842         isLoading: function () {
4843                 return this._loading;
4844         },
4845
4846         // @method redraw: this
4847         // Causes the layer to clear all the tiles and request them again.
4848         redraw: function () {
4849                 if (this._map) {
4850                         this._removeAllTiles();
4851                         this._update();
4852                 }
4853                 return this;
4854         },
4855
4856         getEvents: function () {
4857                 var events = {
4858                         viewprereset: this._invalidateAll,
4859                         viewreset: this._resetView,
4860                         zoom: this._resetView,
4861                         moveend: this._onMoveEnd
4862                 };
4863
4864                 if (!this.options.updateWhenIdle) {
4865                         // update tiles on move, but not more often than once per given interval
4866                         if (!this._onMove) {
4867                                 this._onMove = L.Util.throttle(this._onMoveEnd, this.options.updateInterval, this);
4868                         }
4869
4870                         events.move = this._onMove;
4871                 }
4872
4873                 if (this._zoomAnimated) {
4874                         events.zoomanim = this._animateZoom;
4875                 }
4876
4877                 return events;
4878         },
4879
4880         // @section Extension methods
4881         // Layers extending `GridLayer` shall reimplement the following method.
4882         // @method createTile(coords: Object, done?: Function): HTMLElement
4883         // Called only internally, must be overriden by classes extending `GridLayer`.
4884         // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
4885         // is specified, it must be called when the tile has finished loading and drawing.
4886         createTile: function () {
4887                 return document.createElement('div');
4888         },
4889
4890         // @section
4891         // @method getTileSize: Point
4892         // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
4893         getTileSize: function () {
4894                 var s = this.options.tileSize;
4895                 return s instanceof L.Point ? s : new L.Point(s, s);
4896         },
4897
4898         _updateZIndex: function () {
4899                 if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
4900                         this._container.style.zIndex = this.options.zIndex;
4901                 }
4902         },
4903
4904         _setAutoZIndex: function (compare) {
4905                 // go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)
4906
4907                 var layers = this.getPane().children,
4908                     edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min
4909
4910                 for (var i = 0, len = layers.length, zIndex; i < len; i++) {
4911
4912                         zIndex = layers[i].style.zIndex;
4913
4914                         if (layers[i] !== this._container && zIndex) {
4915                                 edgeZIndex = compare(edgeZIndex, +zIndex);
4916                         }
4917                 }
4918
4919                 if (isFinite(edgeZIndex)) {
4920                         this.options.zIndex = edgeZIndex + compare(-1, 1);
4921                         this._updateZIndex();
4922                 }
4923         },
4924
4925         _updateOpacity: function () {
4926                 if (!this._map) { return; }
4927
4928                 // IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
4929                 if (L.Browser.ielt9) { return; }
4930
4931                 L.DomUtil.setOpacity(this._container, this.options.opacity);
4932
4933                 var now = +new Date(),
4934                     nextFrame = false,
4935                     willPrune = false;
4936
4937                 for (var key in this._tiles) {
4938                         var tile = this._tiles[key];
4939                         if (!tile.current || !tile.loaded) { continue; }
4940
4941                         var fade = Math.min(1, (now - tile.loaded) / 200);
4942
4943                         L.DomUtil.setOpacity(tile.el, fade);
4944                         if (fade < 1) {
4945                                 nextFrame = true;
4946                         } else {
4947                                 if (tile.active) { willPrune = true; }
4948                                 tile.active = true;
4949                         }
4950                 }
4951
4952                 if (willPrune && !this._noPrune) { this._pruneTiles(); }
4953
4954                 if (nextFrame) {
4955                         L.Util.cancelAnimFrame(this._fadeFrame);
4956                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
4957                 }
4958         },
4959
4960         _initContainer: function () {
4961                 if (this._container) { return; }
4962
4963                 this._container = L.DomUtil.create('div', 'leaflet-layer ' + (this.options.className || ''));
4964                 this._updateZIndex();
4965
4966                 if (this.options.opacity < 1) {
4967                         this._updateOpacity();
4968                 }
4969
4970                 this.getPane().appendChild(this._container);
4971         },
4972
4973         _updateLevels: function () {
4974
4975                 var zoom = this._tileZoom,
4976                     maxZoom = this.options.maxZoom;
4977
4978                 if (zoom === undefined) { return undefined; }
4979
4980                 for (var z in this._levels) {
4981                         if (this._levels[z].el.children.length || z === zoom) {
4982                                 this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
4983                         } else {
4984                                 L.DomUtil.remove(this._levels[z].el);
4985                                 this._removeTilesAtZoom(z);
4986                                 delete this._levels[z];
4987                         }
4988                 }
4989
4990                 var level = this._levels[zoom],
4991                     map = this._map;
4992
4993                 if (!level) {
4994                         level = this._levels[zoom] = {};
4995
4996                         level.el = L.DomUtil.create('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
4997                         level.el.style.zIndex = maxZoom;
4998
4999                         level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
5000                         level.zoom = zoom;
5001
5002                         this._setZoomTransform(level, map.getCenter(), map.getZoom());
5003
5004                         // force the browser to consider the newly added element for transition
5005                         L.Util.falseFn(level.el.offsetWidth);
5006                 }
5007
5008                 this._level = level;
5009
5010                 return level;
5011         },
5012
5013         _pruneTiles: function () {
5014                 if (!this._map) {
5015                         return;
5016                 }
5017
5018                 var key, tile;
5019
5020                 var zoom = this._map.getZoom();
5021                 if (zoom > this.options.maxZoom ||
5022                         zoom < this.options.minZoom) {
5023                         this._removeAllTiles();
5024                         return;
5025                 }
5026
5027                 for (key in this._tiles) {
5028                         tile = this._tiles[key];
5029                         tile.retain = tile.current;
5030                 }
5031
5032                 for (key in this._tiles) {
5033                         tile = this._tiles[key];
5034                         if (tile.current && !tile.active) {
5035                                 var coords = tile.coords;
5036                                 if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
5037                                         this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
5038                                 }
5039                         }
5040                 }
5041
5042                 for (key in this._tiles) {
5043                         if (!this._tiles[key].retain) {
5044                                 this._removeTile(key);
5045                         }
5046                 }
5047         },
5048
5049         _removeTilesAtZoom: function (zoom) {
5050                 for (var key in this._tiles) {
5051                         if (this._tiles[key].coords.z !== zoom) {
5052                                 continue;
5053                         }
5054                         this._removeTile(key);
5055                 }
5056         },
5057
5058         _removeAllTiles: function () {
5059                 for (var key in this._tiles) {
5060                         this._removeTile(key);
5061                 }
5062         },
5063
5064         _invalidateAll: function () {
5065                 for (var z in this._levels) {
5066                         L.DomUtil.remove(this._levels[z].el);
5067                         delete this._levels[z];
5068                 }
5069                 this._removeAllTiles();
5070
5071                 this._tileZoom = null;
5072         },
5073
5074         _retainParent: function (x, y, z, minZoom) {
5075                 var x2 = Math.floor(x / 2),
5076                     y2 = Math.floor(y / 2),
5077                     z2 = z - 1,
5078                     coords2 = new L.Point(+x2, +y2);
5079                 coords2.z = +z2;
5080
5081                 var key = this._tileCoordsToKey(coords2),
5082                     tile = this._tiles[key];
5083
5084                 if (tile && tile.active) {
5085                         tile.retain = true;
5086                         return true;
5087
5088                 } else if (tile && tile.loaded) {
5089                         tile.retain = true;
5090                 }
5091
5092                 if (z2 > minZoom) {
5093                         return this._retainParent(x2, y2, z2, minZoom);
5094                 }
5095
5096                 return false;
5097         },
5098
5099         _retainChildren: function (x, y, z, maxZoom) {
5100
5101                 for (var i = 2 * x; i < 2 * x + 2; i++) {
5102                         for (var j = 2 * y; j < 2 * y + 2; j++) {
5103
5104                                 var coords = new L.Point(i, j);
5105                                 coords.z = z + 1;
5106
5107                                 var key = this._tileCoordsToKey(coords),
5108                                     tile = this._tiles[key];
5109
5110                                 if (tile && tile.active) {
5111                                         tile.retain = true;
5112                                         continue;
5113
5114                                 } else if (tile && tile.loaded) {
5115                                         tile.retain = true;
5116                                 }
5117
5118                                 if (z + 1 < maxZoom) {
5119                                         this._retainChildren(i, j, z + 1, maxZoom);
5120                                 }
5121                         }
5122                 }
5123         },
5124
5125         _resetView: function (e) {
5126                 var animating = e && (e.pinch || e.flyTo);
5127                 this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
5128         },
5129
5130         _animateZoom: function (e) {
5131                 this._setView(e.center, e.zoom, true, e.noUpdate);
5132         },
5133
5134         _setView: function (center, zoom, noPrune, noUpdate) {
5135                 var tileZoom = Math.round(zoom);
5136                 if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
5137                     (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
5138                         tileZoom = undefined;
5139                 }
5140
5141                 var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);
5142
5143                 if (!noUpdate || tileZoomChanged) {
5144
5145                         this._tileZoom = tileZoom;
5146
5147                         if (this._abortLoading) {
5148                                 this._abortLoading();
5149                         }
5150
5151                         this._updateLevels();
5152                         this._resetGrid();
5153
5154                         if (tileZoom !== undefined) {
5155                                 this._update(center);
5156                         }
5157
5158                         if (!noPrune) {
5159                                 this._pruneTiles();
5160                         }
5161
5162                         // Flag to prevent _updateOpacity from pruning tiles during
5163                         // a zoom anim or a pinch gesture
5164                         this._noPrune = !!noPrune;
5165                 }
5166
5167                 this._setZoomTransforms(center, zoom);
5168         },
5169
5170         _setZoomTransforms: function (center, zoom) {
5171                 for (var i in this._levels) {
5172                         this._setZoomTransform(this._levels[i], center, zoom);
5173                 }
5174         },
5175
5176         _setZoomTransform: function (level, center, zoom) {
5177                 var scale = this._map.getZoomScale(zoom, level.zoom),
5178                     translate = level.origin.multiplyBy(scale)
5179                         .subtract(this._map._getNewPixelOrigin(center, zoom)).round();
5180
5181                 if (L.Browser.any3d) {
5182                         L.DomUtil.setTransform(level.el, translate, scale);
5183                 } else {
5184                         L.DomUtil.setPosition(level.el, translate);
5185                 }
5186         },
5187
5188         _resetGrid: function () {
5189                 var map = this._map,
5190                     crs = map.options.crs,
5191                     tileSize = this._tileSize = this.getTileSize(),
5192                     tileZoom = this._tileZoom;
5193
5194                 var bounds = this._map.getPixelWorldBounds(this._tileZoom);
5195                 if (bounds) {
5196                         this._globalTileRange = this._pxBoundsToTileRange(bounds);
5197                 }
5198
5199                 this._wrapX = crs.wrapLng && !this.options.noWrap && [
5200                         Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
5201                         Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
5202                 ];
5203                 this._wrapY = crs.wrapLat && !this.options.noWrap && [
5204                         Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
5205                         Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
5206                 ];
5207         },
5208
5209         _onMoveEnd: function () {
5210                 if (!this._map || this._map._animatingZoom) { return; }
5211
5212                 this._update();
5213         },
5214
5215         _getTiledPixelBounds: function (center) {
5216                 var map = this._map,
5217                     mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
5218                     scale = map.getZoomScale(mapZoom, this._tileZoom),
5219                     pixelCenter = map.project(center, this._tileZoom).floor(),
5220                     halfSize = map.getSize().divideBy(scale * 2);
5221
5222                 return new L.Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
5223         },
5224
5225         // Private method to load tiles in the grid's active zoom level according to map bounds
5226         _update: function (center) {
5227                 var map = this._map;
5228                 if (!map) { return; }
5229                 var zoom = map.getZoom();
5230
5231                 if (center === undefined) { center = map.getCenter(); }
5232                 if (this._tileZoom === undefined) { return; }   // if out of minzoom/maxzoom
5233
5234                 var pixelBounds = this._getTiledPixelBounds(center),
5235                     tileRange = this._pxBoundsToTileRange(pixelBounds),
5236                     tileCenter = tileRange.getCenter(),
5237                     queue = [],
5238                     margin = this.options.keepBuffer,
5239                     noPruneRange = new L.Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
5240                                               tileRange.getTopRight().add([margin, -margin]));
5241
5242                 for (var key in this._tiles) {
5243                         var c = this._tiles[key].coords;
5244                         if (c.z !== this._tileZoom || !noPruneRange.contains(L.point(c.x, c.y))) {
5245                                 this._tiles[key].current = false;
5246                         }
5247                 }
5248
5249                 // _update just loads more tiles. If the tile zoom level differs too much
5250                 // from the map's, let _setView reset levels and prune old tiles.
5251                 if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }
5252
5253                 // create a queue of coordinates to load tiles from
5254                 for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
5255                         for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
5256                                 var coords = new L.Point(i, j);
5257                                 coords.z = this._tileZoom;
5258
5259                                 if (!this._isValidTile(coords)) { continue; }
5260
5261                                 var tile = this._tiles[this._tileCoordsToKey(coords)];
5262                                 if (tile) {
5263                                         tile.current = true;
5264                                 } else {
5265                                         queue.push(coords);
5266                                 }
5267                         }
5268                 }
5269
5270                 // sort tile queue to load tiles in order of their distance to center
5271                 queue.sort(function (a, b) {
5272                         return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
5273                 });
5274
5275                 if (queue.length !== 0) {
5276                         // if it's the first batch of tiles to load
5277                         if (!this._loading) {
5278                                 this._loading = true;
5279                                 // @event loading: Event
5280                                 // Fired when the grid layer starts loading tiles.
5281                                 this.fire('loading');
5282                         }
5283
5284                         // create DOM fragment to append tiles in one batch
5285                         var fragment = document.createDocumentFragment();
5286
5287                         for (i = 0; i < queue.length; i++) {
5288                                 this._addTile(queue[i], fragment);
5289                         }
5290
5291                         this._level.el.appendChild(fragment);
5292                 }
5293         },
5294
5295         _isValidTile: function (coords) {
5296                 var crs = this._map.options.crs;
5297
5298                 if (!crs.infinite) {
5299                         // don't load tile if it's out of bounds and not wrapped
5300                         var bounds = this._globalTileRange;
5301                         if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
5302                             (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
5303                 }
5304
5305                 if (!this.options.bounds) { return true; }
5306
5307                 // don't load tile if it doesn't intersect the bounds in options
5308                 var tileBounds = this._tileCoordsToBounds(coords);
5309                 return L.latLngBounds(this.options.bounds).overlaps(tileBounds);
5310         },
5311
5312         _keyToBounds: function (key) {
5313                 return this._tileCoordsToBounds(this._keyToTileCoords(key));
5314         },
5315
5316         // converts tile coordinates to its geographical bounds
5317         _tileCoordsToBounds: function (coords) {
5318
5319                 var map = this._map,
5320                     tileSize = this.getTileSize(),
5321
5322                     nwPoint = coords.scaleBy(tileSize),
5323                     sePoint = nwPoint.add(tileSize),
5324
5325                     nw = map.unproject(nwPoint, coords.z),
5326                     se = map.unproject(sePoint, coords.z),
5327                     bounds = new L.LatLngBounds(nw, se);
5328
5329                 if (!this.options.noWrap) {
5330                         map.wrapLatLngBounds(bounds);
5331                 }
5332
5333                 return bounds;
5334         },
5335
5336         // converts tile coordinates to key for the tile cache
5337         _tileCoordsToKey: function (coords) {
5338                 return coords.x + ':' + coords.y + ':' + coords.z;
5339         },
5340
5341         // converts tile cache key to coordinates
5342         _keyToTileCoords: function (key) {
5343                 var k = key.split(':'),
5344                     coords = new L.Point(+k[0], +k[1]);
5345                 coords.z = +k[2];
5346                 return coords;
5347         },
5348
5349         _removeTile: function (key) {
5350                 var tile = this._tiles[key];
5351                 if (!tile) { return; }
5352
5353                 L.DomUtil.remove(tile.el);
5354
5355                 delete this._tiles[key];
5356
5357                 // @event tileunload: TileEvent
5358                 // Fired when a tile is removed (e.g. when a tile goes off the screen).
5359                 this.fire('tileunload', {
5360                         tile: tile.el,
5361                         coords: this._keyToTileCoords(key)
5362                 });
5363         },
5364
5365         _initTile: function (tile) {
5366                 L.DomUtil.addClass(tile, 'leaflet-tile');
5367
5368                 var tileSize = this.getTileSize();
5369                 tile.style.width = tileSize.x + 'px';
5370                 tile.style.height = tileSize.y + 'px';
5371
5372                 tile.onselectstart = L.Util.falseFn;
5373                 tile.onmousemove = L.Util.falseFn;
5374
5375                 // update opacity on tiles in IE7-8 because of filter inheritance problems
5376                 if (L.Browser.ielt9 && this.options.opacity < 1) {
5377                         L.DomUtil.setOpacity(tile, this.options.opacity);
5378                 }
5379
5380                 // without this hack, tiles disappear after zoom on Chrome for Android
5381                 // https://github.com/Leaflet/Leaflet/issues/2078
5382                 if (L.Browser.android && !L.Browser.android23) {
5383                         tile.style.WebkitBackfaceVisibility = 'hidden';
5384                 }
5385         },
5386
5387         _addTile: function (coords, container) {
5388                 var tilePos = this._getTilePos(coords),
5389                     key = this._tileCoordsToKey(coords);
5390
5391                 var tile = this.createTile(this._wrapCoords(coords), L.bind(this._tileReady, this, coords));
5392
5393                 this._initTile(tile);
5394
5395                 // if createTile is defined with a second argument ("done" callback),
5396                 // we know that tile is async and will be ready later; otherwise
5397                 if (this.createTile.length < 2) {
5398                         // mark tile as ready, but delay one frame for opacity animation to happen
5399                         L.Util.requestAnimFrame(L.bind(this._tileReady, this, coords, null, tile));
5400                 }
5401
5402                 L.DomUtil.setPosition(tile, tilePos);
5403
5404                 // save tile in cache
5405                 this._tiles[key] = {
5406                         el: tile,
5407                         coords: coords,
5408                         current: true
5409                 };
5410
5411                 container.appendChild(tile);
5412                 // @event tileloadstart: TileEvent
5413                 // Fired when a tile is requested and starts loading.
5414                 this.fire('tileloadstart', {
5415                         tile: tile,
5416                         coords: coords
5417                 });
5418         },
5419
5420         _tileReady: function (coords, err, tile) {
5421                 if (!this._map) { return; }
5422
5423                 if (err) {
5424                         // @event tileerror: TileErrorEvent
5425                         // Fired when there is an error loading a tile.
5426                         this.fire('tileerror', {
5427                                 error: err,
5428                                 tile: tile,
5429                                 coords: coords
5430                         });
5431                 }
5432
5433                 var key = this._tileCoordsToKey(coords);
5434
5435                 tile = this._tiles[key];
5436                 if (!tile) { return; }
5437
5438                 tile.loaded = +new Date();
5439                 if (this._map._fadeAnimated) {
5440                         L.DomUtil.setOpacity(tile.el, 0);
5441                         L.Util.cancelAnimFrame(this._fadeFrame);
5442                         this._fadeFrame = L.Util.requestAnimFrame(this._updateOpacity, this);
5443                 } else {
5444                         tile.active = true;
5445                         this._pruneTiles();
5446                 }
5447
5448                 if (!err) {
5449                         L.DomUtil.addClass(tile.el, 'leaflet-tile-loaded');
5450
5451                         // @event tileload: TileEvent
5452                         // Fired when a tile loads.
5453                         this.fire('tileload', {
5454                                 tile: tile.el,
5455                                 coords: coords
5456                         });
5457                 }
5458
5459                 if (this._noTilesToLoad()) {
5460                         this._loading = false;
5461                         // @event load: Event
5462                         // Fired when the grid layer loaded all visible tiles.
5463                         this.fire('load');
5464
5465                         if (L.Browser.ielt9 || !this._map._fadeAnimated) {
5466                                 L.Util.requestAnimFrame(this._pruneTiles, this);
5467                         } else {
5468                                 // Wait a bit more than 0.2 secs (the duration of the tile fade-in)
5469                                 // to trigger a pruning.
5470                                 setTimeout(L.bind(this._pruneTiles, this), 250);
5471                         }
5472                 }
5473         },
5474
5475         _getTilePos: function (coords) {
5476                 return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
5477         },
5478
5479         _wrapCoords: function (coords) {
5480                 var newCoords = new L.Point(
5481                         this._wrapX ? L.Util.wrapNum(coords.x, this._wrapX) : coords.x,
5482                         this._wrapY ? L.Util.wrapNum(coords.y, this._wrapY) : coords.y);
5483                 newCoords.z = coords.z;
5484                 return newCoords;
5485         },
5486
5487         _pxBoundsToTileRange: function (bounds) {
5488                 var tileSize = this.getTileSize();
5489                 return new L.Bounds(
5490                         bounds.min.unscaleBy(tileSize).floor(),
5491                         bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
5492         },
5493
5494         _noTilesToLoad: function () {
5495                 for (var key in this._tiles) {
5496                         if (!this._tiles[key].loaded) { return false; }
5497                 }
5498                 return true;
5499         }
5500 });
5501
5502 // @factory L.gridLayer(options?: GridLayer options)
5503 // Creates a new instance of GridLayer with the supplied options.
5504 L.gridLayer = function (options) {
5505         return new L.GridLayer(options);
5506 };
5507
5508
5509
5510 /*
5511  * @class TileLayer
5512  * @inherits GridLayer
5513  * @aka L.TileLayer
5514  * Used to load and display tile layers on the map. Extends `GridLayer`.
5515  *
5516  * @example
5517  *
5518  * ```js
5519  * L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar'}).addTo(map);
5520  * ```
5521  *
5522  * @section URL template
5523  * @example
5524  *
5525  * A string of the following form:
5526  *
5527  * ```
5528  * 'http://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
5529  * ```
5530  *
5531  * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add @2x to the URL to load retina tiles.
5532  *
5533  * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
5534  *
5535  * ```
5536  * L.tileLayer('http://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
5537  * ```
5538  */
5539
5540
5541 L.TileLayer = L.GridLayer.extend({
5542
5543         // @section
5544         // @aka TileLayer options
5545         options: {
5546                 // @option minZoom: Number = 0
5547                 // Minimum zoom number.
5548                 minZoom: 0,
5549
5550                 // @option maxZoom: Number = 18
5551                 // Maximum zoom number.
5552                 maxZoom: 18,
5553
5554                 // @option maxNativeZoom: Number = null
5555                 // Maximum zoom number the tile source has available. If it is specified,
5556                 // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
5557                 // from `maxNativeZoom` level and auto-scaled.
5558                 maxNativeZoom: null,
5559
5560                 // @option minNativeZoom: Number = null
5561                 // Minimum zoom number the tile source has available. If it is specified,
5562                 // the tiles on all zoom levels lower than `minNativeZoom` will be loaded
5563                 // from `minNativeZoom` level and auto-scaled.
5564                 minNativeZoom: null,
5565
5566                 // @option subdomains: String|String[] = 'abc'
5567                 // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
5568                 subdomains: 'abc',
5569
5570                 // @option errorTileUrl: String = ''
5571                 // URL to the tile image to show in place of the tile that failed to load.
5572                 errorTileUrl: '',
5573
5574                 // @option zoomOffset: Number = 0
5575                 // The zoom number used in tile URLs will be offset with this value.
5576                 zoomOffset: 0,
5577
5578                 // @option tms: Boolean = false
5579                 // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
5580                 tms: false,
5581
5582                 // @option zoomReverse: Boolean = false
5583                 // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
5584                 zoomReverse: false,
5585
5586                 // @option detectRetina: Boolean = false
5587                 // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
5588                 detectRetina: false,
5589
5590                 // @option crossOrigin: Boolean = false
5591                 // If true, all tiles will have their crossOrigin attribute set to ''. This is needed if you want to access tile pixel data.
5592                 crossOrigin: false
5593         },
5594
5595         initialize: function (url, options) {
5596
5597                 this._url = url;
5598
5599                 options = L.setOptions(this, options);
5600
5601                 // detecting retina displays, adjusting tileSize and zoom levels
5602                 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
5603
5604                         options.tileSize = Math.floor(options.tileSize / 2);
5605
5606                         if (!options.zoomReverse) {
5607                                 options.zoomOffset++;
5608                                 options.maxZoom--;
5609                         } else {
5610                                 options.zoomOffset--;
5611                                 options.minZoom++;
5612                         }
5613
5614                         options.minZoom = Math.max(0, options.minZoom);
5615                 }
5616
5617                 if (typeof options.subdomains === 'string') {
5618                         options.subdomains = options.subdomains.split('');
5619                 }
5620
5621                 // for https://github.com/Leaflet/Leaflet/issues/137
5622                 if (!L.Browser.android) {
5623                         this.on('tileunload', this._onTileRemove);
5624                 }
5625         },
5626
5627         // @method setUrl(url: String, noRedraw?: Boolean): this
5628         // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
5629         setUrl: function (url, noRedraw) {
5630                 this._url = url;
5631
5632                 if (!noRedraw) {
5633                         this.redraw();
5634                 }
5635                 return this;
5636         },
5637
5638         // @method createTile(coords: Object, done?: Function): HTMLElement
5639         // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
5640         // to return an `<img>` HTML element with the appropiate image URL given `coords`. The `done`
5641         // callback is called when the tile has been loaded.
5642         createTile: function (coords, done) {
5643                 var tile = document.createElement('img');
5644
5645                 L.DomEvent.on(tile, 'load', L.bind(this._tileOnLoad, this, done, tile));
5646                 L.DomEvent.on(tile, 'error', L.bind(this._tileOnError, this, done, tile));
5647
5648                 if (this.options.crossOrigin) {
5649                         tile.crossOrigin = '';
5650                 }
5651
5652                 /*
5653                  Alt tag is set to empty string to keep screen readers from reading URL and for compliance reasons
5654                  http://www.w3.org/TR/WCAG20-TECHS/H67
5655                 */
5656                 tile.alt = '';
5657
5658                 /*
5659                  Set role="presentation" to force screen readers to ignore this
5660                  https://www.w3.org/TR/wai-aria/roles#textalternativecomputation
5661                 */
5662                 tile.setAttribute('role', 'presentation');
5663
5664                 tile.src = this.getTileUrl(coords);
5665
5666                 return tile;
5667         },
5668
5669         // @section Extension methods
5670         // @uninheritable
5671         // Layers extending `TileLayer` might reimplement the following method.
5672         // @method getTileUrl(coords: Object): String
5673         // Called only internally, returns the URL for a tile given its coordinates.
5674         // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
5675         getTileUrl: function (coords) {
5676                 var data = {
5677                         r: L.Browser.retina ? '@2x' : '',
5678                         s: this._getSubdomain(coords),
5679                         x: coords.x,
5680                         y: coords.y,
5681                         z: this._getZoomForUrl()
5682                 };
5683                 if (this._map && !this._map.options.crs.infinite) {
5684                         var invertedY = this._globalTileRange.max.y - coords.y;
5685                         if (this.options.tms) {
5686                                 data['y'] = invertedY;
5687                         }
5688                         data['-y'] = invertedY;
5689                 }
5690
5691                 return L.Util.template(this._url, L.extend(data, this.options));
5692         },
5693
5694         _tileOnLoad: function (done, tile) {
5695                 // For https://github.com/Leaflet/Leaflet/issues/3332
5696                 if (L.Browser.ielt9) {
5697                         setTimeout(L.bind(done, this, null, tile), 0);
5698                 } else {
5699                         done(null, tile);
5700                 }
5701         },
5702
5703         _tileOnError: function (done, tile, e) {
5704                 var errorUrl = this.options.errorTileUrl;
5705                 if (errorUrl && tile.src !== errorUrl) {
5706                         tile.src = errorUrl;
5707                 }
5708                 done(e, tile);
5709         },
5710
5711         getTileSize: function () {
5712                 var map = this._map,
5713                 tileSize = L.GridLayer.prototype.getTileSize.call(this),
5714                 zoom = this._tileZoom + this.options.zoomOffset,
5715                 minNativeZoom = this.options.minNativeZoom,
5716                 maxNativeZoom = this.options.maxNativeZoom;
5717
5718                 // decrease tile size when scaling below minNativeZoom
5719                 if (minNativeZoom !== null && zoom < minNativeZoom) {
5720                         return tileSize.divideBy(map.getZoomScale(minNativeZoom, zoom)).round();
5721                 }
5722
5723                 // increase tile size when scaling above maxNativeZoom
5724                 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5725                         return tileSize.divideBy(map.getZoomScale(maxNativeZoom, zoom)).round();
5726                 }
5727
5728                 return tileSize;
5729         },
5730
5731         _onTileRemove: function (e) {
5732                 e.tile.onload = null;
5733         },
5734
5735         _getZoomForUrl: function () {
5736                 var zoom = this._tileZoom,
5737                 maxZoom = this.options.maxZoom,
5738                 zoomReverse = this.options.zoomReverse,
5739                 zoomOffset = this.options.zoomOffset,
5740                 minNativeZoom = this.options.minNativeZoom,
5741                 maxNativeZoom = this.options.maxNativeZoom;
5742
5743                 if (zoomReverse) {
5744                         zoom = maxZoom - zoom;
5745                 }
5746
5747                 zoom += zoomOffset;
5748
5749                 if (minNativeZoom !== null && zoom < minNativeZoom) {
5750                         return minNativeZoom;
5751                 }
5752
5753                 if (maxNativeZoom !== null && zoom > maxNativeZoom) {
5754                         return maxNativeZoom;
5755                 }
5756
5757                 return zoom;
5758         },
5759
5760         _getSubdomain: function (tilePoint) {
5761                 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
5762                 return this.options.subdomains[index];
5763         },
5764
5765         // stops loading all tiles in the background layer
5766         _abortLoading: function () {
5767                 var i, tile;
5768                 for (i in this._tiles) {
5769                         if (this._tiles[i].coords.z !== this._tileZoom) {
5770                                 tile = this._tiles[i].el;
5771
5772                                 tile.onload = L.Util.falseFn;
5773                                 tile.onerror = L.Util.falseFn;
5774
5775                                 if (!tile.complete) {
5776                                         tile.src = L.Util.emptyImageUrl;
5777                                         L.DomUtil.remove(tile);
5778                                 }
5779                         }
5780                 }
5781         }
5782 });
5783
5784
5785 // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
5786 // Instantiates a tile layer object given a `URL template` and optionally an options object.
5787
5788 L.tileLayer = function (url, options) {
5789         return new L.TileLayer(url, options);
5790 };
5791
5792
5793
5794 /*
5795  * @class TileLayer.WMS
5796  * @inherits TileLayer
5797  * @aka L.TileLayer.WMS
5798  * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
5799  *
5800  * @example
5801  *
5802  * ```js
5803  * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
5804  *      layers: 'nexrad-n0r-900913',
5805  *      format: 'image/png',
5806  *      transparent: true,
5807  *      attribution: "Weather data © 2012 IEM Nexrad"
5808  * });
5809  * ```
5810  */
5811
5812 L.TileLayer.WMS = L.TileLayer.extend({
5813
5814         // @section
5815         // @aka TileLayer.WMS options
5816         // If any custom options not documented here are used, they will be sent to the
5817         // WMS server as extra parameters in each request URL. This can be useful for
5818         // [non-standard vendor WMS parameters](http://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
5819         defaultWmsParams: {
5820                 service: 'WMS',
5821                 request: 'GetMap',
5822
5823                 // @option layers: String = ''
5824                 // **(required)** Comma-separated list of WMS layers to show.
5825                 layers: '',
5826
5827                 // @option styles: String = ''
5828                 // Comma-separated list of WMS styles.
5829                 styles: '',
5830
5831                 // @option format: String = 'image/jpeg'
5832                 // WMS image format (use `'image/png'` for layers with transparency).
5833                 format: 'image/jpeg',
5834
5835                 // @option transparent: Boolean = false
5836                 // If `true`, the WMS service will return images with transparency.
5837                 transparent: false,
5838
5839                 // @option version: String = '1.1.1'
5840                 // Version of the WMS service to use
5841                 version: '1.1.1'
5842         },
5843
5844         options: {
5845                 // @option crs: CRS = null
5846                 // Coordinate Reference System to use for the WMS requests, defaults to
5847                 // map CRS. Don't change this if you're not sure what it means.
5848                 crs: null,
5849
5850                 // @option uppercase: Boolean = false
5851                 // If `true`, WMS request parameter keys will be uppercase.
5852                 uppercase: false
5853         },
5854
5855         initialize: function (url, options) {
5856
5857                 this._url = url;
5858
5859                 var wmsParams = L.extend({}, this.defaultWmsParams);
5860
5861                 // all keys that are not TileLayer options go to WMS params
5862                 for (var i in options) {
5863                         if (!(i in this.options)) {
5864                                 wmsParams[i] = options[i];
5865                         }
5866                 }
5867
5868                 options = L.setOptions(this, options);
5869
5870                 wmsParams.width = wmsParams.height = options.tileSize * (options.detectRetina && L.Browser.retina ? 2 : 1);
5871
5872                 this.wmsParams = wmsParams;
5873         },
5874
5875         onAdd: function (map) {
5876
5877                 this._crs = this.options.crs || map.options.crs;
5878                 this._wmsVersion = parseFloat(this.wmsParams.version);
5879
5880                 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
5881                 this.wmsParams[projectionKey] = this._crs.code;
5882
5883                 L.TileLayer.prototype.onAdd.call(this, map);
5884         },
5885
5886         getTileUrl: function (coords) {
5887
5888                 var tileBounds = this._tileCoordsToBounds(coords),
5889                     nw = this._crs.project(tileBounds.getNorthWest()),
5890                     se = this._crs.project(tileBounds.getSouthEast()),
5891
5892                     bbox = (this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
5893                             [se.y, nw.x, nw.y, se.x] :
5894                             [nw.x, se.y, se.x, nw.y]).join(','),
5895
5896                     url = L.TileLayer.prototype.getTileUrl.call(this, coords);
5897
5898                 return url +
5899                         L.Util.getParamString(this.wmsParams, url, this.options.uppercase) +
5900                         (this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
5901         },
5902
5903         // @method setParams(params: Object, noRedraw?: Boolean): this
5904         // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
5905         setParams: function (params, noRedraw) {
5906
5907                 L.extend(this.wmsParams, params);
5908
5909                 if (!noRedraw) {
5910                         this.redraw();
5911                 }
5912
5913                 return this;
5914         }
5915 });
5916
5917
5918 // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
5919 // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
5920 L.tileLayer.wms = function (url, options) {
5921         return new L.TileLayer.WMS(url, options);
5922 };
5923
5924
5925
5926 /*
5927  * @class ImageOverlay
5928  * @aka L.ImageOverlay
5929  * @inherits Interactive layer
5930  *
5931  * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
5932  *
5933  * @example
5934  *
5935  * ```js
5936  * var imageUrl = 'http://www.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
5937  *      imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
5938  * L.imageOverlay(imageUrl, imageBounds).addTo(map);
5939  * ```
5940  */
5941
5942 L.ImageOverlay = L.Layer.extend({
5943
5944         // @section
5945         // @aka ImageOverlay options
5946         options: {
5947                 // @option opacity: Number = 1.0
5948                 // The opacity of the image overlay.
5949                 opacity: 1,
5950
5951                 // @option alt: String = ''
5952                 // Text for the `alt` attribute of the image (useful for accessibility).
5953                 alt: '',
5954
5955                 // @option interactive: Boolean = false
5956                 // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
5957                 interactive: false,
5958
5959                 // @option crossOrigin: Boolean = false
5960                 // If true, the image will have its crossOrigin attribute set to ''. This is needed if you want to access image pixel data.
5961                 crossOrigin: false
5962         },
5963
5964         initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
5965                 this._url = url;
5966                 this._bounds = L.latLngBounds(bounds);
5967
5968                 L.setOptions(this, options);
5969         },
5970
5971         onAdd: function () {
5972                 if (!this._image) {
5973                         this._initImage();
5974
5975                         if (this.options.opacity < 1) {
5976                                 this._updateOpacity();
5977                         }
5978                 }
5979
5980                 if (this.options.interactive) {
5981                         L.DomUtil.addClass(this._image, 'leaflet-interactive');
5982                         this.addInteractiveTarget(this._image);
5983                 }
5984
5985                 this.getPane().appendChild(this._image);
5986                 this._reset();
5987         },
5988
5989         onRemove: function () {
5990                 L.DomUtil.remove(this._image);
5991                 if (this.options.interactive) {
5992                         this.removeInteractiveTarget(this._image);
5993                 }
5994         },
5995
5996         // @method setOpacity(opacity: Number): this
5997         // Sets the opacity of the overlay.
5998         setOpacity: function (opacity) {
5999                 this.options.opacity = opacity;
6000
6001                 if (this._image) {
6002                         this._updateOpacity();
6003                 }
6004                 return this;
6005         },
6006
6007         setStyle: function (styleOpts) {
6008                 if (styleOpts.opacity) {
6009                         this.setOpacity(styleOpts.opacity);
6010                 }
6011                 return this;
6012         },
6013
6014         // @method bringToFront(): this
6015         // Brings the layer to the top of all overlays.
6016         bringToFront: function () {
6017                 if (this._map) {
6018                         L.DomUtil.toFront(this._image);
6019                 }
6020                 return this;
6021         },
6022
6023         // @method bringToBack(): this
6024         // Brings the layer to the bottom of all overlays.
6025         bringToBack: function () {
6026                 if (this._map) {
6027                         L.DomUtil.toBack(this._image);
6028                 }
6029                 return this;
6030         },
6031
6032         // @method setUrl(url: String): this
6033         // Changes the URL of the image.
6034         setUrl: function (url) {
6035                 this._url = url;
6036
6037                 if (this._image) {
6038                         this._image.src = url;
6039                 }
6040                 return this;
6041         },
6042
6043         // @method setBounds(bounds: LatLngBounds): this
6044         // Update the bounds that this ImageOverlay covers
6045         setBounds: function (bounds) {
6046                 this._bounds = bounds;
6047
6048                 if (this._map) {
6049                         this._reset();
6050                 }
6051                 return this;
6052         },
6053
6054         getEvents: function () {
6055                 var events = {
6056                         zoom: this._reset,
6057                         viewreset: this._reset
6058                 };
6059
6060                 if (this._zoomAnimated) {
6061                         events.zoomanim = this._animateZoom;
6062                 }
6063
6064                 return events;
6065         },
6066
6067         // @method getBounds(): LatLngBounds
6068         // Get the bounds that this ImageOverlay covers
6069         getBounds: function () {
6070                 return this._bounds;
6071         },
6072
6073         // @method getElement(): HTMLElement
6074         // Get the img element that represents the ImageOverlay on the map
6075         getElement: function () {
6076                 return this._image;
6077         },
6078
6079         _initImage: function () {
6080                 var img = this._image = L.DomUtil.create('img',
6081                                 'leaflet-image-layer ' + (this._zoomAnimated ? 'leaflet-zoom-animated' : ''));
6082
6083                 img.onselectstart = L.Util.falseFn;
6084                 img.onmousemove = L.Util.falseFn;
6085
6086                 img.onload = L.bind(this.fire, this, 'load');
6087
6088                 if (this.options.crossOrigin) {
6089                         img.crossOrigin = '';
6090                 }
6091
6092                 img.src = this._url;
6093                 img.alt = this.options.alt;
6094         },
6095
6096         _animateZoom: function (e) {
6097                 var scale = this._map.getZoomScale(e.zoom),
6098                     offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;
6099
6100                 L.DomUtil.setTransform(this._image, offset, scale);
6101         },
6102
6103         _reset: function () {
6104                 var image = this._image,
6105                     bounds = new L.Bounds(
6106                         this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
6107                         this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
6108                     size = bounds.getSize();
6109
6110                 L.DomUtil.setPosition(image, bounds.min);
6111
6112                 image.style.width  = size.x + 'px';
6113                 image.style.height = size.y + 'px';
6114         },
6115
6116         _updateOpacity: function () {
6117                 L.DomUtil.setOpacity(this._image, this.options.opacity);
6118         }
6119 });
6120
6121 // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
6122 // Instantiates an image overlay object given the URL of the image and the
6123 // geographical bounds it is tied to.
6124 L.imageOverlay = function (url, bounds, options) {
6125         return new L.ImageOverlay(url, bounds, options);
6126 };
6127
6128
6129
6130 /*
6131  * @class Icon
6132  * @aka L.Icon
6133  * @inherits Layer
6134  *
6135  * Represents an icon to provide when creating a marker.
6136  *
6137  * @example
6138  *
6139  * ```js
6140  * var myIcon = L.icon({
6141  *     iconUrl: 'my-icon.png',
6142  *     iconRetinaUrl: 'my-icon@2x.png',
6143  *     iconSize: [38, 95],
6144  *     iconAnchor: [22, 94],
6145  *     popupAnchor: [-3, -76],
6146  *     shadowUrl: 'my-icon-shadow.png',
6147  *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
6148  *     shadowSize: [68, 95],
6149  *     shadowAnchor: [22, 94]
6150  * });
6151  *
6152  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6153  * ```
6154  *
6155  * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
6156  *
6157  */
6158
6159 L.Icon = L.Class.extend({
6160
6161         /* @section
6162          * @aka Icon options
6163          *
6164          * @option iconUrl: String = null
6165          * **(required)** The URL to the icon image (absolute or relative to your script path).
6166          *
6167          * @option iconRetinaUrl: String = null
6168          * The URL to a retina sized version of the icon image (absolute or relative to your
6169          * script path). Used for Retina screen devices.
6170          *
6171          * @option iconSize: Point = null
6172          * Size of the icon image in pixels.
6173          *
6174          * @option iconAnchor: Point = null
6175          * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
6176          * will be aligned so that this point is at the marker's geographical location. Centered
6177          * by default if size is specified, also can be set in CSS with negative margins.
6178          *
6179          * @option popupAnchor: Point = null
6180          * The coordinates of the point from which popups will "open", relative to the icon anchor.
6181          *
6182          * @option shadowUrl: String = null
6183          * The URL to the icon shadow image. If not specified, no shadow image will be created.
6184          *
6185          * @option shadowRetinaUrl: String = null
6186          *
6187          * @option shadowSize: Point = null
6188          * Size of the shadow image in pixels.
6189          *
6190          * @option shadowAnchor: Point = null
6191          * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
6192          * as iconAnchor if not specified).
6193          *
6194          * @option className: String = ''
6195          * A custom class name to assign to both icon and shadow images. Empty by default.
6196          */
6197
6198         initialize: function (options) {
6199                 L.setOptions(this, options);
6200         },
6201
6202         // @method createIcon(oldIcon?: HTMLElement): HTMLElement
6203         // Called internally when the icon has to be shown, returns a `<img>` HTML element
6204         // styled according to the options.
6205         createIcon: function (oldIcon) {
6206                 return this._createIcon('icon', oldIcon);
6207         },
6208
6209         // @method createShadow(oldIcon?: HTMLElement): HTMLElement
6210         // As `createIcon`, but for the shadow beneath it.
6211         createShadow: function (oldIcon) {
6212                 return this._createIcon('shadow', oldIcon);
6213         },
6214
6215         _createIcon: function (name, oldIcon) {
6216                 var src = this._getIconUrl(name);
6217
6218                 if (!src) {
6219                         if (name === 'icon') {
6220                                 throw new Error('iconUrl not set in Icon options (see the docs).');
6221                         }
6222                         return null;
6223                 }
6224
6225                 var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
6226                 this._setIconStyles(img, name);
6227
6228                 return img;
6229         },
6230
6231         _setIconStyles: function (img, name) {
6232                 var options = this.options;
6233                 var sizeOption = options[name + 'Size'];
6234
6235                 if (typeof sizeOption === 'number') {
6236                         sizeOption = [sizeOption, sizeOption];
6237                 }
6238
6239                 var size = L.point(sizeOption),
6240                     anchor = L.point(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
6241                             size && size.divideBy(2, true));
6242
6243                 img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');
6244
6245                 if (anchor) {
6246                         img.style.marginLeft = (-anchor.x) + 'px';
6247                         img.style.marginTop  = (-anchor.y) + 'px';
6248                 }
6249
6250                 if (size) {
6251                         img.style.width  = size.x + 'px';
6252                         img.style.height = size.y + 'px';
6253                 }
6254         },
6255
6256         _createImg: function (src, el) {
6257                 el = el || document.createElement('img');
6258                 el.src = src;
6259                 return el;
6260         },
6261
6262         _getIconUrl: function (name) {
6263                 return L.Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
6264         }
6265 });
6266
6267
6268 // @factory L.icon(options: Icon options)
6269 // Creates an icon instance with the given options.
6270 L.icon = function (options) {
6271         return new L.Icon(options);
6272 };
6273
6274
6275
6276 /*
6277  * @miniclass Icon.Default (Icon)
6278  * @aka L.Icon.Default
6279  * @section
6280  *
6281  * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
6282  * no icon is specified. Points to the blue marker image distributed with Leaflet
6283  * releases.
6284  *
6285  * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
6286  * (which is a set of `Icon options`).
6287  *
6288  * If you want to _completely_ replace the default icon, override the
6289  * `L.Marker.prototype.options.icon` with your own icon instead.
6290  */
6291
6292 L.Icon.Default = L.Icon.extend({
6293
6294         options: {
6295                 iconUrl:       'marker-icon.png',
6296                 iconRetinaUrl: 'marker-icon-2x.png',
6297                 shadowUrl:     'marker-shadow.png',
6298                 iconSize:    [25, 41],
6299                 iconAnchor:  [12, 41],
6300                 popupAnchor: [1, -34],
6301                 tooltipAnchor: [16, -28],
6302                 shadowSize:  [41, 41]
6303         },
6304
6305         _getIconUrl: function (name) {
6306                 if (!L.Icon.Default.imagePath) {        // Deprecated, backwards-compatibility only
6307                         L.Icon.Default.imagePath = this._detectIconPath();
6308                 }
6309
6310                 // @option imagePath: String
6311                 // `L.Icon.Default` will try to auto-detect the absolute location of the
6312                 // blue icon images. If you are placing these images in a non-standard
6313                 // way, set this option to point to the right absolute path.
6314                 return (this.options.imagePath || L.Icon.Default.imagePath) + L.Icon.prototype._getIconUrl.call(this, name);
6315         },
6316
6317         _detectIconPath: function () {
6318                 var el = L.DomUtil.create('div',  'leaflet-default-icon-path', document.body);
6319                 var path = L.DomUtil.getStyle(el, 'background-image') ||
6320                            L.DomUtil.getStyle(el, 'backgroundImage');   // IE8
6321
6322                 document.body.removeChild(el);
6323
6324                 return path.indexOf('url') === 0 ?
6325                         path.replace(/^url\([\"\']?/, '').replace(/marker-icon\.png[\"\']?\)$/, '') : '';
6326         }
6327 });
6328
6329
6330
6331 /*
6332  * @class Marker
6333  * @inherits Interactive layer
6334  * @aka L.Marker
6335  * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
6336  *
6337  * @example
6338  *
6339  * ```js
6340  * L.marker([50.5, 30.5]).addTo(map);
6341  * ```
6342  */
6343
6344 L.Marker = L.Layer.extend({
6345
6346         // @section
6347         // @aka Marker options
6348         options: {
6349                 // @option icon: Icon = *
6350                 // Icon class to use for rendering the marker. See [Icon documentation](#L.Icon) for details on how to customize the marker icon. If not specified, a new `L.Icon.Default` is used.
6351                 icon: new L.Icon.Default(),
6352
6353                 // Option inherited from "Interactive layer" abstract class
6354                 interactive: true,
6355
6356                 // @option draggable: Boolean = false
6357                 // Whether the marker is draggable with mouse/touch or not.
6358                 draggable: false,
6359
6360                 // @option keyboard: Boolean = true
6361                 // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
6362                 keyboard: true,
6363
6364                 // @option title: String = ''
6365                 // Text for the browser tooltip that appear on marker hover (no tooltip by default).
6366                 title: '',
6367
6368                 // @option alt: String = ''
6369                 // Text for the `alt` attribute of the icon image (useful for accessibility).
6370                 alt: '',
6371
6372                 // @option zIndexOffset: Number = 0
6373                 // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
6374                 zIndexOffset: 0,
6375
6376                 // @option opacity: Number = 1.0
6377                 // The opacity of the marker.
6378                 opacity: 1,
6379
6380                 // @option riseOnHover: Boolean = false
6381                 // If `true`, the marker will get on top of others when you hover the mouse over it.
6382                 riseOnHover: false,
6383
6384                 // @option riseOffset: Number = 250
6385                 // The z-index offset used for the `riseOnHover` feature.
6386                 riseOffset: 250,
6387
6388                 // @option pane: String = 'markerPane'
6389                 // `Map pane` where the markers icon will be added.
6390                 pane: 'markerPane',
6391
6392                 // FIXME: shadowPane is no longer a valid option
6393                 nonBubblingEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu']
6394         },
6395
6396         /* @section
6397          *
6398          * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
6399          */
6400
6401         initialize: function (latlng, options) {
6402                 L.setOptions(this, options);
6403                 this._latlng = L.latLng(latlng);
6404         },
6405
6406         onAdd: function (map) {
6407                 this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;
6408
6409                 if (this._zoomAnimated) {
6410                         map.on('zoomanim', this._animateZoom, this);
6411                 }
6412
6413                 this._initIcon();
6414                 this.update();
6415         },
6416
6417         onRemove: function (map) {
6418                 if (this.dragging && this.dragging.enabled()) {
6419                         this.options.draggable = true;
6420                         this.dragging.removeHooks();
6421                 }
6422
6423                 if (this._zoomAnimated) {
6424                         map.off('zoomanim', this._animateZoom, this);
6425                 }
6426
6427                 this._removeIcon();
6428                 this._removeShadow();
6429         },
6430
6431         getEvents: function () {
6432                 return {
6433                         zoom: this.update,
6434                         viewreset: this.update
6435                 };
6436         },
6437
6438         // @method getLatLng: LatLng
6439         // Returns the current geographical position of the marker.
6440         getLatLng: function () {
6441                 return this._latlng;
6442         },
6443
6444         // @method setLatLng(latlng: LatLng): this
6445         // Changes the marker position to the given point.
6446         setLatLng: function (latlng) {
6447                 var oldLatLng = this._latlng;
6448                 this._latlng = L.latLng(latlng);
6449                 this.update();
6450
6451                 // @event move: Event
6452                 // Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
6453                 return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
6454         },
6455
6456         // @method setZIndexOffset(offset: Number): this
6457         // Changes the [zIndex offset](#marker-zindexoffset) of the marker.
6458         setZIndexOffset: function (offset) {
6459                 this.options.zIndexOffset = offset;
6460                 return this.update();
6461         },
6462
6463         // @method setIcon(icon: Icon): this
6464         // Changes the marker icon.
6465         setIcon: function (icon) {
6466
6467                 this.options.icon = icon;
6468
6469                 if (this._map) {
6470                         this._initIcon();
6471                         this.update();
6472                 }
6473
6474                 if (this._popup) {
6475                         this.bindPopup(this._popup, this._popup.options);
6476                 }
6477
6478                 return this;
6479         },
6480
6481         getElement: function () {
6482                 return this._icon;
6483         },
6484
6485         update: function () {
6486
6487                 if (this._icon) {
6488                         var pos = this._map.latLngToLayerPoint(this._latlng).round();
6489                         this._setPos(pos);
6490                 }
6491
6492                 return this;
6493         },
6494
6495         _initIcon: function () {
6496                 var options = this.options,
6497                     classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
6498
6499                 var icon = options.icon.createIcon(this._icon),
6500                     addIcon = false;
6501
6502                 // if we're not reusing the icon, remove the old one and init new one
6503                 if (icon !== this._icon) {
6504                         if (this._icon) {
6505                                 this._removeIcon();
6506                         }
6507                         addIcon = true;
6508
6509                         if (options.title) {
6510                                 icon.title = options.title;
6511                         }
6512                         if (options.alt) {
6513                                 icon.alt = options.alt;
6514                         }
6515                 }
6516
6517                 L.DomUtil.addClass(icon, classToAdd);
6518
6519                 if (options.keyboard) {
6520                         icon.tabIndex = '0';
6521                 }
6522
6523                 this._icon = icon;
6524
6525                 if (options.riseOnHover) {
6526                         this.on({
6527                                 mouseover: this._bringToFront,
6528                                 mouseout: this._resetZIndex
6529                         });
6530                 }
6531
6532                 var newShadow = options.icon.createShadow(this._shadow),
6533                     addShadow = false;
6534
6535                 if (newShadow !== this._shadow) {
6536                         this._removeShadow();
6537                         addShadow = true;
6538                 }
6539
6540                 if (newShadow) {
6541                         L.DomUtil.addClass(newShadow, classToAdd);
6542                         newShadow.alt = '';
6543                 }
6544                 this._shadow = newShadow;
6545
6546
6547                 if (options.opacity < 1) {
6548                         this._updateOpacity();
6549                 }
6550
6551
6552                 if (addIcon) {
6553                         this.getPane().appendChild(this._icon);
6554                 }
6555                 this._initInteraction();
6556                 if (newShadow && addShadow) {
6557                         this.getPane('shadowPane').appendChild(this._shadow);
6558                 }
6559         },
6560
6561         _removeIcon: function () {
6562                 if (this.options.riseOnHover) {
6563                         this.off({
6564                                 mouseover: this._bringToFront,
6565                                 mouseout: this._resetZIndex
6566                         });
6567                 }
6568
6569                 L.DomUtil.remove(this._icon);
6570                 this.removeInteractiveTarget(this._icon);
6571
6572                 this._icon = null;
6573         },
6574
6575         _removeShadow: function () {
6576                 if (this._shadow) {
6577                         L.DomUtil.remove(this._shadow);
6578                 }
6579                 this._shadow = null;
6580         },
6581
6582         _setPos: function (pos) {
6583                 L.DomUtil.setPosition(this._icon, pos);
6584
6585                 if (this._shadow) {
6586                         L.DomUtil.setPosition(this._shadow, pos);
6587                 }
6588
6589                 this._zIndex = pos.y + this.options.zIndexOffset;
6590
6591                 this._resetZIndex();
6592         },
6593
6594         _updateZIndex: function (offset) {
6595                 this._icon.style.zIndex = this._zIndex + offset;
6596         },
6597
6598         _animateZoom: function (opt) {
6599                 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
6600
6601                 this._setPos(pos);
6602         },
6603
6604         _initInteraction: function () {
6605
6606                 if (!this.options.interactive) { return; }
6607
6608                 L.DomUtil.addClass(this._icon, 'leaflet-interactive');
6609
6610                 this.addInteractiveTarget(this._icon);
6611
6612                 if (L.Handler.MarkerDrag) {
6613                         var draggable = this.options.draggable;
6614                         if (this.dragging) {
6615                                 draggable = this.dragging.enabled();
6616                                 this.dragging.disable();
6617                         }
6618
6619                         this.dragging = new L.Handler.MarkerDrag(this);
6620
6621                         if (draggable) {
6622                                 this.dragging.enable();
6623                         }
6624                 }
6625         },
6626
6627         // @method setOpacity(opacity: Number): this
6628         // Changes the opacity of the marker.
6629         setOpacity: function (opacity) {
6630                 this.options.opacity = opacity;
6631                 if (this._map) {
6632                         this._updateOpacity();
6633                 }
6634
6635                 return this;
6636         },
6637
6638         _updateOpacity: function () {
6639                 var opacity = this.options.opacity;
6640
6641                 L.DomUtil.setOpacity(this._icon, opacity);
6642
6643                 if (this._shadow) {
6644                         L.DomUtil.setOpacity(this._shadow, opacity);
6645                 }
6646         },
6647
6648         _bringToFront: function () {
6649                 this._updateZIndex(this.options.riseOffset);
6650         },
6651
6652         _resetZIndex: function () {
6653                 this._updateZIndex(0);
6654         },
6655
6656         _getPopupAnchor: function () {
6657                 return this.options.icon.options.popupAnchor || [0, 0];
6658         },
6659
6660         _getTooltipAnchor: function () {
6661                 return this.options.icon.options.tooltipAnchor || [0, 0];
6662         }
6663 });
6664
6665
6666 // factory L.marker(latlng: LatLng, options? : Marker options)
6667
6668 // @factory L.marker(latlng: LatLng, options? : Marker options)
6669 // Instantiates a Marker object given a geographical point and optionally an options object.
6670 L.marker = function (latlng, options) {
6671         return new L.Marker(latlng, options);
6672 };
6673
6674
6675
6676 /*
6677  * @class DivIcon
6678  * @aka L.DivIcon
6679  * @inherits Icon
6680  *
6681  * Represents a lightweight icon for markers that uses a simple `<div>`
6682  * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
6683  *
6684  * @example
6685  * ```js
6686  * var myIcon = L.divIcon({className: 'my-div-icon'});
6687  * // you can set .my-div-icon styles in CSS
6688  *
6689  * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
6690  * ```
6691  *
6692  * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
6693  */
6694
6695 L.DivIcon = L.Icon.extend({
6696         options: {
6697                 // @section
6698                 // @aka DivIcon options
6699                 iconSize: [12, 12], // also can be set through CSS
6700
6701                 // iconAnchor: (Point),
6702                 // popupAnchor: (Point),
6703
6704                 // @option html: String = ''
6705                 // Custom HTML code to put inside the div element, empty by default.
6706                 html: false,
6707
6708                 // @option bgPos: Point = [0, 0]
6709                 // Optional relative position of the background, in pixels
6710                 bgPos: null,
6711
6712                 className: 'leaflet-div-icon'
6713         },
6714
6715         createIcon: function (oldIcon) {
6716                 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
6717                     options = this.options;
6718
6719                 div.innerHTML = options.html !== false ? options.html : '';
6720
6721                 if (options.bgPos) {
6722                         var bgPos = L.point(options.bgPos);
6723                         div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
6724                 }
6725                 this._setIconStyles(div, 'icon');
6726
6727                 return div;
6728         },
6729
6730         createShadow: function () {
6731                 return null;
6732         }
6733 });
6734
6735 // @factory L.divIcon(options: DivIcon options)
6736 // Creates a `DivIcon` instance with the given options.
6737 L.divIcon = function (options) {
6738         return new L.DivIcon(options);
6739 };
6740
6741
6742
6743 /*
6744  * @class DivOverlay
6745  * @inherits Layer
6746  * @aka L.DivOverlay
6747  * Base model for L.Popup and L.Tooltip. Inherit from it for custom popup like plugins.
6748  */
6749
6750 // @namespace DivOverlay
6751 L.DivOverlay = L.Layer.extend({
6752
6753         // @section
6754         // @aka DivOverlay options
6755         options: {
6756                 // @option offset: Point = Point(0, 7)
6757                 // The offset of the popup position. Useful to control the anchor
6758                 // of the popup when opening it on some overlays.
6759                 offset: [0, 7],
6760
6761                 // @option className: String = ''
6762                 // A custom CSS class name to assign to the popup.
6763                 className: '',
6764
6765                 // @option pane: String = 'popupPane'
6766                 // `Map pane` where the popup will be added.
6767                 pane: 'popupPane'
6768         },
6769
6770         initialize: function (options, source) {
6771                 L.setOptions(this, options);
6772
6773                 this._source = source;
6774         },
6775
6776         onAdd: function (map) {
6777                 this._zoomAnimated = map._zoomAnimated;
6778
6779                 if (!this._container) {
6780                         this._initLayout();
6781                 }
6782
6783                 if (map._fadeAnimated) {
6784                         L.DomUtil.setOpacity(this._container, 0);
6785                 }
6786
6787                 clearTimeout(this._removeTimeout);
6788                 this.getPane().appendChild(this._container);
6789                 this.update();
6790
6791                 if (map._fadeAnimated) {
6792                         L.DomUtil.setOpacity(this._container, 1);
6793                 }
6794
6795                 this.bringToFront();
6796         },
6797
6798         onRemove: function (map) {
6799                 if (map._fadeAnimated) {
6800                         L.DomUtil.setOpacity(this._container, 0);
6801                         this._removeTimeout = setTimeout(L.bind(L.DomUtil.remove, L.DomUtil, this._container), 200);
6802                 } else {
6803                         L.DomUtil.remove(this._container);
6804                 }
6805         },
6806
6807         // @namespace Popup
6808         // @method getLatLng: LatLng
6809         // Returns the geographical point of popup.
6810         getLatLng: function () {
6811                 return this._latlng;
6812         },
6813
6814         // @method setLatLng(latlng: LatLng): this
6815         // Sets the geographical point where the popup will open.
6816         setLatLng: function (latlng) {
6817                 this._latlng = L.latLng(latlng);
6818                 if (this._map) {
6819                         this._updatePosition();
6820                         this._adjustPan();
6821                 }
6822                 return this;
6823         },
6824
6825         // @method getContent: String|HTMLElement
6826         // Returns the content of the popup.
6827         getContent: function () {
6828                 return this._content;
6829         },
6830
6831         // @method setContent(htmlContent: String|HTMLElement|Function): this
6832         // Sets the HTML content of the popup. If a function is passed the source layer will be passed to the function. The function should return a `String` or `HTMLElement` to be used in the popup.
6833         setContent: function (content) {
6834                 this._content = content;
6835                 this.update();
6836                 return this;
6837         },
6838
6839         // @method getElement: String|HTMLElement
6840         // Alias for [getContent()](#popup-getcontent)
6841         getElement: function () {
6842                 return this._container;
6843         },
6844
6845         // @method update: null
6846         // Updates the popup content, layout and position. Useful for updating the popup after something inside changed, e.g. image loaded.
6847         update: function () {
6848                 if (!this._map) { return; }
6849
6850                 this._container.style.visibility = 'hidden';
6851
6852                 this._updateContent();
6853                 this._updateLayout();
6854                 this._updatePosition();
6855
6856                 this._container.style.visibility = '';
6857
6858                 this._adjustPan();
6859         },
6860
6861         getEvents: function () {
6862                 var events = {
6863                         zoom: this._updatePosition,
6864                         viewreset: this._updatePosition
6865                 };
6866
6867                 if (this._zoomAnimated) {
6868                         events.zoomanim = this._animateZoom;
6869                 }
6870                 return events;
6871         },
6872
6873         // @method isOpen: Boolean
6874         // Returns `true` when the popup is visible on the map.
6875         isOpen: function () {
6876                 return !!this._map && this._map.hasLayer(this);
6877         },
6878
6879         // @method bringToFront: this
6880         // Brings this popup in front of other popups (in the same map pane).
6881         bringToFront: function () {
6882                 if (this._map) {
6883                         L.DomUtil.toFront(this._container);
6884                 }
6885                 return this;
6886         },
6887
6888         // @method bringToBack: this
6889         // Brings this popup to the back of other popups (in the same map pane).
6890         bringToBack: function () {
6891                 if (this._map) {
6892                         L.DomUtil.toBack(this._container);
6893                 }
6894                 return this;
6895         },
6896
6897         _updateContent: function () {
6898                 if (!this._content) { return; }
6899
6900                 var node = this._contentNode;
6901                 var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;
6902
6903                 if (typeof content === 'string') {
6904                         node.innerHTML = content;
6905                 } else {
6906                         while (node.hasChildNodes()) {
6907                                 node.removeChild(node.firstChild);
6908                         }
6909                         node.appendChild(content);
6910                 }
6911                 this.fire('contentupdate');
6912         },
6913
6914         _updatePosition: function () {
6915                 if (!this._map) { return; }
6916
6917                 var pos = this._map.latLngToLayerPoint(this._latlng),
6918                     offset = L.point(this.options.offset),
6919                     anchor = this._getAnchor();
6920
6921                 if (this._zoomAnimated) {
6922                         L.DomUtil.setPosition(this._container, pos.add(anchor));
6923                 } else {
6924                         offset = offset.add(pos).add(anchor);
6925                 }
6926
6927                 var bottom = this._containerBottom = -offset.y,
6928                     left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;
6929
6930                 // bottom position the popup in case the height of the popup changes (images loading etc)
6931                 this._container.style.bottom = bottom + 'px';
6932                 this._container.style.left = left + 'px';
6933         },
6934
6935         _getAnchor: function () {
6936                 return [0, 0];
6937         }
6938
6939 });
6940
6941
6942
6943 /*
6944  * @class Popup
6945  * @inherits DivOverlay
6946  * @aka L.Popup
6947  * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
6948  * open popups while making sure that only one popup is open at one time
6949  * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
6950  *
6951  * @example
6952  *
6953  * If you want to just bind a popup to marker click and then open it, it's really easy:
6954  *
6955  * ```js
6956  * marker.bindPopup(popupContent).openPopup();
6957  * ```
6958  * Path overlays like polylines also have a `bindPopup` method.
6959  * Here's a more complicated way to open a popup on a map:
6960  *
6961  * ```js
6962  * var popup = L.popup()
6963  *      .setLatLng(latlng)
6964  *      .setContent('<p>Hello world!<br />This is a nice popup.</p>')
6965  *      .openOn(map);
6966  * ```
6967  */
6968
6969
6970 // @namespace Popup
6971 L.Popup = L.DivOverlay.extend({
6972
6973         // @section
6974         // @aka Popup options
6975         options: {
6976                 // @option maxWidth: Number = 300
6977                 // Max width of the popup, in pixels.
6978                 maxWidth: 300,
6979
6980                 // @option minWidth: Number = 50
6981                 // Min width of the popup, in pixels.
6982                 minWidth: 50,
6983
6984                 // @option maxHeight: Number = null
6985                 // If set, creates a scrollable container of the given height
6986                 // inside a popup if its content exceeds it.
6987                 maxHeight: null,
6988
6989                 // @option autoPan: Boolean = true
6990                 // Set it to `false` if you don't want the map to do panning animation
6991                 // to fit the opened popup.
6992                 autoPan: true,
6993
6994                 // @option autoPanPaddingTopLeft: Point = null
6995                 // The margin between the popup and the top left corner of the map
6996                 // view after autopanning was performed.
6997                 autoPanPaddingTopLeft: null,
6998
6999                 // @option autoPanPaddingBottomRight: Point = null
7000                 // The margin between the popup and the bottom right corner of the map
7001                 // view after autopanning was performed.
7002                 autoPanPaddingBottomRight: null,
7003
7004                 // @option autoPanPadding: Point = Point(5, 5)
7005                 // Equivalent of setting both top left and bottom right autopan padding to the same value.
7006                 autoPanPadding: [5, 5],
7007
7008                 // @option keepInView: Boolean = false
7009                 // Set it to `true` if you want to prevent users from panning the popup
7010                 // off of the screen while it is open.
7011                 keepInView: false,
7012
7013                 // @option closeButton: Boolean = true
7014                 // Controls the presence of a close button in the popup.
7015                 closeButton: true,
7016
7017                 // @option autoClose: Boolean = true
7018                 // Set it to `false` if you want to override the default behavior of
7019                 // the popup closing when user clicks the map (set globally by
7020                 // the Map's [closePopupOnClick](#map-closepopuponclick) option).
7021                 autoClose: true,
7022
7023                 // @option className: String = ''
7024                 // A custom CSS class name to assign to the popup.
7025                 className: ''
7026         },
7027
7028         // @namespace Popup
7029         // @method openOn(map: Map): this
7030         // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
7031         openOn: function (map) {
7032                 map.openPopup(this);
7033                 return this;
7034         },
7035
7036         onAdd: function (map) {
7037                 L.DivOverlay.prototype.onAdd.call(this, map);
7038
7039                 // @namespace Map
7040                 // @section Popup events
7041                 // @event popupopen: PopupEvent
7042                 // Fired when a popup is opened in the map
7043                 map.fire('popupopen', {popup: this});
7044
7045                 if (this._source) {
7046                         // @namespace Layer
7047                         // @section Popup events
7048                         // @event popupopen: PopupEvent
7049                         // Fired when a popup bound to this layer is opened
7050                         this._source.fire('popupopen', {popup: this}, true);
7051                         // For non-path layers, we toggle the popup when clicking
7052                         // again the layer, so prevent the map to reopen it.
7053                         if (!(this._source instanceof L.Path)) {
7054                                 this._source.on('preclick', L.DomEvent.stopPropagation);
7055                         }
7056                 }
7057         },
7058
7059         onRemove: function (map) {
7060                 L.DivOverlay.prototype.onRemove.call(this, map);
7061
7062                 // @namespace Map
7063                 // @section Popup events
7064                 // @event popupclose: PopupEvent
7065                 // Fired when a popup in the map is closed
7066                 map.fire('popupclose', {popup: this});
7067
7068                 if (this._source) {
7069                         // @namespace Layer
7070                         // @section Popup events
7071                         // @event popupclose: PopupEvent
7072                         // Fired when a popup bound to this layer is closed
7073                         this._source.fire('popupclose', {popup: this}, true);
7074                         if (!(this._source instanceof L.Path)) {
7075                                 this._source.off('preclick', L.DomEvent.stopPropagation);
7076                         }
7077                 }
7078         },
7079
7080         getEvents: function () {
7081                 var events = L.DivOverlay.prototype.getEvents.call(this);
7082
7083                 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
7084                         events.preclick = this._close;
7085                 }
7086
7087                 if (this.options.keepInView) {
7088                         events.moveend = this._adjustPan;
7089                 }
7090
7091                 return events;
7092         },
7093
7094         _close: function () {
7095                 if (this._map) {
7096                         this._map.closePopup(this);
7097                 }
7098         },
7099
7100         _initLayout: function () {
7101                 var prefix = 'leaflet-popup',
7102                     container = this._container = L.DomUtil.create('div',
7103                         prefix + ' ' + (this.options.className || '') +
7104                         ' leaflet-zoom-animated');
7105
7106                 if (this.options.closeButton) {
7107                         var closeButton = this._closeButton = L.DomUtil.create('a', prefix + '-close-button', container);
7108                         closeButton.href = '#close';
7109                         closeButton.innerHTML = '&#215;';
7110
7111                         L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
7112                 }
7113
7114                 var wrapper = this._wrapper = L.DomUtil.create('div', prefix + '-content-wrapper', container);
7115                 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
7116
7117                 L.DomEvent
7118                         .disableClickPropagation(wrapper)
7119                         .disableScrollPropagation(this._contentNode)
7120                         .on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
7121
7122                 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
7123                 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
7124         },
7125
7126         _updateLayout: function () {
7127                 var container = this._contentNode,
7128                     style = container.style;
7129
7130                 style.width = '';
7131                 style.whiteSpace = 'nowrap';
7132
7133                 var width = container.offsetWidth;
7134                 width = Math.min(width, this.options.maxWidth);
7135                 width = Math.max(width, this.options.minWidth);
7136
7137                 style.width = (width + 1) + 'px';
7138                 style.whiteSpace = '';
7139
7140                 style.height = '';
7141
7142                 var height = container.offsetHeight,
7143                     maxHeight = this.options.maxHeight,
7144                     scrolledClass = 'leaflet-popup-scrolled';
7145
7146                 if (maxHeight && height > maxHeight) {
7147                         style.height = maxHeight + 'px';
7148                         L.DomUtil.addClass(container, scrolledClass);
7149                 } else {
7150                         L.DomUtil.removeClass(container, scrolledClass);
7151                 }
7152
7153                 this._containerWidth = this._container.offsetWidth;
7154         },
7155
7156         _animateZoom: function (e) {
7157                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
7158                     anchor = this._getAnchor();
7159                 L.DomUtil.setPosition(this._container, pos.add(anchor));
7160         },
7161
7162         _adjustPan: function () {
7163                 if (!this.options.autoPan || (this._map._panAnim && this._map._panAnim._inProgress)) { return; }
7164
7165                 var map = this._map,
7166                     marginBottom = parseInt(L.DomUtil.getStyle(this._container, 'marginBottom'), 10) || 0,
7167                     containerHeight = this._container.offsetHeight + marginBottom,
7168                     containerWidth = this._containerWidth,
7169                     layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
7170
7171                 layerPos._add(L.DomUtil.getPosition(this._container));
7172
7173                 var containerPos = map.layerPointToContainerPoint(layerPos),
7174                     padding = L.point(this.options.autoPanPadding),
7175                     paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
7176                     paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
7177                     size = map.getSize(),
7178                     dx = 0,
7179                     dy = 0;
7180
7181                 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
7182                         dx = containerPos.x + containerWidth - size.x + paddingBR.x;
7183                 }
7184                 if (containerPos.x - dx - paddingTL.x < 0) { // left
7185                         dx = containerPos.x - paddingTL.x;
7186                 }
7187                 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
7188                         dy = containerPos.y + containerHeight - size.y + paddingBR.y;
7189                 }
7190                 if (containerPos.y - dy - paddingTL.y < 0) { // top
7191                         dy = containerPos.y - paddingTL.y;
7192                 }
7193
7194                 // @namespace Map
7195                 // @section Popup events
7196                 // @event autopanstart: Event
7197                 // Fired when the map starts autopanning when opening a popup.
7198                 if (dx || dy) {
7199                         map
7200                             .fire('autopanstart')
7201                             .panBy([dx, dy]);
7202                 }
7203         },
7204
7205         _onCloseButtonClick: function (e) {
7206                 this._close();
7207                 L.DomEvent.stop(e);
7208         },
7209
7210         _getAnchor: function () {
7211                 // Where should we anchor the popup on the source layer?
7212                 return L.point(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
7213         }
7214
7215 });
7216
7217 // @namespace Popup
7218 // @factory L.popup(options?: Popup options, source?: Layer)
7219 // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
7220 L.popup = function (options, source) {
7221         return new L.Popup(options, source);
7222 };
7223
7224
7225 /* @namespace Map
7226  * @section Interaction Options
7227  * @option closePopupOnClick: Boolean = true
7228  * Set it to `false` if you don't want popups to close when user clicks the map.
7229  */
7230 L.Map.mergeOptions({
7231         closePopupOnClick: true
7232 });
7233
7234
7235 // @namespace Map
7236 // @section Methods for Layers and Controls
7237 L.Map.include({
7238         // @method openPopup(popup: Popup): this
7239         // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
7240         // @alternative
7241         // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
7242         // Creates a popup with the specified content and options and opens it in the given point on a map.
7243         openPopup: function (popup, latlng, options) {
7244                 if (!(popup instanceof L.Popup)) {
7245                         popup = new L.Popup(options).setContent(popup);
7246                 }
7247
7248                 if (latlng) {
7249                         popup.setLatLng(latlng);
7250                 }
7251
7252                 if (this.hasLayer(popup)) {
7253                         return this;
7254                 }
7255
7256                 if (this._popup && this._popup.options.autoClose) {
7257                         this.closePopup();
7258                 }
7259
7260                 this._popup = popup;
7261                 return this.addLayer(popup);
7262         },
7263
7264         // @method closePopup(popup?: Popup): this
7265         // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
7266         closePopup: function (popup) {
7267                 if (!popup || popup === this._popup) {
7268                         popup = this._popup;
7269                         this._popup = null;
7270                 }
7271                 if (popup) {
7272                         this.removeLayer(popup);
7273                 }
7274                 return this;
7275         }
7276 });
7277
7278 /*
7279  * @namespace Layer
7280  * @section Popup methods example
7281  *
7282  * All layers share a set of methods convenient for binding popups to it.
7283  *
7284  * ```js
7285  * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
7286  * layer.openPopup();
7287  * layer.closePopup();
7288  * ```
7289  *
7290  * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
7291  */
7292
7293 // @section Popup methods
7294 L.Layer.include({
7295
7296         // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
7297         // Binds a popup to the layer with the passed `content` and sets up the
7298         // neccessary event listeners. If a `Function` is passed it will receive
7299         // the layer as the first argument and should return a `String` or `HTMLElement`.
7300         bindPopup: function (content, options) {
7301
7302                 if (content instanceof L.Popup) {
7303                         L.setOptions(content, options);
7304                         this._popup = content;
7305                         content._source = this;
7306                 } else {
7307                         if (!this._popup || options) {
7308                                 this._popup = new L.Popup(options, this);
7309                         }
7310                         this._popup.setContent(content);
7311                 }
7312
7313                 if (!this._popupHandlersAdded) {
7314                         this.on({
7315                                 click: this._openPopup,
7316                                 remove: this.closePopup,
7317                                 move: this._movePopup
7318                         });
7319                         this._popupHandlersAdded = true;
7320                 }
7321
7322                 return this;
7323         },
7324
7325         // @method unbindPopup(): this
7326         // Removes the popup previously bound with `bindPopup`.
7327         unbindPopup: function () {
7328                 if (this._popup) {
7329                         this.off({
7330                                 click: this._openPopup,
7331                                 remove: this.closePopup,
7332                                 move: this._movePopup
7333                         });
7334                         this._popupHandlersAdded = false;
7335                         this._popup = null;
7336                 }
7337                 return this;
7338         },
7339
7340         // @method openPopup(latlng?: LatLng): this
7341         // Opens the bound popup at the specificed `latlng` or at the default popup anchor if no `latlng` is passed.
7342         openPopup: function (layer, latlng) {
7343                 if (!(layer instanceof L.Layer)) {
7344                         latlng = layer;
7345                         layer = this;
7346                 }
7347
7348                 if (layer instanceof L.FeatureGroup) {
7349                         for (var id in this._layers) {
7350                                 layer = this._layers[id];
7351                                 break;
7352                         }
7353                 }
7354
7355                 if (!latlng) {
7356                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7357                 }
7358
7359                 if (this._popup && this._map) {
7360                         // set popup source to this layer
7361                         this._popup._source = layer;
7362
7363                         // update the popup (content, layout, ect...)
7364                         this._popup.update();
7365
7366                         // open the popup on the map
7367                         this._map.openPopup(this._popup, latlng);
7368                 }
7369
7370                 return this;
7371         },
7372
7373         // @method closePopup(): this
7374         // Closes the popup bound to this layer if it is open.
7375         closePopup: function () {
7376                 if (this._popup) {
7377                         this._popup._close();
7378                 }
7379                 return this;
7380         },
7381
7382         // @method togglePopup(): this
7383         // Opens or closes the popup bound to this layer depending on its current state.
7384         togglePopup: function (target) {
7385                 if (this._popup) {
7386                         if (this._popup._map) {
7387                                 this.closePopup();
7388                         } else {
7389                                 this.openPopup(target);
7390                         }
7391                 }
7392                 return this;
7393         },
7394
7395         // @method isPopupOpen(): boolean
7396         // Returns `true` if the popup bound to this layer is currently open.
7397         isPopupOpen: function () {
7398                 return (this._popup ? this._popup.isOpen() : false);
7399         },
7400
7401         // @method setPopupContent(content: String|HTMLElement|Popup): this
7402         // Sets the content of the popup bound to this layer.
7403         setPopupContent: function (content) {
7404                 if (this._popup) {
7405                         this._popup.setContent(content);
7406                 }
7407                 return this;
7408         },
7409
7410         // @method getPopup(): Popup
7411         // Returns the popup bound to this layer.
7412         getPopup: function () {
7413                 return this._popup;
7414         },
7415
7416         _openPopup: function (e) {
7417                 var layer = e.layer || e.target;
7418
7419                 if (!this._popup) {
7420                         return;
7421                 }
7422
7423                 if (!this._map) {
7424                         return;
7425                 }
7426
7427                 // prevent map click
7428                 L.DomEvent.stop(e);
7429
7430                 // if this inherits from Path its a vector and we can just
7431                 // open the popup at the new location
7432                 if (layer instanceof L.Path) {
7433                         this.openPopup(e.layer || e.target, e.latlng);
7434                         return;
7435                 }
7436
7437                 // otherwise treat it like a marker and figure out
7438                 // if we should toggle it open/closed
7439                 if (this._map.hasLayer(this._popup) && this._popup._source === layer) {
7440                         this.closePopup();
7441                 } else {
7442                         this.openPopup(layer, e.latlng);
7443                 }
7444         },
7445
7446         _movePopup: function (e) {
7447                 this._popup.setLatLng(e.latlng);
7448         }
7449 });
7450
7451
7452
7453 /*
7454  * @class Tooltip
7455  * @inherits DivOverlay
7456  * @aka L.Tooltip
7457  * Used to display small texts on top of map layers.
7458  *
7459  * @example
7460  *
7461  * ```js
7462  * marker.bindTooltip("my tooltip text").openTooltip();
7463  * ```
7464  * Note about tooltip offset. Leaflet takes two options in consideration
7465  * for computing tooltip offseting:
7466  * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
7467  *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
7468  *   move it to the bottom. Negatives will move to the left and top.
7469  * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
7470  *   should adapt this value if you use a custom icon.
7471  */
7472
7473
7474 // @namespace Tooltip
7475 L.Tooltip = L.DivOverlay.extend({
7476
7477         // @section
7478         // @aka Tooltip options
7479         options: {
7480                 // @option pane: String = 'tooltipPane'
7481                 // `Map pane` where the tooltip will be added.
7482                 pane: 'tooltipPane',
7483
7484                 // @option offset: Point = Point(0, 0)
7485                 // Optional offset of the tooltip position.
7486                 offset: [0, 0],
7487
7488                 // @option direction: String = 'auto'
7489                 // Direction where to open the tooltip. Possible values are: `right`, `left`,
7490                 // `top`, `bottom`, `center`, `auto`.
7491                 // `auto` will dynamicaly switch between `right` and `left` according to the tooltip
7492                 // position on the map.
7493                 direction: 'auto',
7494
7495                 // @option permanent: Boolean = false
7496                 // Whether to open the tooltip permanently or only on mouseover.
7497                 permanent: false,
7498
7499                 // @option sticky: Boolean = false
7500                 // If true, the tooltip will follow the mouse instead of being fixed at the feature center.
7501                 sticky: false,
7502
7503                 // @option interactive: Boolean = false
7504                 // If true, the tooltip will listen to the feature events.
7505                 interactive: false,
7506
7507                 // @option opacity: Number = 0.9
7508                 // Tooltip container opacity.
7509                 opacity: 0.9
7510         },
7511
7512         onAdd: function (map) {
7513                 L.DivOverlay.prototype.onAdd.call(this, map);
7514                 this.setOpacity(this.options.opacity);
7515
7516                 // @namespace Map
7517                 // @section Tooltip events
7518                 // @event tooltipopen: TooltipEvent
7519                 // Fired when a tooltip is opened in the map.
7520                 map.fire('tooltipopen', {tooltip: this});
7521
7522                 if (this._source) {
7523                         // @namespace Layer
7524                         // @section Tooltip events
7525                         // @event tooltipopen: TooltipEvent
7526                         // Fired when a tooltip bound to this layer is opened.
7527                         this._source.fire('tooltipopen', {tooltip: this}, true);
7528                 }
7529         },
7530
7531         onRemove: function (map) {
7532                 L.DivOverlay.prototype.onRemove.call(this, map);
7533
7534                 // @namespace Map
7535                 // @section Tooltip events
7536                 // @event tooltipclose: TooltipEvent
7537                 // Fired when a tooltip in the map is closed.
7538                 map.fire('tooltipclose', {tooltip: this});
7539
7540                 if (this._source) {
7541                         // @namespace Layer
7542                         // @section Tooltip events
7543                         // @event tooltipclose: TooltipEvent
7544                         // Fired when a tooltip bound to this layer is closed.
7545                         this._source.fire('tooltipclose', {tooltip: this}, true);
7546                 }
7547         },
7548
7549         getEvents: function () {
7550                 var events = L.DivOverlay.prototype.getEvents.call(this);
7551
7552                 if (L.Browser.touch && !this.options.permanent) {
7553                         events.preclick = this._close;
7554                 }
7555
7556                 return events;
7557         },
7558
7559         _close: function () {
7560                 if (this._map) {
7561                         this._map.closeTooltip(this);
7562                 }
7563         },
7564
7565         _initLayout: function () {
7566                 var prefix = 'leaflet-tooltip',
7567                     className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');
7568
7569                 this._contentNode = this._container = L.DomUtil.create('div', className);
7570         },
7571
7572         _updateLayout: function () {},
7573
7574         _adjustPan: function () {},
7575
7576         _setPosition: function (pos) {
7577                 var map = this._map,
7578                     container = this._container,
7579                     centerPoint = map.latLngToContainerPoint(map.getCenter()),
7580                     tooltipPoint = map.layerPointToContainerPoint(pos),
7581                     direction = this.options.direction,
7582                     tooltipWidth = container.offsetWidth,
7583                     tooltipHeight = container.offsetHeight,
7584                     offset = L.point(this.options.offset),
7585                     anchor = this._getAnchor();
7586
7587                 if (direction === 'top') {
7588                         pos = pos.add(L.point(-tooltipWidth / 2 + offset.x, -tooltipHeight + offset.y + anchor.y, true));
7589                 } else if (direction === 'bottom') {
7590                         pos = pos.subtract(L.point(tooltipWidth / 2 - offset.x, -offset.y, true));
7591                 } else if (direction === 'center') {
7592                         pos = pos.subtract(L.point(tooltipWidth / 2 + offset.x, tooltipHeight / 2 - anchor.y + offset.y, true));
7593                 } else if (direction === 'right' || direction === 'auto' && tooltipPoint.x < centerPoint.x) {
7594                         direction = 'right';
7595                         pos = pos.add(L.point(offset.x + anchor.x, anchor.y - tooltipHeight / 2 + offset.y, true));
7596                 } else {
7597                         direction = 'left';
7598                         pos = pos.subtract(L.point(tooltipWidth + anchor.x - offset.x, tooltipHeight / 2 - anchor.y - offset.y, true));
7599                 }
7600
7601                 L.DomUtil.removeClass(container, 'leaflet-tooltip-right');
7602                 L.DomUtil.removeClass(container, 'leaflet-tooltip-left');
7603                 L.DomUtil.removeClass(container, 'leaflet-tooltip-top');
7604                 L.DomUtil.removeClass(container, 'leaflet-tooltip-bottom');
7605                 L.DomUtil.addClass(container, 'leaflet-tooltip-' + direction);
7606                 L.DomUtil.setPosition(container, pos);
7607         },
7608
7609         _updatePosition: function () {
7610                 var pos = this._map.latLngToLayerPoint(this._latlng);
7611                 this._setPosition(pos);
7612         },
7613
7614         setOpacity: function (opacity) {
7615                 this.options.opacity = opacity;
7616
7617                 if (this._container) {
7618                         L.DomUtil.setOpacity(this._container, opacity);
7619                 }
7620         },
7621
7622         _animateZoom: function (e) {
7623                 var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
7624                 this._setPosition(pos);
7625         },
7626
7627         _getAnchor: function () {
7628                 // Where should we anchor the tooltip on the source layer?
7629                 return L.point(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
7630         }
7631
7632 });
7633
7634 // @namespace Tooltip
7635 // @factory L.tooltip(options?: Tooltip options, source?: Layer)
7636 // Instantiates a Tooltip object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
7637 L.tooltip = function (options, source) {
7638         return new L.Tooltip(options, source);
7639 };
7640
7641 // @namespace Map
7642 // @section Methods for Layers and Controls
7643 L.Map.include({
7644
7645         // @method openTooltip(tooltip: Tooltip): this
7646         // Opens the specified tooltip.
7647         // @alternative
7648         // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
7649         // Creates a tooltip with the specified content and options and open it.
7650         openTooltip: function (tooltip, latlng, options) {
7651                 if (!(tooltip instanceof L.Tooltip)) {
7652                         tooltip = new L.Tooltip(options).setContent(tooltip);
7653                 }
7654
7655                 if (latlng) {
7656                         tooltip.setLatLng(latlng);
7657                 }
7658
7659                 if (this.hasLayer(tooltip)) {
7660                         return this;
7661                 }
7662
7663                 return this.addLayer(tooltip);
7664         },
7665
7666         // @method closeTooltip(tooltip?: Tooltip): this
7667         // Closes the tooltip given as parameter.
7668         closeTooltip: function (tooltip) {
7669                 if (tooltip) {
7670                         this.removeLayer(tooltip);
7671                 }
7672                 return this;
7673         }
7674
7675 });
7676
7677 /*
7678  * @namespace Layer
7679  * @section Tooltip methods example
7680  *
7681  * All layers share a set of methods convenient for binding tooltips to it.
7682  *
7683  * ```js
7684  * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
7685  * layer.openTooltip();
7686  * layer.closeTooltip();
7687  * ```
7688  */
7689
7690 // @section Tooltip methods
7691 L.Layer.include({
7692
7693         // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
7694         // Binds a tooltip to the layer with the passed `content` and sets up the
7695         // neccessary event listeners. If a `Function` is passed it will receive
7696         // the layer as the first argument and should return a `String` or `HTMLElement`.
7697         bindTooltip: function (content, options) {
7698
7699                 if (content instanceof L.Tooltip) {
7700                         L.setOptions(content, options);
7701                         this._tooltip = content;
7702                         content._source = this;
7703                 } else {
7704                         if (!this._tooltip || options) {
7705                                 this._tooltip = L.tooltip(options, this);
7706                         }
7707                         this._tooltip.setContent(content);
7708
7709                 }
7710
7711                 this._initTooltipInteractions();
7712
7713                 if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
7714                         this.openTooltip();
7715                 }
7716
7717                 return this;
7718         },
7719
7720         // @method unbindTooltip(): this
7721         // Removes the tooltip previously bound with `bindTooltip`.
7722         unbindTooltip: function () {
7723                 if (this._tooltip) {
7724                         this._initTooltipInteractions(true);
7725                         this.closeTooltip();
7726                         this._tooltip = null;
7727                 }
7728                 return this;
7729         },
7730
7731         _initTooltipInteractions: function (remove) {
7732                 if (!remove && this._tooltipHandlersAdded) { return; }
7733                 var onOff = remove ? 'off' : 'on',
7734                     events = {
7735                         remove: this.closeTooltip,
7736                         move: this._moveTooltip
7737                     };
7738                 if (!this._tooltip.options.permanent) {
7739                         events.mouseover = this._openTooltip;
7740                         events.mouseout = this.closeTooltip;
7741                         if (this._tooltip.options.sticky) {
7742                                 events.mousemove = this._moveTooltip;
7743                         }
7744                         if (L.Browser.touch) {
7745                                 events.click = this._openTooltip;
7746                         }
7747                 } else {
7748                         events.add = this._openTooltip;
7749                 }
7750                 this[onOff](events);
7751                 this._tooltipHandlersAdded = !remove;
7752         },
7753
7754         // @method openTooltip(latlng?: LatLng): this
7755         // Opens the bound tooltip at the specificed `latlng` or at the default tooltip anchor if no `latlng` is passed.
7756         openTooltip: function (layer, latlng) {
7757                 if (!(layer instanceof L.Layer)) {
7758                         latlng = layer;
7759                         layer = this;
7760                 }
7761
7762                 if (layer instanceof L.FeatureGroup) {
7763                         for (var id in this._layers) {
7764                                 layer = this._layers[id];
7765                                 break;
7766                         }
7767                 }
7768
7769                 if (!latlng) {
7770                         latlng = layer.getCenter ? layer.getCenter() : layer.getLatLng();
7771                 }
7772
7773                 if (this._tooltip && this._map) {
7774
7775                         // set tooltip source to this layer
7776                         this._tooltip._source = layer;
7777
7778                         // update the tooltip (content, layout, ect...)
7779                         this._tooltip.update();
7780
7781                         // open the tooltip on the map
7782                         this._map.openTooltip(this._tooltip, latlng);
7783
7784                         // Tooltip container may not be defined if not permanent and never
7785                         // opened.
7786                         if (this._tooltip.options.interactive && this._tooltip._container) {
7787                                 L.DomUtil.addClass(this._tooltip._container, 'leaflet-clickable');
7788                                 this.addInteractiveTarget(this._tooltip._container);
7789                         }
7790                 }
7791
7792                 return this;
7793         },
7794
7795         // @method closeTooltip(): this
7796         // Closes the tooltip bound to this layer if it is open.
7797         closeTooltip: function () {
7798                 if (this._tooltip) {
7799                         this._tooltip._close();
7800                         if (this._tooltip.options.interactive && this._tooltip._container) {
7801                                 L.DomUtil.removeClass(this._tooltip._container, 'leaflet-clickable');
7802                                 this.removeInteractiveTarget(this._tooltip._container);
7803                         }
7804                 }
7805                 return this;
7806         },
7807
7808         // @method toggleTooltip(): this
7809         // Opens or closes the tooltip bound to this layer depending on its current state.
7810         toggleTooltip: function (target) {
7811                 if (this._tooltip) {
7812                         if (this._tooltip._map) {
7813                                 this.closeTooltip();
7814                         } else {
7815                                 this.openTooltip(target);
7816                         }
7817                 }
7818                 return this;
7819         },
7820
7821         // @method isTooltipOpen(): boolean
7822         // Returns `true` if the tooltip bound to this layer is currently open.
7823         isTooltipOpen: function () {
7824                 return this._tooltip.isOpen();
7825         },
7826
7827         // @method setTooltipContent(content: String|HTMLElement|Tooltip): this
7828         // Sets the content of the tooltip bound to this layer.
7829         setTooltipContent: function (content) {
7830                 if (this._tooltip) {
7831                         this._tooltip.setContent(content);
7832                 }
7833                 return this;
7834         },
7835
7836         // @method getTooltip(): Tooltip
7837         // Returns the tooltip bound to this layer.
7838         getTooltip: function () {
7839                 return this._tooltip;
7840         },
7841
7842         _openTooltip: function (e) {
7843                 var layer = e.layer || e.target;
7844
7845                 if (!this._tooltip || !this._map) {
7846                         return;
7847                 }
7848                 this.openTooltip(layer, this._tooltip.options.sticky ? e.latlng : undefined);
7849         },
7850
7851         _moveTooltip: function (e) {
7852                 var latlng = e.latlng, containerPoint, layerPoint;
7853                 if (this._tooltip.options.sticky && e.originalEvent) {
7854                         containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
7855                         layerPoint = this._map.containerPointToLayerPoint(containerPoint);
7856                         latlng = this._map.layerPointToLatLng(layerPoint);
7857                 }
7858                 this._tooltip.setLatLng(latlng);
7859         }
7860 });
7861
7862
7863
7864 /*
7865  * @class LayerGroup
7866  * @aka L.LayerGroup
7867  * @inherits Layer
7868  *
7869  * Used to group several layers and handle them as one. If you add it to the map,
7870  * any layers added or removed from the group will be added/removed on the map as
7871  * well. Extends `Layer`.
7872  *
7873  * @example
7874  *
7875  * ```js
7876  * L.layerGroup([marker1, marker2])
7877  *      .addLayer(polyline)
7878  *      .addTo(map);
7879  * ```
7880  */
7881
7882 L.LayerGroup = L.Layer.extend({
7883
7884         initialize: function (layers) {
7885                 this._layers = {};
7886
7887                 var i, len;
7888
7889                 if (layers) {
7890                         for (i = 0, len = layers.length; i < len; i++) {
7891                                 this.addLayer(layers[i]);
7892                         }
7893                 }
7894         },
7895
7896         // @method addLayer(layer: Layer): this
7897         // Adds the given layer to the group.
7898         addLayer: function (layer) {
7899                 var id = this.getLayerId(layer);
7900
7901                 this._layers[id] = layer;
7902
7903                 if (this._map) {
7904                         this._map.addLayer(layer);
7905                 }
7906
7907                 return this;
7908         },
7909
7910         // @method removeLayer(layer: Layer): this
7911         // Removes the given layer from the group.
7912         // @alternative
7913         // @method removeLayer(id: Number): this
7914         // Removes the layer with the given internal ID from the group.
7915         removeLayer: function (layer) {
7916                 var id = layer in this._layers ? layer : this.getLayerId(layer);
7917
7918                 if (this._map && this._layers[id]) {
7919                         this._map.removeLayer(this._layers[id]);
7920                 }
7921
7922                 delete this._layers[id];
7923
7924                 return this;
7925         },
7926
7927         // @method hasLayer(layer: Layer): Boolean
7928         // Returns `true` if the given layer is currently added to the group.
7929         hasLayer: function (layer) {
7930                 return !!layer && (layer in this._layers || this.getLayerId(layer) in this._layers);
7931         },
7932
7933         // @method clearLayers(): this
7934         // Removes all the layers from the group.
7935         clearLayers: function () {
7936                 for (var i in this._layers) {
7937                         this.removeLayer(this._layers[i]);
7938                 }
7939                 return this;
7940         },
7941
7942         // @method invoke(methodName: String, …): this
7943         // Calls `methodName` on every layer contained in this group, passing any
7944         // additional parameters. Has no effect if the layers contained do not
7945         // implement `methodName`.
7946         invoke: function (methodName) {
7947                 var args = Array.prototype.slice.call(arguments, 1),
7948                     i, layer;
7949
7950                 for (i in this._layers) {
7951                         layer = this._layers[i];
7952
7953                         if (layer[methodName]) {
7954                                 layer[methodName].apply(layer, args);
7955                         }
7956                 }
7957
7958                 return this;
7959         },
7960
7961         onAdd: function (map) {
7962                 for (var i in this._layers) {
7963                         map.addLayer(this._layers[i]);
7964                 }
7965         },
7966
7967         onRemove: function (map) {
7968                 for (var i in this._layers) {
7969                         map.removeLayer(this._layers[i]);
7970                 }
7971         },
7972
7973         // @method eachLayer(fn: Function, context?: Object): this
7974         // Iterates over the layers of the group, optionally specifying context of the iterator function.
7975         // ```js
7976         // group.eachLayer(function (layer) {
7977         //      layer.bindPopup('Hello');
7978         // });
7979         // ```
7980         eachLayer: function (method, context) {
7981                 for (var i in this._layers) {
7982                         method.call(context, this._layers[i]);
7983                 }
7984                 return this;
7985         },
7986
7987         // @method getLayer(id: Number): Layer
7988         // Returns the layer with the given internal ID.
7989         getLayer: function (id) {
7990                 return this._layers[id];
7991         },
7992
7993         // @method getLayers(): Layer[]
7994         // Returns an array of all the layers added to the group.
7995         getLayers: function () {
7996                 var layers = [];
7997
7998                 for (var i in this._layers) {
7999                         layers.push(this._layers[i]);
8000                 }
8001                 return layers;
8002         },
8003
8004         // @method setZIndex(zIndex: Number): this
8005         // Calls `setZIndex` on every layer contained in this group, passing the z-index.
8006         setZIndex: function (zIndex) {
8007                 return this.invoke('setZIndex', zIndex);
8008         },
8009
8010         // @method getLayerId(layer: Layer): Number
8011         // Returns the internal ID for a layer
8012         getLayerId: function (layer) {
8013                 return L.stamp(layer);
8014         }
8015 });
8016
8017
8018 // @factory L.layerGroup(layers: Layer[])
8019 // Create a layer group, optionally given an initial set of layers.
8020 L.layerGroup = function (layers) {
8021         return new L.LayerGroup(layers);
8022 };
8023
8024
8025
8026 /*
8027  * @class FeatureGroup
8028  * @aka L.FeatureGroup
8029  * @inherits LayerGroup
8030  *
8031  * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
8032  *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
8033  *  * Events are propagated to the `FeatureGroup`, so if the group has an event
8034  * handler, it will handle events from any of the layers. This includes mouse events
8035  * and custom events.
8036  *  * Has `layeradd` and `layerremove` events
8037  *
8038  * @example
8039  *
8040  * ```js
8041  * L.featureGroup([marker1, marker2, polyline])
8042  *      .bindPopup('Hello world!')
8043  *      .on('click', function() { alert('Clicked on a member of the group!'); })
8044  *      .addTo(map);
8045  * ```
8046  */
8047
8048 L.FeatureGroup = L.LayerGroup.extend({
8049
8050         addLayer: function (layer) {
8051                 if (this.hasLayer(layer)) {
8052                         return this;
8053                 }
8054
8055                 layer.addEventParent(this);
8056
8057                 L.LayerGroup.prototype.addLayer.call(this, layer);
8058
8059                 // @event layeradd: LayerEvent
8060                 // Fired when a layer is added to this `FeatureGroup`
8061                 return this.fire('layeradd', {layer: layer});
8062         },
8063
8064         removeLayer: function (layer) {
8065                 if (!this.hasLayer(layer)) {
8066                         return this;
8067                 }
8068                 if (layer in this._layers) {
8069                         layer = this._layers[layer];
8070                 }
8071
8072                 layer.removeEventParent(this);
8073
8074                 L.LayerGroup.prototype.removeLayer.call(this, layer);
8075
8076                 // @event layerremove: LayerEvent
8077                 // Fired when a layer is removed from this `FeatureGroup`
8078                 return this.fire('layerremove', {layer: layer});
8079         },
8080
8081         // @method setStyle(style: Path options): this
8082         // Sets the given path options to each layer of the group that has a `setStyle` method.
8083         setStyle: function (style) {
8084                 return this.invoke('setStyle', style);
8085         },
8086
8087         // @method bringToFront(): this
8088         // Brings the layer group to the top of all other layers
8089         bringToFront: function () {
8090                 return this.invoke('bringToFront');
8091         },
8092
8093         // @method bringToBack(): this
8094         // Brings the layer group to the top of all other layers
8095         bringToBack: function () {
8096                 return this.invoke('bringToBack');
8097         },
8098
8099         // @method getBounds(): LatLngBounds
8100         // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
8101         getBounds: function () {
8102                 var bounds = new L.LatLngBounds();
8103
8104                 for (var id in this._layers) {
8105                         var layer = this._layers[id];
8106                         bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
8107                 }
8108                 return bounds;
8109         }
8110 });
8111
8112 // @factory L.featureGroup(layers: Layer[])
8113 // Create a feature group, optionally given an initial set of layers.
8114 L.featureGroup = function (layers) {
8115         return new L.FeatureGroup(layers);
8116 };
8117
8118
8119
8120 /*
8121  * @class Renderer
8122  * @inherits Layer
8123  * @aka L.Renderer
8124  *
8125  * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
8126  * DOM container of the renderer, its bounds, and its zoom animation.
8127  *
8128  * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
8129  * itself can be added or removed to the map. All paths use a renderer, which can
8130  * be implicit (the map will decide the type of renderer and use it automatically)
8131  * or explicit (using the [`renderer`](#path-renderer) option of the path).
8132  *
8133  * Do not use this class directly, use `SVG` and `Canvas` instead.
8134  *
8135  * @event update: Event
8136  * Fired when the renderer updates its bounds, center and zoom, for example when
8137  * its map has moved
8138  */
8139
8140 L.Renderer = L.Layer.extend({
8141
8142         // @section
8143         // @aka Renderer options
8144         options: {
8145                 // @option padding: Number = 0.1
8146                 // How much to extend the clip area around the map view (relative to its size)
8147                 // e.g. 0.1 would be 10% of map view in each direction
8148                 padding: 0.1
8149         },
8150
8151         initialize: function (options) {
8152                 L.setOptions(this, options);
8153                 L.stamp(this);
8154                 this._layers = this._layers || {};
8155         },
8156
8157         onAdd: function () {
8158                 if (!this._container) {
8159                         this._initContainer(); // defined by renderer implementations
8160
8161                         if (this._zoomAnimated) {
8162                                 L.DomUtil.addClass(this._container, 'leaflet-zoom-animated');
8163                         }
8164                 }
8165
8166                 this.getPane().appendChild(this._container);
8167                 this._update();
8168                 this.on('update', this._updatePaths, this);
8169         },
8170
8171         onRemove: function () {
8172                 L.DomUtil.remove(this._container);
8173                 this.off('update', this._updatePaths, this);
8174         },
8175
8176         getEvents: function () {
8177                 var events = {
8178                         viewreset: this._reset,
8179                         zoom: this._onZoom,
8180                         moveend: this._update,
8181                         zoomend: this._onZoomEnd
8182                 };
8183                 if (this._zoomAnimated) {
8184                         events.zoomanim = this._onAnimZoom;
8185                 }
8186                 return events;
8187         },
8188
8189         _onAnimZoom: function (ev) {
8190                 this._updateTransform(ev.center, ev.zoom);
8191         },
8192
8193         _onZoom: function () {
8194                 this._updateTransform(this._map.getCenter(), this._map.getZoom());
8195         },
8196
8197         _updateTransform: function (center, zoom) {
8198                 var scale = this._map.getZoomScale(zoom, this._zoom),
8199                     position = L.DomUtil.getPosition(this._container),
8200                     viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
8201                     currentCenterPoint = this._map.project(this._center, zoom),
8202                     destCenterPoint = this._map.project(center, zoom),
8203                     centerOffset = destCenterPoint.subtract(currentCenterPoint),
8204
8205                     topLeftOffset = viewHalf.multiplyBy(-scale).add(position).add(viewHalf).subtract(centerOffset);
8206
8207                 if (L.Browser.any3d) {
8208                         L.DomUtil.setTransform(this._container, topLeftOffset, scale);
8209                 } else {
8210                         L.DomUtil.setPosition(this._container, topLeftOffset);
8211                 }
8212         },
8213
8214         _reset: function () {
8215                 this._update();
8216                 this._updateTransform(this._center, this._zoom);
8217
8218                 for (var id in this._layers) {
8219                         this._layers[id]._reset();
8220                 }
8221         },
8222
8223         _onZoomEnd: function () {
8224                 for (var id in this._layers) {
8225                         this._layers[id]._project();
8226                 }
8227         },
8228
8229         _updatePaths: function () {
8230                 for (var id in this._layers) {
8231                         this._layers[id]._update();
8232                 }
8233         },
8234
8235         _update: function () {
8236                 // Update pixel bounds of renderer container (for positioning/sizing/clipping later)
8237                 // Subclasses are responsible of firing the 'update' event.
8238                 var p = this.options.padding,
8239                     size = this._map.getSize(),
8240                     min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();
8241
8242                 this._bounds = new L.Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());
8243
8244                 this._center = this._map.getCenter();
8245                 this._zoom = this._map.getZoom();
8246         }
8247 });
8248
8249
8250 L.Map.include({
8251         // @namespace Map; @method getRenderer(layer: Path): Renderer
8252         // Returns the instance of `Renderer` that should be used to render the given
8253         // `Path`. It will ensure that the `renderer` options of the map and paths
8254         // are respected, and that the renderers do exist on the map.
8255         getRenderer: function (layer) {
8256                 // @namespace Path; @option renderer: Renderer
8257                 // Use this specific instance of `Renderer` for this path. Takes
8258                 // precedence over the map's [default renderer](#map-renderer).
8259                 var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;
8260
8261                 if (!renderer) {
8262                         // @namespace Map; @option preferCanvas: Boolean = false
8263                         // Whether `Path`s should be rendered on a `Canvas` renderer.
8264                         // By default, all `Path`s are rendered in a `SVG` renderer.
8265                         renderer = this._renderer = (this.options.preferCanvas && L.canvas()) || L.svg();
8266                 }
8267
8268                 if (!this.hasLayer(renderer)) {
8269                         this.addLayer(renderer);
8270                 }
8271                 return renderer;
8272         },
8273
8274         _getPaneRenderer: function (name) {
8275                 if (name === 'overlayPane' || name === undefined) {
8276                         return false;
8277                 }
8278
8279                 var renderer = this._paneRenderers[name];
8280                 if (renderer === undefined) {
8281                         renderer = (L.SVG && L.svg({pane: name})) || (L.Canvas && L.canvas({pane: name}));
8282                         this._paneRenderers[name] = renderer;
8283                 }
8284                 return renderer;
8285         }
8286 });
8287
8288
8289
8290 /*
8291  * @class Path
8292  * @aka L.Path
8293  * @inherits Interactive layer
8294  *
8295  * An abstract class that contains options and constants shared between vector
8296  * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
8297  */
8298
8299 L.Path = L.Layer.extend({
8300
8301         // @section
8302         // @aka Path options
8303         options: {
8304                 // @option stroke: Boolean = true
8305                 // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
8306                 stroke: true,
8307
8308                 // @option color: String = '#3388ff'
8309                 // Stroke color
8310                 color: '#3388ff',
8311
8312                 // @option weight: Number = 3
8313                 // Stroke width in pixels
8314                 weight: 3,
8315
8316                 // @option opacity: Number = 1.0
8317                 // Stroke opacity
8318                 opacity: 1,
8319
8320                 // @option lineCap: String= 'round'
8321                 // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
8322                 lineCap: 'round',
8323
8324                 // @option lineJoin: String = 'round'
8325                 // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
8326                 lineJoin: 'round',
8327
8328                 // @option dashArray: String = null
8329                 // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
8330                 dashArray: null,
8331
8332                 // @option dashOffset: String = null
8333                 // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
8334                 dashOffset: null,
8335
8336                 // @option fill: Boolean = depends
8337                 // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
8338                 fill: false,
8339
8340                 // @option fillColor: String = *
8341                 // Fill color. Defaults to the value of the [`color`](#path-color) option
8342                 fillColor: null,
8343
8344                 // @option fillOpacity: Number = 0.2
8345                 // Fill opacity.
8346                 fillOpacity: 0.2,
8347
8348                 // @option fillRule: String = 'evenodd'
8349                 // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
8350                 fillRule: 'evenodd',
8351
8352                 // className: '',
8353
8354                 // Option inherited from "Interactive layer" abstract class
8355                 interactive: true
8356         },
8357
8358         beforeAdd: function (map) {
8359                 // Renderer is set here because we need to call renderer.getEvents
8360                 // before this.getEvents.
8361                 this._renderer = map.getRenderer(this);
8362         },
8363
8364         onAdd: function () {
8365                 this._renderer._initPath(this);
8366                 this._reset();
8367                 this._renderer._addPath(this);
8368         },
8369
8370         onRemove: function () {
8371                 this._renderer._removePath(this);
8372         },
8373
8374         // @method redraw(): this
8375         // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
8376         redraw: function () {
8377                 if (this._map) {
8378                         this._renderer._updatePath(this);
8379                 }
8380                 return this;
8381         },
8382
8383         // @method setStyle(style: Path options): this
8384         // Changes the appearance of a Path based on the options in the `Path options` object.
8385         setStyle: function (style) {
8386                 L.setOptions(this, style);
8387                 if (this._renderer) {
8388                         this._renderer._updateStyle(this);
8389                 }
8390                 return this;
8391         },
8392
8393         // @method bringToFront(): this
8394         // Brings the layer to the top of all path layers.
8395         bringToFront: function () {
8396                 if (this._renderer) {
8397                         this._renderer._bringToFront(this);
8398                 }
8399                 return this;
8400         },
8401
8402         // @method bringToBack(): this
8403         // Brings the layer to the bottom of all path layers.
8404         bringToBack: function () {
8405                 if (this._renderer) {
8406                         this._renderer._bringToBack(this);
8407                 }
8408                 return this;
8409         },
8410
8411         getElement: function () {
8412                 return this._path;
8413         },
8414
8415         _reset: function () {
8416                 // defined in children classes
8417                 this._project();
8418                 this._update();
8419         },
8420
8421         _clickTolerance: function () {
8422                 // used when doing hit detection for Canvas layers
8423                 return (this.options.stroke ? this.options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0);
8424         }
8425 });
8426
8427
8428
8429 /*
8430  * @namespace LineUtil
8431  *
8432  * Various utility functions for polyine points processing, used by Leaflet internally to make polylines lightning-fast.
8433  */
8434
8435 L.LineUtil = {
8436
8437         // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
8438         // Improves rendering performance dramatically by lessening the number of points to draw.
8439
8440         // @function simplify(points: Point[], tolerance: Number): Point[]
8441         // Dramatically reduces the number of points in a polyline while retaining
8442         // its shape and returns a new array of simplified points, using the
8443         // [Douglas-Peucker algorithm](http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm).
8444         // Used for a huge performance boost when processing/displaying Leaflet polylines for
8445         // each zoom level and also reducing visual noise. tolerance affects the amount of
8446         // simplification (lesser value means higher quality but slower and with more points).
8447         // Also released as a separated micro-library [Simplify.js](http://mourner.github.com/simplify-js/).
8448         simplify: function (points, tolerance) {
8449                 if (!tolerance || !points.length) {
8450                         return points.slice();
8451                 }
8452
8453                 var sqTolerance = tolerance * tolerance;
8454
8455                 // stage 1: vertex reduction
8456                 points = this._reducePoints(points, sqTolerance);
8457
8458                 // stage 2: Douglas-Peucker simplification
8459                 points = this._simplifyDP(points, sqTolerance);
8460
8461                 return points;
8462         },
8463
8464         // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
8465         // Returns the distance between point `p` and segment `p1` to `p2`.
8466         pointToSegmentDistance:  function (p, p1, p2) {
8467                 return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
8468         },
8469
8470         // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
8471         // Returns the closest point from a point `p` on a segment `p1` to `p2`.
8472         closestPointOnSegment: function (p, p1, p2) {
8473                 return this._sqClosestPointOnSegment(p, p1, p2);
8474         },
8475
8476         // Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
8477         _simplifyDP: function (points, sqTolerance) {
8478
8479                 var len = points.length,
8480                     ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
8481                     markers = new ArrayConstructor(len);
8482
8483                 markers[0] = markers[len - 1] = 1;
8484
8485                 this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
8486
8487                 var i,
8488                     newPoints = [];
8489
8490                 for (i = 0; i < len; i++) {
8491                         if (markers[i]) {
8492                                 newPoints.push(points[i]);
8493                         }
8494                 }
8495
8496                 return newPoints;
8497         },
8498
8499         _simplifyDPStep: function (points, markers, sqTolerance, first, last) {
8500
8501                 var maxSqDist = 0,
8502                     index, i, sqDist;
8503
8504                 for (i = first + 1; i <= last - 1; i++) {
8505                         sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
8506
8507                         if (sqDist > maxSqDist) {
8508                                 index = i;
8509                                 maxSqDist = sqDist;
8510                         }
8511                 }
8512
8513                 if (maxSqDist > sqTolerance) {
8514                         markers[index] = 1;
8515
8516                         this._simplifyDPStep(points, markers, sqTolerance, first, index);
8517                         this._simplifyDPStep(points, markers, sqTolerance, index, last);
8518                 }
8519         },
8520
8521         // reduce points that are too close to each other to a single point
8522         _reducePoints: function (points, sqTolerance) {
8523                 var reducedPoints = [points[0]];
8524
8525                 for (var i = 1, prev = 0, len = points.length; i < len; i++) {
8526                         if (this._sqDist(points[i], points[prev]) > sqTolerance) {
8527                                 reducedPoints.push(points[i]);
8528                                 prev = i;
8529                         }
8530                 }
8531                 if (prev < len - 1) {
8532                         reducedPoints.push(points[len - 1]);
8533                 }
8534                 return reducedPoints;
8535         },
8536
8537
8538         // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
8539         // Clips the segment a to b by rectangular bounds with the
8540         // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
8541         // (modifying the segment points directly!). Used by Leaflet to only show polyline
8542         // points that are on the screen or near, increasing performance.
8543         clipSegment: function (a, b, bounds, useLastCode, round) {
8544                 var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
8545                     codeB = this._getBitCode(b, bounds),
8546
8547                     codeOut, p, newCode;
8548
8549                 // save 2nd code to avoid calculating it on the next segment
8550                 this._lastCode = codeB;
8551
8552                 while (true) {
8553                         // if a,b is inside the clip window (trivial accept)
8554                         if (!(codeA | codeB)) {
8555                                 return [a, b];
8556                         }
8557
8558                         // if a,b is outside the clip window (trivial reject)
8559                         if (codeA & codeB) {
8560                                 return false;
8561                         }
8562
8563                         // other cases
8564                         codeOut = codeA || codeB;
8565                         p = this._getEdgeIntersection(a, b, codeOut, bounds, round);
8566                         newCode = this._getBitCode(p, bounds);
8567
8568                         if (codeOut === codeA) {
8569                                 a = p;
8570                                 codeA = newCode;
8571                         } else {
8572                                 b = p;
8573                                 codeB = newCode;
8574                         }
8575                 }
8576         },
8577
8578         _getEdgeIntersection: function (a, b, code, bounds, round) {
8579                 var dx = b.x - a.x,
8580                     dy = b.y - a.y,
8581                     min = bounds.min,
8582                     max = bounds.max,
8583                     x, y;
8584
8585                 if (code & 8) { // top
8586                         x = a.x + dx * (max.y - a.y) / dy;
8587                         y = max.y;
8588
8589                 } else if (code & 4) { // bottom
8590                         x = a.x + dx * (min.y - a.y) / dy;
8591                         y = min.y;
8592
8593                 } else if (code & 2) { // right
8594                         x = max.x;
8595                         y = a.y + dy * (max.x - a.x) / dx;
8596
8597                 } else if (code & 1) { // left
8598                         x = min.x;
8599                         y = a.y + dy * (min.x - a.x) / dx;
8600                 }
8601
8602                 return new L.Point(x, y, round);
8603         },
8604
8605         _getBitCode: function (p, bounds) {
8606                 var code = 0;
8607
8608                 if (p.x < bounds.min.x) { // left
8609                         code |= 1;
8610                 } else if (p.x > bounds.max.x) { // right
8611                         code |= 2;
8612                 }
8613
8614                 if (p.y < bounds.min.y) { // bottom
8615                         code |= 4;
8616                 } else if (p.y > bounds.max.y) { // top
8617                         code |= 8;
8618                 }
8619
8620                 return code;
8621         },
8622
8623         // square distance (to avoid unnecessary Math.sqrt calls)
8624         _sqDist: function (p1, p2) {
8625                 var dx = p2.x - p1.x,
8626                     dy = p2.y - p1.y;
8627                 return dx * dx + dy * dy;
8628         },
8629
8630         // return closest point on segment or distance to that point
8631         _sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
8632                 var x = p1.x,
8633                     y = p1.y,
8634                     dx = p2.x - x,
8635                     dy = p2.y - y,
8636                     dot = dx * dx + dy * dy,
8637                     t;
8638
8639                 if (dot > 0) {
8640                         t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
8641
8642                         if (t > 1) {
8643                                 x = p2.x;
8644                                 y = p2.y;
8645                         } else if (t > 0) {
8646                                 x += dx * t;
8647                                 y += dy * t;
8648                         }
8649                 }
8650
8651                 dx = p.x - x;
8652                 dy = p.y - y;
8653
8654                 return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
8655         }
8656 };
8657
8658
8659
8660 /*
8661  * @class Polyline
8662  * @aka L.Polyline
8663  * @inherits Path
8664  *
8665  * A class for drawing polyline overlays on a map. Extends `Path`.
8666  *
8667  * @example
8668  *
8669  * ```js
8670  * // create a red polyline from an array of LatLng points
8671  * var latlngs = [
8672  *      [45.51, -122.68],
8673  *      [37.77, -122.43],
8674  *      [34.04, -118.2]
8675  * ];
8676  *
8677  * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
8678  *
8679  * // zoom the map to the polyline
8680  * map.fitBounds(polyline.getBounds());
8681  * ```
8682  *
8683  * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
8684  *
8685  * ```js
8686  * // create a red polyline from an array of arrays of LatLng points
8687  * var latlngs = [
8688  *      [[45.51, -122.68],
8689  *       [37.77, -122.43],
8690  *       [34.04, -118.2]],
8691  *      [[40.78, -73.91],
8692  *       [41.83, -87.62],
8693  *       [32.76, -96.72]]
8694  * ];
8695  * ```
8696  */
8697
8698 L.Polyline = L.Path.extend({
8699
8700         // @section
8701         // @aka Polyline options
8702         options: {
8703                 // @option smoothFactor: Number = 1.0
8704                 // How much to simplify the polyline on each zoom level. More means
8705                 // better performance and smoother look, and less means more accurate representation.
8706                 smoothFactor: 1.0,
8707
8708                 // @option noClip: Boolean = false
8709                 // Disable polyline clipping.
8710                 noClip: false
8711         },
8712
8713         initialize: function (latlngs, options) {
8714                 L.setOptions(this, options);
8715                 this._setLatLngs(latlngs);
8716         },
8717
8718         // @method getLatLngs(): LatLng[]
8719         // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
8720         getLatLngs: function () {
8721                 return this._latlngs;
8722         },
8723
8724         // @method setLatLngs(latlngs: LatLng[]): this
8725         // Replaces all the points in the polyline with the given array of geographical points.
8726         setLatLngs: function (latlngs) {
8727                 this._setLatLngs(latlngs);
8728                 return this.redraw();
8729         },
8730
8731         // @method isEmpty(): Boolean
8732         // Returns `true` if the Polyline has no LatLngs.
8733         isEmpty: function () {
8734                 return !this._latlngs.length;
8735         },
8736
8737         closestLayerPoint: function (p) {
8738                 var minDistance = Infinity,
8739                     minPoint = null,
8740                     closest = L.LineUtil._sqClosestPointOnSegment,
8741                     p1, p2;
8742
8743                 for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
8744                         var points = this._parts[j];
8745
8746                         for (var i = 1, len = points.length; i < len; i++) {
8747                                 p1 = points[i - 1];
8748                                 p2 = points[i];
8749
8750                                 var sqDist = closest(p, p1, p2, true);
8751
8752                                 if (sqDist < minDistance) {
8753                                         minDistance = sqDist;
8754                                         minPoint = closest(p, p1, p2);
8755                                 }
8756                         }
8757                 }
8758                 if (minPoint) {
8759                         minPoint.distance = Math.sqrt(minDistance);
8760                 }
8761                 return minPoint;
8762         },
8763
8764         // @method getCenter(): LatLng
8765         // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the polyline.
8766         getCenter: function () {
8767                 // throws error when not yet added to map as this center calculation requires projected coordinates
8768                 if (!this._map) {
8769                         throw new Error('Must add layer to map before using getCenter()');
8770                 }
8771
8772                 var i, halfDist, segDist, dist, p1, p2, ratio,
8773                     points = this._rings[0],
8774                     len = points.length;
8775
8776                 if (!len) { return null; }
8777
8778                 // polyline centroid algorithm; only uses the first ring if there are multiple
8779
8780                 for (i = 0, halfDist = 0; i < len - 1; i++) {
8781                         halfDist += points[i].distanceTo(points[i + 1]) / 2;
8782                 }
8783
8784                 // The line is so small in the current view that all points are on the same pixel.
8785                 if (halfDist === 0) {
8786                         return this._map.layerPointToLatLng(points[0]);
8787                 }
8788
8789                 for (i = 0, dist = 0; i < len - 1; i++) {
8790                         p1 = points[i];
8791                         p2 = points[i + 1];
8792                         segDist = p1.distanceTo(p2);
8793                         dist += segDist;
8794
8795                         if (dist > halfDist) {
8796                                 ratio = (dist - halfDist) / segDist;
8797                                 return this._map.layerPointToLatLng([
8798                                         p2.x - ratio * (p2.x - p1.x),
8799                                         p2.y - ratio * (p2.y - p1.y)
8800                                 ]);
8801                         }
8802                 }
8803         },
8804
8805         // @method getBounds(): LatLngBounds
8806         // Returns the `LatLngBounds` of the path.
8807         getBounds: function () {
8808                 return this._bounds;
8809         },
8810
8811         // @method addLatLng(latlng: LatLng, latlngs? LatLng[]): this
8812         // Adds a given point to the polyline. By default, adds to the first ring of
8813         // the polyline in case of a multi-polyline, but can be overridden by passing
8814         // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
8815         addLatLng: function (latlng, latlngs) {
8816                 latlngs = latlngs || this._defaultShape();
8817                 latlng = L.latLng(latlng);
8818                 latlngs.push(latlng);
8819                 this._bounds.extend(latlng);
8820                 return this.redraw();
8821         },
8822
8823         _setLatLngs: function (latlngs) {
8824                 this._bounds = new L.LatLngBounds();
8825                 this._latlngs = this._convertLatLngs(latlngs);
8826         },
8827
8828         _defaultShape: function () {
8829                 return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
8830         },
8831
8832         // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
8833         _convertLatLngs: function (latlngs) {
8834                 var result = [],
8835                     flat = L.Polyline._flat(latlngs);
8836
8837                 for (var i = 0, len = latlngs.length; i < len; i++) {
8838                         if (flat) {
8839                                 result[i] = L.latLng(latlngs[i]);
8840                                 this._bounds.extend(result[i]);
8841                         } else {
8842                                 result[i] = this._convertLatLngs(latlngs[i]);
8843                         }
8844                 }
8845
8846                 return result;
8847         },
8848
8849         _project: function () {
8850                 var pxBounds = new L.Bounds();
8851                 this._rings = [];
8852                 this._projectLatlngs(this._latlngs, this._rings, pxBounds);
8853
8854                 var w = this._clickTolerance(),
8855                     p = new L.Point(w, w);
8856
8857                 if (this._bounds.isValid() && pxBounds.isValid()) {
8858                         pxBounds.min._subtract(p);
8859                         pxBounds.max._add(p);
8860                         this._pxBounds = pxBounds;
8861                 }
8862         },
8863
8864         // recursively turns latlngs into a set of rings with projected coordinates
8865         _projectLatlngs: function (latlngs, result, projectedBounds) {
8866                 var flat = latlngs[0] instanceof L.LatLng,
8867                     len = latlngs.length,
8868                     i, ring;
8869
8870                 if (flat) {
8871                         ring = [];
8872                         for (i = 0; i < len; i++) {
8873                                 ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
8874                                 projectedBounds.extend(ring[i]);
8875                         }
8876                         result.push(ring);
8877                 } else {
8878                         for (i = 0; i < len; i++) {
8879                                 this._projectLatlngs(latlngs[i], result, projectedBounds);
8880                         }
8881                 }
8882         },
8883
8884         // clip polyline by renderer bounds so that we have less to render for performance
8885         _clipPoints: function () {
8886                 var bounds = this._renderer._bounds;
8887
8888                 this._parts = [];
8889                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
8890                         return;
8891                 }
8892
8893                 if (this.options.noClip) {
8894                         this._parts = this._rings;
8895                         return;
8896                 }
8897
8898                 var parts = this._parts,
8899                     i, j, k, len, len2, segment, points;
8900
8901                 for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
8902                         points = this._rings[i];
8903
8904                         for (j = 0, len2 = points.length; j < len2 - 1; j++) {
8905                                 segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
8906
8907                                 if (!segment) { continue; }
8908
8909                                 parts[k] = parts[k] || [];
8910                                 parts[k].push(segment[0]);
8911
8912                                 // if segment goes out of screen, or it's the last one, it's the end of the line part
8913                                 if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
8914                                         parts[k].push(segment[1]);
8915                                         k++;
8916                                 }
8917                         }
8918                 }
8919         },
8920
8921         // simplify each clipped part of the polyline for performance
8922         _simplifyPoints: function () {
8923                 var parts = this._parts,
8924                     tolerance = this.options.smoothFactor;
8925
8926                 for (var i = 0, len = parts.length; i < len; i++) {
8927                         parts[i] = L.LineUtil.simplify(parts[i], tolerance);
8928                 }
8929         },
8930
8931         _update: function () {
8932                 if (!this._map) { return; }
8933
8934                 this._clipPoints();
8935                 this._simplifyPoints();
8936                 this._updatePath();
8937         },
8938
8939         _updatePath: function () {
8940                 this._renderer._updatePoly(this);
8941         }
8942 });
8943
8944 // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
8945 // Instantiates a polyline object given an array of geographical points and
8946 // optionally an options object. You can create a `Polyline` object with
8947 // multiple separate lines (`MultiPolyline`) by passing an array of arrays
8948 // of geographic points.
8949 L.polyline = function (latlngs, options) {
8950         return new L.Polyline(latlngs, options);
8951 };
8952
8953 L.Polyline._flat = function (latlngs) {
8954         // true if it's a flat array of latlngs; false if nested
8955         return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
8956 };
8957
8958
8959
8960 /*
8961  * @namespace PolyUtil
8962  * Various utility functions for polygon geometries.
8963  */
8964
8965 L.PolyUtil = {};
8966
8967 /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
8968  * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgeman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
8969  * Used by Leaflet to only show polygon points that are on the screen or near, increasing
8970  * performance. Note that polygon points needs different algorithm for clipping
8971  * than polyline, so there's a seperate method for it.
8972  */
8973 L.PolyUtil.clipPolygon = function (points, bounds, round) {
8974         var clippedPoints,
8975             edges = [1, 4, 2, 8],
8976             i, j, k,
8977             a, b,
8978             len, edge, p,
8979             lu = L.LineUtil;
8980
8981         for (i = 0, len = points.length; i < len; i++) {
8982                 points[i]._code = lu._getBitCode(points[i], bounds);
8983         }
8984
8985         // for each edge (left, bottom, right, top)
8986         for (k = 0; k < 4; k++) {
8987                 edge = edges[k];
8988                 clippedPoints = [];
8989
8990                 for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
8991                         a = points[i];
8992                         b = points[j];
8993
8994                         // if a is inside the clip window
8995                         if (!(a._code & edge)) {
8996                                 // if b is outside the clip window (a->b goes out of screen)
8997                                 if (b._code & edge) {
8998                                         p = lu._getEdgeIntersection(b, a, edge, bounds, round);
8999                                         p._code = lu._getBitCode(p, bounds);
9000                                         clippedPoints.push(p);
9001                                 }
9002                                 clippedPoints.push(a);
9003
9004                         // else if b is inside the clip window (a->b enters the screen)
9005                         } else if (!(b._code & edge)) {
9006                                 p = lu._getEdgeIntersection(b, a, edge, bounds, round);
9007                                 p._code = lu._getBitCode(p, bounds);
9008                                 clippedPoints.push(p);
9009                         }
9010                 }
9011                 points = clippedPoints;
9012         }
9013
9014         return points;
9015 };
9016
9017
9018
9019 /*
9020  * @class Polygon
9021  * @aka L.Polygon
9022  * @inherits Polyline
9023  *
9024  * A class for drawing polygon overlays on a map. Extends `Polyline`.
9025  *
9026  * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
9027  *
9028  *
9029  * @example
9030  *
9031  * ```js
9032  * // create a red polygon from an array of LatLng points
9033  * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
9034  *
9035  * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
9036  *
9037  * // zoom the map to the polygon
9038  * map.fitBounds(polygon.getBounds());
9039  * ```
9040  *
9041  * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
9042  *
9043  * ```js
9044  * var latlngs = [
9045  *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
9046  *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
9047  * ];
9048  * ```
9049  *
9050  * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
9051  *
9052  * ```js
9053  * var latlngs = [
9054  *   [ // first polygon
9055  *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
9056  *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
9057  *   ],
9058  *   [ // second polygon
9059  *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
9060  *   ]
9061  * ];
9062  * ```
9063  */
9064
9065 L.Polygon = L.Polyline.extend({
9066
9067         options: {
9068                 fill: true
9069         },
9070
9071         isEmpty: function () {
9072                 return !this._latlngs.length || !this._latlngs[0].length;
9073         },
9074
9075         getCenter: function () {
9076                 // throws error when not yet added to map as this center calculation requires projected coordinates
9077                 if (!this._map) {
9078                         throw new Error('Must add layer to map before using getCenter()');
9079                 }
9080
9081                 var i, j, p1, p2, f, area, x, y, center,
9082                     points = this._rings[0],
9083                     len = points.length;
9084
9085                 if (!len) { return null; }
9086
9087                 // polygon centroid algorithm; only uses the first ring if there are multiple
9088
9089                 area = x = y = 0;
9090
9091                 for (i = 0, j = len - 1; i < len; j = i++) {
9092                         p1 = points[i];
9093                         p2 = points[j];
9094
9095                         f = p1.y * p2.x - p2.y * p1.x;
9096                         x += (p1.x + p2.x) * f;
9097                         y += (p1.y + p2.y) * f;
9098                         area += f * 3;
9099                 }
9100
9101                 if (area === 0) {
9102                         // Polygon is so small that all points are on same pixel.
9103                         center = points[0];
9104                 } else {
9105                         center = [x / area, y / area];
9106                 }
9107                 return this._map.layerPointToLatLng(center);
9108         },
9109
9110         _convertLatLngs: function (latlngs) {
9111                 var result = L.Polyline.prototype._convertLatLngs.call(this, latlngs),
9112                     len = result.length;
9113
9114                 // remove last point if it equals first one
9115                 if (len >= 2 && result[0] instanceof L.LatLng && result[0].equals(result[len - 1])) {
9116                         result.pop();
9117                 }
9118                 return result;
9119         },
9120
9121         _setLatLngs: function (latlngs) {
9122                 L.Polyline.prototype._setLatLngs.call(this, latlngs);
9123                 if (L.Polyline._flat(this._latlngs)) {
9124                         this._latlngs = [this._latlngs];
9125                 }
9126         },
9127
9128         _defaultShape: function () {
9129                 return L.Polyline._flat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
9130         },
9131
9132         _clipPoints: function () {
9133                 // polygons need a different clipping algorithm so we redefine that
9134
9135                 var bounds = this._renderer._bounds,
9136                     w = this.options.weight,
9137                     p = new L.Point(w, w);
9138
9139                 // increase clip padding by stroke width to avoid stroke on clip edges
9140                 bounds = new L.Bounds(bounds.min.subtract(p), bounds.max.add(p));
9141
9142                 this._parts = [];
9143                 if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
9144                         return;
9145                 }
9146
9147                 if (this.options.noClip) {
9148                         this._parts = this._rings;
9149                         return;
9150                 }
9151
9152                 for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
9153                         clipped = L.PolyUtil.clipPolygon(this._rings[i], bounds, true);
9154                         if (clipped.length) {
9155                                 this._parts.push(clipped);
9156                         }
9157                 }
9158         },
9159
9160         _updatePath: function () {
9161                 this._renderer._updatePoly(this, true);
9162         }
9163 });
9164
9165
9166 // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
9167 L.polygon = function (latlngs, options) {
9168         return new L.Polygon(latlngs, options);
9169 };
9170
9171
9172
9173 /*
9174  * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
9175  */
9176
9177 /*
9178  * @class Rectangle
9179  * @aka L.Retangle
9180  * @inherits Polygon
9181  *
9182  * A class for drawing rectangle overlays on a map. Extends `Polygon`.
9183  *
9184  * @example
9185  *
9186  * ```js
9187  * // define rectangle geographical bounds
9188  * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
9189  *
9190  * // create an orange rectangle
9191  * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
9192  *
9193  * // zoom the map to the rectangle bounds
9194  * map.fitBounds(bounds);
9195  * ```
9196  *
9197  */
9198
9199
9200 L.Rectangle = L.Polygon.extend({
9201         initialize: function (latLngBounds, options) {
9202                 L.Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
9203         },
9204
9205         // @method setBounds(latLngBounds: LatLngBounds): this
9206         // Redraws the rectangle with the passed bounds.
9207         setBounds: function (latLngBounds) {
9208                 return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
9209         },
9210
9211         _boundsToLatLngs: function (latLngBounds) {
9212                 latLngBounds = L.latLngBounds(latLngBounds);
9213                 return [
9214                         latLngBounds.getSouthWest(),
9215                         latLngBounds.getNorthWest(),
9216                         latLngBounds.getNorthEast(),
9217                         latLngBounds.getSouthEast()
9218                 ];
9219         }
9220 });
9221
9222
9223 // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
9224 L.rectangle = function (latLngBounds, options) {
9225         return new L.Rectangle(latLngBounds, options);
9226 };
9227
9228
9229
9230 /*
9231  * @class CircleMarker
9232  * @aka L.CircleMarker
9233  * @inherits Path
9234  *
9235  * A circle of a fixed size with radius specified in pixels. Extends `Path`.
9236  */
9237
9238 L.CircleMarker = L.Path.extend({
9239
9240         // @section
9241         // @aka CircleMarker options
9242         options: {
9243                 fill: true,
9244
9245                 // @option radius: Number = 10
9246                 // Radius of the circle marker, in pixels
9247                 radius: 10
9248         },
9249
9250         initialize: function (latlng, options) {
9251                 L.setOptions(this, options);
9252                 this._latlng = L.latLng(latlng);
9253                 this._radius = this.options.radius;
9254         },
9255
9256         // @method setLatLng(latLng: LatLng): this
9257         // Sets the position of a circle marker to a new location.
9258         setLatLng: function (latlng) {
9259                 this._latlng = L.latLng(latlng);
9260                 this.redraw();
9261                 return this.fire('move', {latlng: this._latlng});
9262         },
9263
9264         // @method getLatLng(): LatLng
9265         // Returns the current geographical position of the circle marker
9266         getLatLng: function () {
9267                 return this._latlng;
9268         },
9269
9270         // @method setRadius(radius: Number): this
9271         // Sets the radius of a circle marker. Units are in pixels.
9272         setRadius: function (radius) {
9273                 this.options.radius = this._radius = radius;
9274                 return this.redraw();
9275         },
9276
9277         // @method getRadius(): Number
9278         // Returns the current radius of the circle
9279         getRadius: function () {
9280                 return this._radius;
9281         },
9282
9283         setStyle : function (options) {
9284                 var radius = options && options.radius || this._radius;
9285                 L.Path.prototype.setStyle.call(this, options);
9286                 this.setRadius(radius);
9287                 return this;
9288         },
9289
9290         _project: function () {
9291                 this._point = this._map.latLngToLayerPoint(this._latlng);
9292                 this._updateBounds();
9293         },
9294
9295         _updateBounds: function () {
9296                 var r = this._radius,
9297                     r2 = this._radiusY || r,
9298                     w = this._clickTolerance(),
9299                     p = [r + w, r2 + w];
9300                 this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
9301         },
9302
9303         _update: function () {
9304                 if (this._map) {
9305                         this._updatePath();
9306                 }
9307         },
9308
9309         _updatePath: function () {
9310                 this._renderer._updateCircle(this);
9311         },
9312
9313         _empty: function () {
9314                 return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
9315         }
9316 });
9317
9318
9319 // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
9320 // Instantiates a circle marker object given a geographical point, and an optional options object.
9321 L.circleMarker = function (latlng, options) {
9322         return new L.CircleMarker(latlng, options);
9323 };
9324
9325
9326
9327 /*
9328  * @class Circle
9329  * @aka L.Circle
9330  * @inherits CircleMarker
9331  *
9332  * A class for drawing circle overlays on a map. Extends `CircleMarker`.
9333  *
9334  * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
9335  *
9336  * @example
9337  *
9338  * ```js
9339  * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
9340  * ```
9341  */
9342
9343 L.Circle = L.CircleMarker.extend({
9344
9345         initialize: function (latlng, options, legacyOptions) {
9346                 if (typeof options === 'number') {
9347                         // Backwards compatibility with 0.7.x factory (latlng, radius, options?)
9348                         options = L.extend({}, legacyOptions, {radius: options});
9349                 }
9350                 L.setOptions(this, options);
9351                 this._latlng = L.latLng(latlng);
9352
9353                 if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }
9354
9355                 // @section
9356                 // @aka Circle options
9357                 // @option radius: Number; Radius of the circle, in meters.
9358                 this._mRadius = this.options.radius;
9359         },
9360
9361         // @method setRadius(radius: Number): this
9362         // Sets the radius of a circle. Units are in meters.
9363         setRadius: function (radius) {
9364                 this._mRadius = radius;
9365                 return this.redraw();
9366         },
9367
9368         // @method getRadius(): Number
9369         // Returns the current radius of a circle. Units are in meters.
9370         getRadius: function () {
9371                 return this._mRadius;
9372         },
9373
9374         // @method getBounds(): LatLngBounds
9375         // Returns the `LatLngBounds` of the path.
9376         getBounds: function () {
9377                 var half = [this._radius, this._radiusY || this._radius];
9378
9379                 return new L.LatLngBounds(
9380                         this._map.layerPointToLatLng(this._point.subtract(half)),
9381                         this._map.layerPointToLatLng(this._point.add(half)));
9382         },
9383
9384         setStyle: L.Path.prototype.setStyle,
9385
9386         _project: function () {
9387
9388                 var lng = this._latlng.lng,
9389                     lat = this._latlng.lat,
9390                     map = this._map,
9391                     crs = map.options.crs;
9392
9393                 if (crs.distance === L.CRS.Earth.distance) {
9394                         var d = Math.PI / 180,
9395                             latR = (this._mRadius / L.CRS.Earth.R) / d,
9396                             top = map.project([lat + latR, lng]),
9397                             bottom = map.project([lat - latR, lng]),
9398                             p = top.add(bottom).divideBy(2),
9399                             lat2 = map.unproject(p).lat,
9400                             lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
9401                                     (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;
9402
9403                         if (isNaN(lngR) || lngR === 0) {
9404                                 lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
9405                         }
9406
9407                         this._point = p.subtract(map.getPixelOrigin());
9408                         this._radius = isNaN(lngR) ? 0 : Math.max(Math.round(p.x - map.project([lat2, lng - lngR]).x), 1);
9409                         this._radiusY = Math.max(Math.round(p.y - top.y), 1);
9410
9411                 } else {
9412                         var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));
9413
9414                         this._point = map.latLngToLayerPoint(this._latlng);
9415                         this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
9416                 }
9417
9418                 this._updateBounds();
9419         }
9420 });
9421
9422 // @factory L.circle(latlng: LatLng, options?: Circle options)
9423 // Instantiates a circle object given a geographical point, and an options object
9424 // which contains the circle radius.
9425 // @alternative
9426 // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
9427 // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
9428 // Do not use in new applications or plugins.
9429 L.circle = function (latlng, options, legacyOptions) {
9430         return new L.Circle(latlng, options, legacyOptions);
9431 };
9432
9433
9434
9435 /*
9436  * @class SVG
9437  * @inherits Renderer
9438  * @aka L.SVG
9439  *
9440  * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
9441  * Inherits `Renderer`.
9442  *
9443  * Due to [technical limitations](http://caniuse.com/#search=svg), SVG is not
9444  * available in all web browsers, notably Android 2.x and 3.x.
9445  *
9446  * Although SVG is not available on IE7 and IE8, these browsers support
9447  * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
9448  * (a now deprecated technology), and the SVG renderer will fall back to VML in
9449  * this case.
9450  *
9451  * @example
9452  *
9453  * Use SVG by default for all paths in the map:
9454  *
9455  * ```js
9456  * var map = L.map('map', {
9457  *      renderer: L.svg()
9458  * });
9459  * ```
9460  *
9461  * Use a SVG renderer with extra padding for specific vector geometries:
9462  *
9463  * ```js
9464  * var map = L.map('map');
9465  * var myRenderer = L.svg({ padding: 0.5 });
9466  * var line = L.polyline( coordinates, { renderer: myRenderer } );
9467  * var circle = L.circle( center, { renderer: myRenderer } );
9468  * ```
9469  */
9470
9471 L.SVG = L.Renderer.extend({
9472
9473         getEvents: function () {
9474                 var events = L.Renderer.prototype.getEvents.call(this);
9475                 events.zoomstart = this._onZoomStart;
9476                 return events;
9477         },
9478
9479         _initContainer: function () {
9480                 this._container = L.SVG.create('svg');
9481
9482                 // makes it possible to click through svg root; we'll reset it back in individual paths
9483                 this._container.setAttribute('pointer-events', 'none');
9484
9485                 this._rootGroup = L.SVG.create('g');
9486                 this._container.appendChild(this._rootGroup);
9487         },
9488
9489         _onZoomStart: function () {
9490                 // Drag-then-pinch interactions might mess up the center and zoom.
9491                 // In this case, the easiest way to prevent this is re-do the renderer
9492                 //   bounds and padding when the zooming starts.
9493                 this._update();
9494         },
9495
9496         _update: function () {
9497                 if (this._map._animatingZoom && this._bounds) { return; }
9498
9499                 L.Renderer.prototype._update.call(this);
9500
9501                 var b = this._bounds,
9502                     size = b.getSize(),
9503                     container = this._container;
9504
9505                 // set size of svg-container if changed
9506                 if (!this._svgSize || !this._svgSize.equals(size)) {
9507                         this._svgSize = size;
9508                         container.setAttribute('width', size.x);
9509                         container.setAttribute('height', size.y);
9510                 }
9511
9512                 // movement: update container viewBox so that we don't have to change coordinates of individual layers
9513                 L.DomUtil.setPosition(container, b.min);
9514                 container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));
9515
9516                 this.fire('update');
9517         },
9518
9519         // methods below are called by vector layers implementations
9520
9521         _initPath: function (layer) {
9522                 var path = layer._path = L.SVG.create('path');
9523
9524                 // @namespace Path
9525                 // @option className: String = null
9526                 // Custom class name set on an element. Only for SVG renderer.
9527                 if (layer.options.className) {
9528                         L.DomUtil.addClass(path, layer.options.className);
9529                 }
9530
9531                 if (layer.options.interactive) {
9532                         L.DomUtil.addClass(path, 'leaflet-interactive');
9533                 }
9534
9535                 this._updateStyle(layer);
9536                 this._layers[L.stamp(layer)] = layer;
9537         },
9538
9539         _addPath: function (layer) {
9540                 this._rootGroup.appendChild(layer._path);
9541                 layer.addInteractiveTarget(layer._path);
9542         },
9543
9544         _removePath: function (layer) {
9545                 L.DomUtil.remove(layer._path);
9546                 layer.removeInteractiveTarget(layer._path);
9547                 delete this._layers[L.stamp(layer)];
9548         },
9549
9550         _updatePath: function (layer) {
9551                 layer._project();
9552                 layer._update();
9553         },
9554
9555         _updateStyle: function (layer) {
9556                 var path = layer._path,
9557                     options = layer.options;
9558
9559                 if (!path) { return; }
9560
9561                 if (options.stroke) {
9562                         path.setAttribute('stroke', options.color);
9563                         path.setAttribute('stroke-opacity', options.opacity);
9564                         path.setAttribute('stroke-width', options.weight);
9565                         path.setAttribute('stroke-linecap', options.lineCap);
9566                         path.setAttribute('stroke-linejoin', options.lineJoin);
9567
9568                         if (options.dashArray) {
9569                                 path.setAttribute('stroke-dasharray', options.dashArray);
9570                         } else {
9571                                 path.removeAttribute('stroke-dasharray');
9572                         }
9573
9574                         if (options.dashOffset) {
9575                                 path.setAttribute('stroke-dashoffset', options.dashOffset);
9576                         } else {
9577                                 path.removeAttribute('stroke-dashoffset');
9578                         }
9579                 } else {
9580                         path.setAttribute('stroke', 'none');
9581                 }
9582
9583                 if (options.fill) {
9584                         path.setAttribute('fill', options.fillColor || options.color);
9585                         path.setAttribute('fill-opacity', options.fillOpacity);
9586                         path.setAttribute('fill-rule', options.fillRule || 'evenodd');
9587                 } else {
9588                         path.setAttribute('fill', 'none');
9589                 }
9590         },
9591
9592         _updatePoly: function (layer, closed) {
9593                 this._setPath(layer, L.SVG.pointsToPath(layer._parts, closed));
9594         },
9595
9596         _updateCircle: function (layer) {
9597                 var p = layer._point,
9598                     r = layer._radius,
9599                     r2 = layer._radiusY || r,
9600                     arc = 'a' + r + ',' + r2 + ' 0 1,0 ';
9601
9602                 // drawing a circle with two half-arcs
9603                 var d = layer._empty() ? 'M0 0' :
9604                                 'M' + (p.x - r) + ',' + p.y +
9605                                 arc + (r * 2) + ',0 ' +
9606                                 arc + (-r * 2) + ',0 ';
9607
9608                 this._setPath(layer, d);
9609         },
9610
9611         _setPath: function (layer, path) {
9612                 layer._path.setAttribute('d', path);
9613         },
9614
9615         // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
9616         _bringToFront: function (layer) {
9617                 L.DomUtil.toFront(layer._path);
9618         },
9619
9620         _bringToBack: function (layer) {
9621                 L.DomUtil.toBack(layer._path);
9622         }
9623 });
9624
9625
9626 // @namespace SVG; @section
9627 // There are several static functions which can be called without instantiating L.SVG:
9628 L.extend(L.SVG, {
9629         // @function create(name: String): SVGElement
9630         // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
9631         // corresponding to the class name passed. For example, using 'line' will return
9632         // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
9633         create: function (name) {
9634                 return document.createElementNS('http://www.w3.org/2000/svg', name);
9635         },
9636
9637         // @function pointsToPath(rings: Point[], closed: Boolean): String
9638         // Generates a SVG path string for multiple rings, with each ring turning
9639         // into "M..L..L.." instructions
9640         pointsToPath: function (rings, closed) {
9641                 var str = '',
9642                     i, j, len, len2, points, p;
9643
9644                 for (i = 0, len = rings.length; i < len; i++) {
9645                         points = rings[i];
9646
9647                         for (j = 0, len2 = points.length; j < len2; j++) {
9648                                 p = points[j];
9649                                 str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
9650                         }
9651
9652                         // closes the ring for polygons; "x" is VML syntax
9653                         str += closed ? (L.Browser.svg ? 'z' : 'x') : '';
9654                 }
9655
9656                 // SVG complains about empty path strings
9657                 return str || 'M0 0';
9658         }
9659 });
9660
9661 // @namespace Browser; @property svg: Boolean
9662 // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
9663 L.Browser.svg = !!(document.createElementNS && L.SVG.create('svg').createSVGRect);
9664
9665
9666 // @namespace SVG
9667 // @factory L.svg(options?: Renderer options)
9668 // Creates a SVG renderer with the given options.
9669 L.svg = function (options) {
9670         return L.Browser.svg || L.Browser.vml ? new L.SVG(options) : null;
9671 };
9672
9673
9674
9675 /*
9676  * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
9677  */
9678
9679 /*
9680  * @class SVG
9681  *
9682  * Although SVG is not available on IE7 and IE8, these browsers support [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language), and the SVG renderer will fall back to VML in this case.
9683  *
9684  * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
9685  * with old versions of Internet Explorer.
9686  */
9687
9688 // @namespace Browser; @property vml: Boolean
9689 // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
9690 L.Browser.vml = !L.Browser.svg && (function () {
9691         try {
9692                 var div = document.createElement('div');
9693                 div.innerHTML = '<v:shape adj="1"/>';
9694
9695                 var shape = div.firstChild;
9696                 shape.style.behavior = 'url(#default#VML)';
9697
9698                 return shape && (typeof shape.adj === 'object');
9699
9700         } catch (e) {
9701                 return false;
9702         }
9703 }());
9704
9705 // redefine some SVG methods to handle VML syntax which is similar but with some differences
9706 L.SVG.include(!L.Browser.vml ? {} : {
9707
9708         _initContainer: function () {
9709                 this._container = L.DomUtil.create('div', 'leaflet-vml-container');
9710         },
9711
9712         _update: function () {
9713                 if (this._map._animatingZoom) { return; }
9714                 L.Renderer.prototype._update.call(this);
9715                 this.fire('update');
9716         },
9717
9718         _initPath: function (layer) {
9719                 var container = layer._container = L.SVG.create('shape');
9720
9721                 L.DomUtil.addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));
9722
9723                 container.coordsize = '1 1';
9724
9725                 layer._path = L.SVG.create('path');
9726                 container.appendChild(layer._path);
9727
9728                 this._updateStyle(layer);
9729                 this._layers[L.stamp(layer)] = layer;
9730         },
9731
9732         _addPath: function (layer) {
9733                 var container = layer._container;
9734                 this._container.appendChild(container);
9735
9736                 if (layer.options.interactive) {
9737                         layer.addInteractiveTarget(container);
9738                 }
9739         },
9740
9741         _removePath: function (layer) {
9742                 var container = layer._container;
9743                 L.DomUtil.remove(container);
9744                 layer.removeInteractiveTarget(container);
9745                 delete this._layers[L.stamp(layer)];
9746         },
9747
9748         _updateStyle: function (layer) {
9749                 var stroke = layer._stroke,
9750                     fill = layer._fill,
9751                     options = layer.options,
9752                     container = layer._container;
9753
9754                 container.stroked = !!options.stroke;
9755                 container.filled = !!options.fill;
9756
9757                 if (options.stroke) {
9758                         if (!stroke) {
9759                                 stroke = layer._stroke = L.SVG.create('stroke');
9760                         }
9761                         container.appendChild(stroke);
9762                         stroke.weight = options.weight + 'px';
9763                         stroke.color = options.color;
9764                         stroke.opacity = options.opacity;
9765
9766                         if (options.dashArray) {
9767                                 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
9768                                     options.dashArray.join(' ') :
9769                                     options.dashArray.replace(/( *, *)/g, ' ');
9770                         } else {
9771                                 stroke.dashStyle = '';
9772                         }
9773                         stroke.endcap = options.lineCap.replace('butt', 'flat');
9774                         stroke.joinstyle = options.lineJoin;
9775
9776                 } else if (stroke) {
9777                         container.removeChild(stroke);
9778                         layer._stroke = null;
9779                 }
9780
9781                 if (options.fill) {
9782                         if (!fill) {
9783                                 fill = layer._fill = L.SVG.create('fill');
9784                         }
9785                         container.appendChild(fill);
9786                         fill.color = options.fillColor || options.color;
9787                         fill.opacity = options.fillOpacity;
9788
9789                 } else if (fill) {
9790                         container.removeChild(fill);
9791                         layer._fill = null;
9792                 }
9793         },
9794
9795         _updateCircle: function (layer) {
9796                 var p = layer._point.round(),
9797                     r = Math.round(layer._radius),
9798                     r2 = Math.round(layer._radiusY || r);
9799
9800                 this._setPath(layer, layer._empty() ? 'M0 0' :
9801                                 'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
9802         },
9803
9804         _setPath: function (layer, path) {
9805                 layer._path.v = path;
9806         },
9807
9808         _bringToFront: function (layer) {
9809                 L.DomUtil.toFront(layer._container);
9810         },
9811
9812         _bringToBack: function (layer) {
9813                 L.DomUtil.toBack(layer._container);
9814         }
9815 });
9816
9817 if (L.Browser.vml) {
9818         L.SVG.create = (function () {
9819                 try {
9820                         document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
9821                         return function (name) {
9822                                 return document.createElement('<lvml:' + name + ' class="lvml">');
9823                         };
9824                 } catch (e) {
9825                         return function (name) {
9826                                 return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
9827                         };
9828                 }
9829         })();
9830 }
9831
9832
9833
9834 /*
9835  * @class Canvas
9836  * @inherits Renderer
9837  * @aka L.Canvas
9838  *
9839  * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
9840  * Inherits `Renderer`.
9841  *
9842  * Due to [technical limitations](http://caniuse.com/#search=canvas), Canvas is not
9843  * available in all web browsers, notably IE8, and overlapping geometries might
9844  * not display properly in some edge cases.
9845  *
9846  * @example
9847  *
9848  * Use Canvas by default for all paths in the map:
9849  *
9850  * ```js
9851  * var map = L.map('map', {
9852  *      renderer: L.canvas()
9853  * });
9854  * ```
9855  *
9856  * Use a Canvas renderer with extra padding for specific vector geometries:
9857  *
9858  * ```js
9859  * var map = L.map('map');
9860  * var myRenderer = L.canvas({ padding: 0.5 });
9861  * var line = L.polyline( coordinates, { renderer: myRenderer } );
9862  * var circle = L.circle( center, { renderer: myRenderer } );
9863  * ```
9864  */
9865
9866 L.Canvas = L.Renderer.extend({
9867         getEvents: function () {
9868                 var events = L.Renderer.prototype.getEvents.call(this);
9869                 events.viewprereset = this._onViewPreReset;
9870                 return events;
9871         },
9872
9873         _onViewPreReset: function () {
9874                 // Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
9875                 this._postponeUpdatePaths = true;
9876         },
9877
9878         onAdd: function () {
9879                 L.Renderer.prototype.onAdd.call(this);
9880
9881                 // Redraw vectors since canvas is cleared upon removal,
9882                 // in case of removing the renderer itself from the map.
9883                 this._draw();
9884         },
9885
9886         _initContainer: function () {
9887                 var container = this._container = document.createElement('canvas');
9888
9889                 L.DomEvent
9890                         .on(container, 'mousemove', L.Util.throttle(this._onMouseMove, 32, this), this)
9891                         .on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this)
9892                         .on(container, 'mouseout', this._handleMouseOut, this);
9893
9894                 this._ctx = container.getContext('2d');
9895         },
9896
9897         _updatePaths: function () {
9898                 if (this._postponeUpdatePaths) { return; }
9899
9900                 var layer;
9901                 this._redrawBounds = null;
9902                 for (var id in this._layers) {
9903                         layer = this._layers[id];
9904                         layer._update();
9905                 }
9906                 this._redraw();
9907         },
9908
9909         _update: function () {
9910                 if (this._map._animatingZoom && this._bounds) { return; }
9911
9912                 this._drawnLayers = {};
9913
9914                 L.Renderer.prototype._update.call(this);
9915
9916                 var b = this._bounds,
9917                     container = this._container,
9918                     size = b.getSize(),
9919                     m = L.Browser.retina ? 2 : 1;
9920
9921                 L.DomUtil.setPosition(container, b.min);
9922
9923                 // set canvas size (also clearing it); use double size on retina
9924                 container.width = m * size.x;
9925                 container.height = m * size.y;
9926                 container.style.width = size.x + 'px';
9927                 container.style.height = size.y + 'px';
9928
9929                 if (L.Browser.retina) {
9930                         this._ctx.scale(2, 2);
9931                 }
9932
9933                 // translate so we use the same path coordinates after canvas element moves
9934                 this._ctx.translate(-b.min.x, -b.min.y);
9935
9936                 // Tell paths to redraw themselves
9937                 this.fire('update');
9938         },
9939
9940         _reset: function () {
9941                 L.Renderer.prototype._reset.call(this);
9942
9943                 if (this._postponeUpdatePaths) {
9944                         this._postponeUpdatePaths = false;
9945                         this._updatePaths();
9946                 }
9947         },
9948
9949         _initPath: function (layer) {
9950                 this._updateDashArray(layer);
9951                 this._layers[L.stamp(layer)] = layer;
9952
9953                 var order = layer._order = {
9954                         layer: layer,
9955                         prev: this._drawLast,
9956                         next: null
9957                 };
9958                 if (this._drawLast) { this._drawLast.next = order; }
9959                 this._drawLast = order;
9960                 this._drawFirst = this._drawFirst || this._drawLast;
9961         },
9962
9963         _addPath: function (layer) {
9964                 this._requestRedraw(layer);
9965         },
9966
9967         _removePath: function (layer) {
9968                 var order = layer._order;
9969                 var next = order.next;
9970                 var prev = order.prev;
9971
9972                 if (next) {
9973                         next.prev = prev;
9974                 } else {
9975                         this._drawLast = prev;
9976                 }
9977                 if (prev) {
9978                         prev.next = next;
9979                 } else {
9980                         this._drawFirst = next;
9981                 }
9982
9983                 delete layer._order;
9984
9985                 delete this._layers[L.stamp(layer)];
9986
9987                 this._requestRedraw(layer);
9988         },
9989
9990         _updatePath: function (layer) {
9991                 // Redraw the union of the layer's old pixel
9992                 // bounds and the new pixel bounds.
9993                 this._extendRedrawBounds(layer);
9994                 layer._project();
9995                 layer._update();
9996                 // The redraw will extend the redraw bounds
9997                 // with the new pixel bounds.
9998                 this._requestRedraw(layer);
9999         },
10000
10001         _updateStyle: function (layer) {
10002                 this._updateDashArray(layer);
10003                 this._requestRedraw(layer);
10004         },
10005
10006         _updateDashArray: function (layer) {
10007                 if (layer.options.dashArray) {
10008                         var parts = layer.options.dashArray.split(','),
10009                             dashArray = [],
10010                             i;
10011                         for (i = 0; i < parts.length; i++) {
10012                                 dashArray.push(Number(parts[i]));
10013                         }
10014                         layer.options._dashArray = dashArray;
10015                 }
10016         },
10017
10018         _requestRedraw: function (layer) {
10019                 if (!this._map) { return; }
10020
10021                 this._extendRedrawBounds(layer);
10022                 this._redrawRequest = this._redrawRequest || L.Util.requestAnimFrame(this._redraw, this);
10023         },
10024
10025         _extendRedrawBounds: function (layer) {
10026                 var padding = (layer.options.weight || 0) + 1;
10027                 this._redrawBounds = this._redrawBounds || new L.Bounds();
10028                 this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
10029                 this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
10030         },
10031
10032         _redraw: function () {
10033                 this._redrawRequest = null;
10034
10035                 if (this._redrawBounds) {
10036                         this._redrawBounds.min._floor();
10037                         this._redrawBounds.max._ceil();
10038                 }
10039
10040                 this._clear(); // clear layers in redraw bounds
10041                 this._draw(); // draw layers
10042
10043                 this._redrawBounds = null;
10044         },
10045
10046         _clear: function () {
10047                 var bounds = this._redrawBounds;
10048                 if (bounds) {
10049                         var size = bounds.getSize();
10050                         this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
10051                 } else {
10052                         this._ctx.clearRect(0, 0, this._container.width, this._container.height);
10053                 }
10054         },
10055
10056         _draw: function () {
10057                 var layer, bounds = this._redrawBounds;
10058                 this._ctx.save();
10059                 if (bounds) {
10060                         var size = bounds.getSize();
10061                         this._ctx.beginPath();
10062                         this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
10063                         this._ctx.clip();
10064                 }
10065
10066                 this._drawing = true;
10067
10068                 for (var order = this._drawFirst; order; order = order.next) {
10069                         layer = order.layer;
10070                         if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
10071                                 layer._updatePath();
10072                         }
10073                 }
10074
10075                 this._drawing = false;
10076
10077                 this._ctx.restore();  // Restore state before clipping.
10078         },
10079
10080         _updatePoly: function (layer, closed) {
10081                 if (!this._drawing) { return; }
10082
10083                 var i, j, len2, p,
10084                     parts = layer._parts,
10085                     len = parts.length,
10086                     ctx = this._ctx;
10087
10088                 if (!len) { return; }
10089
10090                 this._drawnLayers[layer._leaflet_id] = layer;
10091
10092                 ctx.beginPath();
10093
10094                 if (ctx.setLineDash) {
10095                         ctx.setLineDash(layer.options && layer.options._dashArray || []);
10096                 }
10097
10098                 for (i = 0; i < len; i++) {
10099                         for (j = 0, len2 = parts[i].length; j < len2; j++) {
10100                                 p = parts[i][j];
10101                                 ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
10102                         }
10103                         if (closed) {
10104                                 ctx.closePath();
10105                         }
10106                 }
10107
10108                 this._fillStroke(ctx, layer);
10109
10110                 // TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
10111         },
10112
10113         _updateCircle: function (layer) {
10114
10115                 if (!this._drawing || layer._empty()) { return; }
10116
10117                 var p = layer._point,
10118                     ctx = this._ctx,
10119                     r = layer._radius,
10120                     s = (layer._radiusY || r) / r;
10121
10122                 this._drawnLayers[layer._leaflet_id] = layer;
10123
10124                 if (s !== 1) {
10125                         ctx.save();
10126                         ctx.scale(1, s);
10127                 }
10128
10129                 ctx.beginPath();
10130                 ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);
10131
10132                 if (s !== 1) {
10133                         ctx.restore();
10134                 }
10135
10136                 this._fillStroke(ctx, layer);
10137         },
10138
10139         _fillStroke: function (ctx, layer) {
10140                 var options = layer.options;
10141
10142                 if (options.fill) {
10143                         ctx.globalAlpha = options.fillOpacity;
10144                         ctx.fillStyle = options.fillColor || options.color;
10145                         ctx.fill(options.fillRule || 'evenodd');
10146                 }
10147
10148                 if (options.stroke && options.weight !== 0) {
10149                         ctx.globalAlpha = options.opacity;
10150                         ctx.lineWidth = options.weight;
10151                         ctx.strokeStyle = options.color;
10152                         ctx.lineCap = options.lineCap;
10153                         ctx.lineJoin = options.lineJoin;
10154                         ctx.stroke();
10155                 }
10156         },
10157
10158         // Canvas obviously doesn't have mouse events for individual drawn objects,
10159         // so we emulate that by calculating what's under the mouse on mousemove/click manually
10160
10161         _onClick: function (e) {
10162                 var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;
10163
10164                 for (var order = this._drawFirst; order; order = order.next) {
10165                         layer = order.layer;
10166                         if (layer.options.interactive && layer._containsPoint(point) && !this._map._draggableMoved(layer)) {
10167                                 clickedLayer = layer;
10168                         }
10169                 }
10170                 if (clickedLayer)  {
10171                         L.DomEvent._fakeStop(e);
10172                         this._fireEvent([clickedLayer], e);
10173                 }
10174         },
10175
10176         _onMouseMove: function (e) {
10177                 if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }
10178
10179                 var point = this._map.mouseEventToLayerPoint(e);
10180                 this._handleMouseHover(e, point);
10181         },
10182
10183
10184         _handleMouseOut: function (e) {
10185                 var layer = this._hoveredLayer;
10186                 if (layer) {
10187                         // if we're leaving the layer, fire mouseout
10188                         L.DomUtil.removeClass(this._container, 'leaflet-interactive');
10189                         this._fireEvent([layer], e, 'mouseout');
10190                         this._hoveredLayer = null;
10191                 }
10192         },
10193
10194         _handleMouseHover: function (e, point) {
10195                 var layer, candidateHoveredLayer;
10196
10197                 for (var order = this._drawFirst; order; order = order.next) {
10198                         layer = order.layer;
10199                         if (layer.options.interactive && layer._containsPoint(point)) {
10200                                 candidateHoveredLayer = layer;
10201                         }
10202                 }
10203
10204                 if (candidateHoveredLayer !== this._hoveredLayer) {
10205                         this._handleMouseOut(e);
10206
10207                         if (candidateHoveredLayer) {
10208                                 L.DomUtil.addClass(this._container, 'leaflet-interactive'); // change cursor
10209                                 this._fireEvent([candidateHoveredLayer], e, 'mouseover');
10210                                 this._hoveredLayer = candidateHoveredLayer;
10211                         }
10212                 }
10213
10214                 if (this._hoveredLayer) {
10215                         this._fireEvent([this._hoveredLayer], e);
10216                 }
10217         },
10218
10219         _fireEvent: function (layers, e, type) {
10220                 this._map._fireDOMEvent(e, type || e.type, layers);
10221         },
10222
10223         _bringToFront: function (layer) {
10224                 var order = layer._order;
10225                 var next = order.next;
10226                 var prev = order.prev;
10227
10228                 if (next) {
10229                         next.prev = prev;
10230                 } else {
10231                         // Already last
10232                         return;
10233                 }
10234                 if (prev) {
10235                         prev.next = next;
10236                 } else if (next) {
10237                         // Update first entry unless this is the
10238                         // signle entry
10239                         this._drawFirst = next;
10240                 }
10241
10242                 order.prev = this._drawLast;
10243                 this._drawLast.next = order;
10244
10245                 order.next = null;
10246                 this._drawLast = order;
10247
10248                 this._requestRedraw(layer);
10249         },
10250
10251         _bringToBack: function (layer) {
10252                 var order = layer._order;
10253                 var next = order.next;
10254                 var prev = order.prev;
10255
10256                 if (prev) {
10257                         prev.next = next;
10258                 } else {
10259                         // Already first
10260                         return;
10261                 }
10262                 if (next) {
10263                         next.prev = prev;
10264                 } else if (prev) {
10265                         // Update last entry unless this is the
10266                         // signle entry
10267                         this._drawLast = prev;
10268                 }
10269
10270                 order.prev = null;
10271
10272                 order.next = this._drawFirst;
10273                 this._drawFirst.prev = order;
10274                 this._drawFirst = order;
10275
10276                 this._requestRedraw(layer);
10277         }
10278 });
10279
10280 // @namespace Browser; @property canvas: Boolean
10281 // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
10282 L.Browser.canvas = (function () {
10283         return !!document.createElement('canvas').getContext;
10284 }());
10285
10286 // @namespace Canvas
10287 // @factory L.canvas(options?: Renderer options)
10288 // Creates a Canvas renderer with the given options.
10289 L.canvas = function (options) {
10290         return L.Browser.canvas ? new L.Canvas(options) : null;
10291 };
10292
10293 L.Polyline.prototype._containsPoint = function (p, closed) {
10294         var i, j, k, len, len2, part,
10295             w = this._clickTolerance();
10296
10297         if (!this._pxBounds.contains(p)) { return false; }
10298
10299         // hit detection for polylines
10300         for (i = 0, len = this._parts.length; i < len; i++) {
10301                 part = this._parts[i];
10302
10303                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10304                         if (!closed && (j === 0)) { continue; }
10305
10306                         if (L.LineUtil.pointToSegmentDistance(p, part[k], part[j]) <= w) {
10307                                 return true;
10308                         }
10309                 }
10310         }
10311         return false;
10312 };
10313
10314 L.Polygon.prototype._containsPoint = function (p) {
10315         var inside = false,
10316             part, p1, p2, i, j, k, len, len2;
10317
10318         if (!this._pxBounds.contains(p)) { return false; }
10319
10320         // ray casting algorithm for detecting if point is in polygon
10321         for (i = 0, len = this._parts.length; i < len; i++) {
10322                 part = this._parts[i];
10323
10324                 for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
10325                         p1 = part[j];
10326                         p2 = part[k];
10327
10328                         if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
10329                                 inside = !inside;
10330                         }
10331                 }
10332         }
10333
10334         // also check if it's on polygon stroke
10335         return inside || L.Polyline.prototype._containsPoint.call(this, p, true);
10336 };
10337
10338 L.CircleMarker.prototype._containsPoint = function (p) {
10339         return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
10340 };
10341
10342
10343
10344 /*
10345  * @class GeoJSON
10346  * @aka L.GeoJSON
10347  * @inherits FeatureGroup
10348  *
10349  * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
10350  * GeoJSON data and display it on the map. Extends `FeatureGroup`.
10351  *
10352  * @example
10353  *
10354  * ```js
10355  * L.geoJSON(data, {
10356  *      style: function (feature) {
10357  *              return {color: feature.properties.color};
10358  *      }
10359  * }).bindPopup(function (layer) {
10360  *      return layer.feature.properties.description;
10361  * }).addTo(map);
10362  * ```
10363  */
10364
10365 L.GeoJSON = L.FeatureGroup.extend({
10366
10367         /* @section
10368          * @aka GeoJSON options
10369          *
10370          * @option pointToLayer: Function = *
10371          * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
10372          * called when data is added, passing the GeoJSON point feature and its `LatLng`.
10373          * The default is to spawn a default `Marker`:
10374          * ```js
10375          * function(geoJsonPoint, latlng) {
10376          *      return L.marker(latlng);
10377          * }
10378          * ```
10379          *
10380          * @option style: Function = *
10381          * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
10382          * called internally when data is added.
10383          * The default value is to not override any defaults:
10384          * ```js
10385          * function (geoJsonFeature) {
10386          *      return {}
10387          * }
10388          * ```
10389          *
10390          * @option onEachFeature: Function = *
10391          * A `Function` that will be called once for each created `Feature`, after it has
10392          * been created and styled. Useful for attaching events and popups to features.
10393          * The default is to do nothing with the newly created layers:
10394          * ```js
10395          * function (feature, layer) {}
10396          * ```
10397          *
10398          * @option filter: Function = *
10399          * A `Function` that will be used to decide whether to include a feature or not.
10400          * The default is to include all features:
10401          * ```js
10402          * function (geoJsonFeature) {
10403          *      return true;
10404          * }
10405          * ```
10406          * Note: dynamically changing the `filter` option will have effect only on newly
10407          * added data. It will _not_ re-evaluate already included features.
10408          *
10409          * @option coordsToLatLng: Function = *
10410          * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
10411          * The default is the `coordsToLatLng` static method.
10412          */
10413
10414         initialize: function (geojson, options) {
10415                 L.setOptions(this, options);
10416
10417                 this._layers = {};
10418
10419                 if (geojson) {
10420                         this.addData(geojson);
10421                 }
10422         },
10423
10424         // @method addData( <GeoJSON> data ): this
10425         // Adds a GeoJSON object to the layer.
10426         addData: function (geojson) {
10427                 var features = L.Util.isArray(geojson) ? geojson : geojson.features,
10428                     i, len, feature;
10429
10430                 if (features) {
10431                         for (i = 0, len = features.length; i < len; i++) {
10432                                 // only add this if geometry or geometries are set and not null
10433                                 feature = features[i];
10434                                 if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
10435                                         this.addData(feature);
10436                                 }
10437                         }
10438                         return this;
10439                 }
10440
10441                 var options = this.options;
10442
10443                 if (options.filter && !options.filter(geojson)) { return this; }
10444
10445                 var layer = L.GeoJSON.geometryToLayer(geojson, options);
10446                 if (!layer) {
10447                         return this;
10448                 }
10449                 layer.feature = L.GeoJSON.asFeature(geojson);
10450
10451                 layer.defaultOptions = layer.options;
10452                 this.resetStyle(layer);
10453
10454                 if (options.onEachFeature) {
10455                         options.onEachFeature(geojson, layer);
10456                 }
10457
10458                 return this.addLayer(layer);
10459         },
10460
10461         // @method resetStyle( <Path> layer ): this
10462         // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
10463         resetStyle: function (layer) {
10464                 // reset any custom styles
10465                 layer.options = L.Util.extend({}, layer.defaultOptions);
10466                 this._setLayerStyle(layer, this.options.style);
10467                 return this;
10468         },
10469
10470         // @method setStyle( <Function> style ): this
10471         // Changes styles of GeoJSON vector layers with the given style function.
10472         setStyle: function (style) {
10473                 return this.eachLayer(function (layer) {
10474                         this._setLayerStyle(layer, style);
10475                 }, this);
10476         },
10477
10478         _setLayerStyle: function (layer, style) {
10479                 if (typeof style === 'function') {
10480                         style = style(layer.feature);
10481                 }
10482                 if (layer.setStyle) {
10483                         layer.setStyle(style);
10484                 }
10485         }
10486 });
10487
10488 // @section
10489 // There are several static functions which can be called without instantiating L.GeoJSON:
10490 L.extend(L.GeoJSON, {
10491         // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
10492         // Creates a `Layer` from a given GeoJSON feature. Can use a custom
10493         // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
10494         // functions if provided as options.
10495         geometryToLayer: function (geojson, options) {
10496
10497                 var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
10498                     coords = geometry ? geometry.coordinates : null,
10499                     layers = [],
10500                     pointToLayer = options && options.pointToLayer,
10501                     coordsToLatLng = options && options.coordsToLatLng || this.coordsToLatLng,
10502                     latlng, latlngs, i, len;
10503
10504                 if (!coords && !geometry) {
10505                         return null;
10506                 }
10507
10508                 switch (geometry.type) {
10509                 case 'Point':
10510                         latlng = coordsToLatLng(coords);
10511                         return pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng);
10512
10513                 case 'MultiPoint':
10514                         for (i = 0, len = coords.length; i < len; i++) {
10515                                 latlng = coordsToLatLng(coords[i]);
10516                                 layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng));
10517                         }
10518                         return new L.FeatureGroup(layers);
10519
10520                 case 'LineString':
10521                 case 'MultiLineString':
10522                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, coordsToLatLng);
10523                         return new L.Polyline(latlngs, options);
10524
10525                 case 'Polygon':
10526                 case 'MultiPolygon':
10527                         latlngs = this.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, coordsToLatLng);
10528                         return new L.Polygon(latlngs, options);
10529
10530                 case 'GeometryCollection':
10531                         for (i = 0, len = geometry.geometries.length; i < len; i++) {
10532                                 var layer = this.geometryToLayer({
10533                                         geometry: geometry.geometries[i],
10534                                         type: 'Feature',
10535                                         properties: geojson.properties
10536                                 }, options);
10537
10538                                 if (layer) {
10539                                         layers.push(layer);
10540                                 }
10541                         }
10542                         return new L.FeatureGroup(layers);
10543
10544                 default:
10545                         throw new Error('Invalid GeoJSON object.');
10546                 }
10547         },
10548
10549         // @function coordsToLatLng(coords: Array): LatLng
10550         // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
10551         // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
10552         coordsToLatLng: function (coords) {
10553                 return new L.LatLng(coords[1], coords[0], coords[2]);
10554         },
10555
10556         // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
10557         // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
10558         // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
10559         // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
10560         coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) {
10561                 var latlngs = [];
10562
10563                 for (var i = 0, len = coords.length, latlng; i < len; i++) {
10564                         latlng = levelsDeep ?
10565                                 this.coordsToLatLngs(coords[i], levelsDeep - 1, coordsToLatLng) :
10566                                 (coordsToLatLng || this.coordsToLatLng)(coords[i]);
10567
10568                         latlngs.push(latlng);
10569                 }
10570
10571                 return latlngs;
10572         },
10573
10574         // @function latLngToCoords(latlng: LatLng): Array
10575         // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
10576         latLngToCoords: function (latlng) {
10577                 return latlng.alt !== undefined ?
10578                                 [latlng.lng, latlng.lat, latlng.alt] :
10579                                 [latlng.lng, latlng.lat];
10580         },
10581
10582         // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean): Array
10583         // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
10584         // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
10585         latLngsToCoords: function (latlngs, levelsDeep, closed) {
10586                 var coords = [];
10587
10588                 for (var i = 0, len = latlngs.length; i < len; i++) {
10589                         coords.push(levelsDeep ?
10590                                 L.GeoJSON.latLngsToCoords(latlngs[i], levelsDeep - 1, closed) :
10591                                 L.GeoJSON.latLngToCoords(latlngs[i]));
10592                 }
10593
10594                 if (!levelsDeep && closed) {
10595                         coords.push(coords[0]);
10596                 }
10597
10598                 return coords;
10599         },
10600
10601         getFeature: function (layer, newGeometry) {
10602                 return layer.feature ?
10603                                 L.extend({}, layer.feature, {geometry: newGeometry}) :
10604                                 L.GeoJSON.asFeature(newGeometry);
10605         },
10606
10607         // @function asFeature(geojson: Object): Object
10608         // Normalize GeoJSON geometries/features into GeoJSON features.
10609         asFeature: function (geojson) {
10610                 if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
10611                         return geojson;
10612                 }
10613
10614                 return {
10615                         type: 'Feature',
10616                         properties: {},
10617                         geometry: geojson
10618                 };
10619         }
10620 });
10621
10622 var PointToGeoJSON = {
10623         toGeoJSON: function () {
10624                 return L.GeoJSON.getFeature(this, {
10625                         type: 'Point',
10626                         coordinates: L.GeoJSON.latLngToCoords(this.getLatLng())
10627                 });
10628         }
10629 };
10630
10631 // @namespace Marker
10632 // @method toGeoJSON(): Object
10633 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
10634 L.Marker.include(PointToGeoJSON);
10635
10636 // @namespace CircleMarker
10637 // @method toGeoJSON(): Object
10638 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
10639 L.Circle.include(PointToGeoJSON);
10640 L.CircleMarker.include(PointToGeoJSON);
10641
10642
10643 // @namespace Polyline
10644 // @method toGeoJSON(): Object
10645 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
10646 L.Polyline.prototype.toGeoJSON = function () {
10647         var multi = !L.Polyline._flat(this._latlngs);
10648
10649         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 1 : 0);
10650
10651         return L.GeoJSON.getFeature(this, {
10652                 type: (multi ? 'Multi' : '') + 'LineString',
10653                 coordinates: coords
10654         });
10655 };
10656
10657 // @namespace Polygon
10658 // @method toGeoJSON(): Object
10659 // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
10660 L.Polygon.prototype.toGeoJSON = function () {
10661         var holes = !L.Polyline._flat(this._latlngs),
10662             multi = holes && !L.Polyline._flat(this._latlngs[0]);
10663
10664         var coords = L.GeoJSON.latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true);
10665
10666         if (!holes) {
10667                 coords = [coords];
10668         }
10669
10670         return L.GeoJSON.getFeature(this, {
10671                 type: (multi ? 'Multi' : '') + 'Polygon',
10672                 coordinates: coords
10673         });
10674 };
10675
10676
10677 // @namespace LayerGroup
10678 L.LayerGroup.include({
10679         toMultiPoint: function () {
10680                 var coords = [];
10681
10682                 this.eachLayer(function (layer) {
10683                         coords.push(layer.toGeoJSON().geometry.coordinates);
10684                 });
10685
10686                 return L.GeoJSON.getFeature(this, {
10687                         type: 'MultiPoint',
10688                         coordinates: coords
10689                 });
10690         },
10691
10692         // @method toGeoJSON(): Object
10693         // Returns a [`GeoJSON`](http://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `GeometryCollection`).
10694         toGeoJSON: function () {
10695
10696                 var type = this.feature && this.feature.geometry && this.feature.geometry.type;
10697
10698                 if (type === 'MultiPoint') {
10699                         return this.toMultiPoint();
10700                 }
10701
10702                 var isGeometryCollection = type === 'GeometryCollection',
10703                     jsons = [];
10704
10705                 this.eachLayer(function (layer) {
10706                         if (layer.toGeoJSON) {
10707                                 var json = layer.toGeoJSON();
10708                                 jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json));
10709                         }
10710                 });
10711
10712                 if (isGeometryCollection) {
10713                         return L.GeoJSON.getFeature(this, {
10714                                 geometries: jsons,
10715                                 type: 'GeometryCollection'
10716                         });
10717                 }
10718
10719                 return {
10720                         type: 'FeatureCollection',
10721                         features: jsons
10722                 };
10723         }
10724 });
10725
10726 // @namespace GeoJSON
10727 // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
10728 // Creates a GeoJSON layer. Optionally accepts an object in
10729 // [GeoJSON format](http://geojson.org/geojson-spec.html) to display on the map
10730 // (you can alternatively add it later with `addData` method) and an `options` object.
10731 L.geoJSON = function (geojson, options) {
10732         return new L.GeoJSON(geojson, options);
10733 };
10734 // Backward compatibility.
10735 L.geoJson = L.geoJSON;
10736
10737
10738
10739 /*
10740  * @class Draggable
10741  * @aka L.Draggable
10742  * @inherits Evented
10743  *
10744  * A class for making DOM elements draggable (including touch support).
10745  * Used internally for map and marker dragging. Only works for elements
10746  * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
10747  *
10748  * @example
10749  * ```js
10750  * var draggable = new L.Draggable(elementToDrag);
10751  * draggable.enable();
10752  * ```
10753  */
10754
10755 L.Draggable = L.Evented.extend({
10756
10757         options: {
10758                 // @option clickTolerance: Number = 3
10759                 // The max number of pixels a user can shift the mouse pointer during a click
10760                 // for it to be considered a valid click (as opposed to a mouse drag).
10761                 clickTolerance: 3
10762         },
10763
10764         statics: {
10765                 START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'],
10766                 END: {
10767                         mousedown: 'mouseup',
10768                         touchstart: 'touchend',
10769                         pointerdown: 'touchend',
10770                         MSPointerDown: 'touchend'
10771                 },
10772                 MOVE: {
10773                         mousedown: 'mousemove',
10774                         touchstart: 'touchmove',
10775                         pointerdown: 'touchmove',
10776                         MSPointerDown: 'touchmove'
10777                 }
10778         },
10779
10780         // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline: Boolean)
10781         // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
10782         initialize: function (element, dragStartTarget, preventOutline) {
10783                 this._element = element;
10784                 this._dragStartTarget = dragStartTarget || element;
10785                 this._preventOutline = preventOutline;
10786         },
10787
10788         // @method enable()
10789         // Enables the dragging ability
10790         enable: function () {
10791                 if (this._enabled) { return; }
10792
10793                 L.DomEvent.on(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10794
10795                 this._enabled = true;
10796         },
10797
10798         // @method disable()
10799         // Disables the dragging ability
10800         disable: function () {
10801                 if (!this._enabled) { return; }
10802
10803                 // If we're currently dragging this draggable,
10804                 // disabling it counts as first ending the drag.
10805                 if (L.Draggable._dragging === this) {
10806                         this.finishDrag();
10807                 }
10808
10809                 L.DomEvent.off(this._dragStartTarget, L.Draggable.START.join(' '), this._onDown, this);
10810
10811                 this._enabled = false;
10812                 this._moved = false;
10813         },
10814
10815         _onDown: function (e) {
10816                 // Ignore simulated events, since we handle both touch and
10817                 // mouse explicitly; otherwise we risk getting duplicates of
10818                 // touch events, see #4315.
10819                 // Also ignore the event if disabled; this happens in IE11
10820                 // under some circumstances, see #3666.
10821                 if (e._simulated || !this._enabled) { return; }
10822
10823                 this._moved = false;
10824
10825                 if (L.DomUtil.hasClass(this._element, 'leaflet-zoom-anim')) { return; }
10826
10827                 if (L.Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
10828                 L.Draggable._dragging = this;  // Prevent dragging multiple objects at once.
10829
10830                 if (this._preventOutline) {
10831                         L.DomUtil.preventOutline(this._element);
10832                 }
10833
10834                 L.DomUtil.disableImageDrag();
10835                 L.DomUtil.disableTextSelection();
10836
10837                 if (this._moving) { return; }
10838
10839                 // @event down: Event
10840                 // Fired when a drag is about to start.
10841                 this.fire('down');
10842
10843                 var first = e.touches ? e.touches[0] : e;
10844
10845                 this._startPoint = new L.Point(first.clientX, first.clientY);
10846
10847                 L.DomEvent
10848                         .on(document, L.Draggable.MOVE[e.type], this._onMove, this)
10849                         .on(document, L.Draggable.END[e.type], this._onUp, this);
10850         },
10851
10852         _onMove: function (e) {
10853                 // Ignore simulated events, since we handle both touch and
10854                 // mouse explicitly; otherwise we risk getting duplicates of
10855                 // touch events, see #4315.
10856                 // Also ignore the event if disabled; this happens in IE11
10857                 // under some circumstances, see #3666.
10858                 if (e._simulated || !this._enabled) { return; }
10859
10860                 if (e.touches && e.touches.length > 1) {
10861                         this._moved = true;
10862                         return;
10863                 }
10864
10865                 var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
10866                     newPoint = new L.Point(first.clientX, first.clientY),
10867                     offset = newPoint.subtract(this._startPoint);
10868
10869                 if (!offset.x && !offset.y) { return; }
10870                 if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }
10871
10872                 L.DomEvent.preventDefault(e);
10873
10874                 if (!this._moved) {
10875                         // @event dragstart: Event
10876                         // Fired when a drag starts
10877                         this.fire('dragstart');
10878
10879                         this._moved = true;
10880                         this._startPos = L.DomUtil.getPosition(this._element).subtract(offset);
10881
10882                         L.DomUtil.addClass(document.body, 'leaflet-dragging');
10883
10884                         this._lastTarget = e.target || e.srcElement;
10885                         // IE and Edge do not give the <use> element, so fetch it
10886                         // if necessary
10887                         if ((window.SVGElementInstance) && (this._lastTarget instanceof SVGElementInstance)) {
10888                                 this._lastTarget = this._lastTarget.correspondingUseElement;
10889                         }
10890                         L.DomUtil.addClass(this._lastTarget, 'leaflet-drag-target');
10891                 }
10892
10893                 this._newPos = this._startPos.add(offset);
10894                 this._moving = true;
10895
10896                 L.Util.cancelAnimFrame(this._animRequest);
10897                 this._lastEvent = e;
10898                 this._animRequest = L.Util.requestAnimFrame(this._updatePosition, this, true);
10899         },
10900
10901         _updatePosition: function () {
10902                 var e = {originalEvent: this._lastEvent};
10903
10904                 // @event predrag: Event
10905                 // Fired continuously during dragging *before* each corresponding
10906                 // update of the element's position.
10907                 this.fire('predrag', e);
10908                 L.DomUtil.setPosition(this._element, this._newPos);
10909
10910                 // @event drag: Event
10911                 // Fired continuously during dragging.
10912                 this.fire('drag', e);
10913         },
10914
10915         _onUp: function (e) {
10916                 // Ignore simulated events, since we handle both touch and
10917                 // mouse explicitly; otherwise we risk getting duplicates of
10918                 // touch events, see #4315.
10919                 // Also ignore the event if disabled; this happens in IE11
10920                 // under some circumstances, see #3666.
10921                 if (e._simulated || !this._enabled) { return; }
10922                 this.finishDrag();
10923         },
10924
10925         finishDrag: function () {
10926                 L.DomUtil.removeClass(document.body, 'leaflet-dragging');
10927
10928                 if (this._lastTarget) {
10929                         L.DomUtil.removeClass(this._lastTarget, 'leaflet-drag-target');
10930                         this._lastTarget = null;
10931                 }
10932
10933                 for (var i in L.Draggable.MOVE) {
10934                         L.DomEvent
10935                                 .off(document, L.Draggable.MOVE[i], this._onMove, this)
10936                                 .off(document, L.Draggable.END[i], this._onUp, this);
10937                 }
10938
10939                 L.DomUtil.enableImageDrag();
10940                 L.DomUtil.enableTextSelection();
10941
10942                 if (this._moved && this._moving) {
10943                         // ensure drag is not fired after dragend
10944                         L.Util.cancelAnimFrame(this._animRequest);
10945
10946                         // @event dragend: DragEndEvent
10947                         // Fired when the drag ends.
10948                         this.fire('dragend', {
10949                                 distance: this._newPos.distanceTo(this._startPos)
10950                         });
10951                 }
10952
10953                 this._moving = false;
10954                 L.Draggable._dragging = false;
10955         }
10956
10957 });
10958
10959
10960
10961 /*
10962         L.Handler is a base class for handler classes that are used internally to inject
10963         interaction features like dragging to classes like Map and Marker.
10964 */
10965
10966 // @class Handler
10967 // @aka L.Handler
10968 // Abstract class for map interaction handlers
10969
10970 L.Handler = L.Class.extend({
10971         initialize: function (map) {
10972                 this._map = map;
10973         },
10974
10975         // @method enable(): this
10976         // Enables the handler
10977         enable: function () {
10978                 if (this._enabled) { return this; }
10979
10980                 this._enabled = true;
10981                 this.addHooks();
10982                 return this;
10983         },
10984
10985         // @method disable(): this
10986         // Disables the handler
10987         disable: function () {
10988                 if (!this._enabled) { return this; }
10989
10990                 this._enabled = false;
10991                 this.removeHooks();
10992                 return this;
10993         },
10994
10995         // @method enabled(): Boolean
10996         // Returns `true` if the handler is enabled
10997         enabled: function () {
10998                 return !!this._enabled;
10999         }
11000
11001         // @section Extension methods
11002         // Classes inheriting from `Handler` must implement the two following methods:
11003         // @method addHooks()
11004         // Called when the handler is enabled, should add event hooks.
11005         // @method removeHooks()
11006         // Called when the handler is disabled, should remove the event hooks added previously.
11007 });
11008
11009
11010
11011 /*
11012  * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
11013  */
11014
11015 // @namespace Map
11016 // @section Interaction Options
11017 L.Map.mergeOptions({
11018         // @option dragging: Boolean = true
11019         // Whether the map be draggable with mouse/touch or not.
11020         dragging: true,
11021
11022         // @section Panning Inertia Options
11023         // @option inertia: Boolean = *
11024         // If enabled, panning of the map will have an inertia effect where
11025         // the map builds momentum while dragging and continues moving in
11026         // the same direction for some time. Feels especially nice on touch
11027         // devices. Enabled by default unless running on old Android devices.
11028         inertia: !L.Browser.android23,
11029
11030         // @option inertiaDeceleration: Number = 3000
11031         // The rate with which the inertial movement slows down, in pixels/second².
11032         inertiaDeceleration: 3400, // px/s^2
11033
11034         // @option inertiaMaxSpeed: Number = Infinity
11035         // Max speed of the inertial movement, in pixels/second.
11036         inertiaMaxSpeed: Infinity, // px/s
11037
11038         // @option easeLinearity: Number = 0.2
11039         easeLinearity: 0.2,
11040
11041         // TODO refactor, move to CRS
11042         // @option worldCopyJump: Boolean = false
11043         // With this option enabled, the map tracks when you pan to another "copy"
11044         // of the world and seamlessly jumps to the original one so that all overlays
11045         // like markers and vector layers are still visible.
11046         worldCopyJump: false,
11047
11048         // @option maxBoundsViscosity: Number = 0.0
11049         // If `maxBounds` is set, this option will control how solid the bounds
11050         // are when dragging the map around. The default value of `0.0` allows the
11051         // user to drag outside the bounds at normal speed, higher values will
11052         // slow down map dragging outside bounds, and `1.0` makes the bounds fully
11053         // solid, preventing the user from dragging outside the bounds.
11054         maxBoundsViscosity: 0.0
11055 });
11056
11057 L.Map.Drag = L.Handler.extend({
11058         addHooks: function () {
11059                 if (!this._draggable) {
11060                         var map = this._map;
11061
11062                         this._draggable = new L.Draggable(map._mapPane, map._container);
11063
11064                         this._draggable.on({
11065                                 down: this._onDown,
11066                                 dragstart: this._onDragStart,
11067                                 drag: this._onDrag,
11068                                 dragend: this._onDragEnd
11069                         }, this);
11070
11071                         this._draggable.on('predrag', this._onPreDragLimit, this);
11072                         if (map.options.worldCopyJump) {
11073                                 this._draggable.on('predrag', this._onPreDragWrap, this);
11074                                 map.on('zoomend', this._onZoomEnd, this);
11075
11076                                 map.whenReady(this._onZoomEnd, this);
11077                         }
11078                 }
11079                 L.DomUtil.addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
11080                 this._draggable.enable();
11081                 this._positions = [];
11082                 this._times = [];
11083         },
11084
11085         removeHooks: function () {
11086                 L.DomUtil.removeClass(this._map._container, 'leaflet-grab');
11087                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-drag');
11088                 this._draggable.disable();
11089         },
11090
11091         moved: function () {
11092                 return this._draggable && this._draggable._moved;
11093         },
11094
11095         moving: function () {
11096                 return this._draggable && this._draggable._moving;
11097         },
11098
11099         _onDown: function () {
11100                 this._map._stop();
11101         },
11102
11103         _onDragStart: function () {
11104                 var map = this._map;
11105
11106                 if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
11107                         var bounds = L.latLngBounds(this._map.options.maxBounds);
11108
11109                         this._offsetLimit = L.bounds(
11110                                 this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
11111                                 this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
11112                                         .add(this._map.getSize()));
11113
11114                         this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
11115                 } else {
11116                         this._offsetLimit = null;
11117                 }
11118
11119                 map
11120                     .fire('movestart')
11121                     .fire('dragstart');
11122
11123                 if (map.options.inertia) {
11124                         this._positions = [];
11125                         this._times = [];
11126                 }
11127         },
11128
11129         _onDrag: function (e) {
11130                 if (this._map.options.inertia) {
11131                         var time = this._lastTime = +new Date(),
11132                             pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;
11133
11134                         this._positions.push(pos);
11135                         this._times.push(time);
11136
11137                         if (time - this._times[0] > 50) {
11138                                 this._positions.shift();
11139                                 this._times.shift();
11140                         }
11141                 }
11142
11143                 this._map
11144                     .fire('move', e)
11145                     .fire('drag', e);
11146         },
11147
11148         _onZoomEnd: function () {
11149                 var pxCenter = this._map.getSize().divideBy(2),
11150                     pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);
11151
11152                 this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
11153                 this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
11154         },
11155
11156         _viscousLimit: function (value, threshold) {
11157                 return value - (value - threshold) * this._viscosity;
11158         },
11159
11160         _onPreDragLimit: function () {
11161                 if (!this._viscosity || !this._offsetLimit) { return; }
11162
11163                 var offset = this._draggable._newPos.subtract(this._draggable._startPos);
11164
11165                 var limit = this._offsetLimit;
11166                 if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
11167                 if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
11168                 if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
11169                 if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }
11170
11171                 this._draggable._newPos = this._draggable._startPos.add(offset);
11172         },
11173
11174         _onPreDragWrap: function () {
11175                 // TODO refactor to be able to adjust map pane position after zoom
11176                 var worldWidth = this._worldWidth,
11177                     halfWidth = Math.round(worldWidth / 2),
11178                     dx = this._initialWorldOffset,
11179                     x = this._draggable._newPos.x,
11180                     newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
11181                     newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
11182                     newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
11183
11184                 this._draggable._absPos = this._draggable._newPos.clone();
11185                 this._draggable._newPos.x = newX;
11186         },
11187
11188         _onDragEnd: function (e) {
11189                 var map = this._map,
11190                     options = map.options,
11191
11192                     noInertia = !options.inertia || this._times.length < 2;
11193
11194                 map.fire('dragend', e);
11195
11196                 if (noInertia) {
11197                         map.fire('moveend');
11198
11199                 } else {
11200
11201                         var direction = this._lastPos.subtract(this._positions[0]),
11202                             duration = (this._lastTime - this._times[0]) / 1000,
11203                             ease = options.easeLinearity,
11204
11205                             speedVector = direction.multiplyBy(ease / duration),
11206                             speed = speedVector.distanceTo([0, 0]),
11207
11208                             limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
11209                             limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),
11210
11211                             decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
11212                             offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();
11213
11214                         if (!offset.x && !offset.y) {
11215                                 map.fire('moveend');
11216
11217                         } else {
11218                                 offset = map._limitOffset(offset, map.options.maxBounds);
11219
11220                                 L.Util.requestAnimFrame(function () {
11221                                         map.panBy(offset, {
11222                                                 duration: decelerationDuration,
11223                                                 easeLinearity: ease,
11224                                                 noMoveStart: true,
11225                                                 animate: true
11226                                         });
11227                                 });
11228                         }
11229                 }
11230         }
11231 });
11232
11233 // @section Handlers
11234 // @property dragging: Handler
11235 // Map dragging handler (by both mouse and touch).
11236 L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag);
11237
11238
11239
11240 /*
11241  * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
11242  */
11243
11244 // @namespace Map
11245 // @section Interaction Options
11246
11247 L.Map.mergeOptions({
11248         // @option doubleClickZoom: Boolean|String = true
11249         // Whether the map can be zoomed in by double clicking on it and
11250         // zoomed out by double clicking while holding shift. If passed
11251         // `'center'`, double-click zoom will zoom to the center of the
11252         //  view regardless of where the mouse was.
11253         doubleClickZoom: true
11254 });
11255
11256 L.Map.DoubleClickZoom = L.Handler.extend({
11257         addHooks: function () {
11258                 this._map.on('dblclick', this._onDoubleClick, this);
11259         },
11260
11261         removeHooks: function () {
11262                 this._map.off('dblclick', this._onDoubleClick, this);
11263         },
11264
11265         _onDoubleClick: function (e) {
11266                 var map = this._map,
11267                     oldZoom = map.getZoom(),
11268                     delta = map.options.zoomDelta,
11269                     zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;
11270
11271                 if (map.options.doubleClickZoom === 'center') {
11272                         map.setZoom(zoom);
11273                 } else {
11274                         map.setZoomAround(e.containerPoint, zoom);
11275                 }
11276         }
11277 });
11278
11279 // @section Handlers
11280 //
11281 // Map properties include interaction handlers that allow you to control
11282 // interaction behavior in runtime, enabling or disabling certain features such
11283 // as dragging or touch zoom (see `Handler` methods). For example:
11284 //
11285 // ```js
11286 // map.doubleClickZoom.disable();
11287 // ```
11288 //
11289 // @property doubleClickZoom: Handler
11290 // Double click zoom handler.
11291 L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom);
11292
11293
11294
11295 /*
11296  * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
11297  */
11298
11299 // @namespace Map
11300 // @section Interaction Options
11301 L.Map.mergeOptions({
11302         // @section Mousewheel options
11303         // @option scrollWheelZoom: Boolean|String = true
11304         // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
11305         // it will zoom to the center of the view regardless of where the mouse was.
11306         scrollWheelZoom: true,
11307
11308         // @option wheelDebounceTime: Number = 40
11309         // Limits the rate at which a wheel can fire (in milliseconds). By default
11310         // user can't zoom via wheel more often than once per 40 ms.
11311         wheelDebounceTime: 40,
11312
11313         // @option wheelPxPerZoomLevel: Number = 60
11314         // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
11315         // mean a change of one full zoom level. Smaller values will make wheel-zooming
11316         // faster (and vice versa).
11317         wheelPxPerZoomLevel: 60
11318 });
11319
11320 L.Map.ScrollWheelZoom = L.Handler.extend({
11321         addHooks: function () {
11322                 L.DomEvent.on(this._map._container, 'mousewheel', this._onWheelScroll, this);
11323
11324                 this._delta = 0;
11325         },
11326
11327         removeHooks: function () {
11328                 L.DomEvent.off(this._map._container, 'mousewheel', this._onWheelScroll, this);
11329         },
11330
11331         _onWheelScroll: function (e) {
11332                 var delta = L.DomEvent.getWheelDelta(e);
11333
11334                 var debounce = this._map.options.wheelDebounceTime;
11335
11336                 this._delta += delta;
11337                 this._lastMousePos = this._map.mouseEventToContainerPoint(e);
11338
11339                 if (!this._startTime) {
11340                         this._startTime = +new Date();
11341                 }
11342
11343                 var left = Math.max(debounce - (+new Date() - this._startTime), 0);
11344
11345                 clearTimeout(this._timer);
11346                 this._timer = setTimeout(L.bind(this._performZoom, this), left);
11347
11348                 L.DomEvent.stop(e);
11349         },
11350
11351         _performZoom: function () {
11352                 var map = this._map,
11353                     zoom = map.getZoom(),
11354                     snap = this._map.options.zoomSnap || 0;
11355
11356                 map._stop(); // stop panning and fly animations if any
11357
11358                 // map the delta with a sigmoid function to -4..4 range leaning on -1..1
11359                 var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
11360                     d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
11361                     d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
11362                     delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;
11363
11364                 this._delta = 0;
11365                 this._startTime = null;
11366
11367                 if (!delta) { return; }
11368
11369                 if (map.options.scrollWheelZoom === 'center') {
11370                         map.setZoom(zoom + delta);
11371                 } else {
11372                         map.setZoomAround(this._lastMousePos, zoom + delta);
11373                 }
11374         }
11375 });
11376
11377 // @section Handlers
11378 // @property scrollWheelZoom: Handler
11379 // Scroll wheel zoom handler.
11380 L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom);
11381
11382
11383
11384 /*
11385  * Extends the event handling code with double tap support for mobile browsers.
11386  */
11387
11388 L.extend(L.DomEvent, {
11389
11390         _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart',
11391         _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend',
11392
11393         // inspired by Zepto touch code by Thomas Fuchs
11394         addDoubleTapListener: function (obj, handler, id) {
11395                 var last, touch,
11396                     doubleTap = false,
11397                     delay = 250;
11398
11399                 function onTouchStart(e) {
11400                         var count;
11401
11402                         if (L.Browser.pointer) {
11403                                 if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
11404                                 count = L.DomEvent._pointersCount;
11405                         } else {
11406                                 count = e.touches.length;
11407                         }
11408
11409                         if (count > 1) { return; }
11410
11411                         var now = Date.now(),
11412                             delta = now - (last || now);
11413
11414                         touch = e.touches ? e.touches[0] : e;
11415                         doubleTap = (delta > 0 && delta <= delay);
11416                         last = now;
11417                 }
11418
11419                 function onTouchEnd(e) {
11420                         if (doubleTap && !touch.cancelBubble) {
11421                                 if (L.Browser.pointer) {
11422                                         if ((!L.Browser.edge) || e.pointerType === 'mouse') { return; }
11423
11424                                         // work around .type being readonly with MSPointer* events
11425                                         var newTouch = {},
11426                                             prop, i;
11427
11428                                         for (i in touch) {
11429                                                 prop = touch[i];
11430                                                 newTouch[i] = prop && prop.bind ? prop.bind(touch) : prop;
11431                                         }
11432                                         touch = newTouch;
11433                                 }
11434                                 touch.type = 'dblclick';
11435                                 handler(touch);
11436                                 last = null;
11437                         }
11438                 }
11439
11440                 var pre = '_leaflet_',
11441                     touchstart = this._touchstart,
11442                     touchend = this._touchend;
11443
11444                 obj[pre + touchstart + id] = onTouchStart;
11445                 obj[pre + touchend + id] = onTouchEnd;
11446                 obj[pre + 'dblclick' + id] = handler;
11447
11448                 obj.addEventListener(touchstart, onTouchStart, false);
11449                 obj.addEventListener(touchend, onTouchEnd, false);
11450
11451                 // On some platforms (notably, chrome<55 on win10 + touchscreen + mouse),
11452                 // the browser doesn't fire touchend/pointerup events but does fire
11453                 // native dblclicks. See #4127.
11454                 // Edge 14 also fires native dblclicks, but only for pointerType mouse, see #5180.
11455                 obj.addEventListener('dblclick', handler, false);
11456
11457                 return this;
11458         },
11459
11460         removeDoubleTapListener: function (obj, id) {
11461                 var pre = '_leaflet_',
11462                     touchstart = obj[pre + this._touchstart + id],
11463                     touchend = obj[pre + this._touchend + id],
11464                     dblclick = obj[pre + 'dblclick' + id];
11465
11466                 obj.removeEventListener(this._touchstart, touchstart, false);
11467                 obj.removeEventListener(this._touchend, touchend, false);
11468                 if (!L.Browser.edge) {
11469                         obj.removeEventListener('dblclick', dblclick, false);
11470                 }
11471
11472                 return this;
11473         }
11474 });
11475
11476
11477
11478 /*
11479  * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
11480  */
11481
11482 L.extend(L.DomEvent, {
11483
11484         POINTER_DOWN:   L.Browser.msPointer ? 'MSPointerDown'   : 'pointerdown',
11485         POINTER_MOVE:   L.Browser.msPointer ? 'MSPointerMove'   : 'pointermove',
11486         POINTER_UP:     L.Browser.msPointer ? 'MSPointerUp'     : 'pointerup',
11487         POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel',
11488         TAG_WHITE_LIST: ['INPUT', 'SELECT', 'OPTION'],
11489
11490         _pointers: {},
11491         _pointersCount: 0,
11492
11493         // Provides a touch events wrapper for (ms)pointer events.
11494         // ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890
11495
11496         addPointerListener: function (obj, type, handler, id) {
11497
11498                 if (type === 'touchstart') {
11499                         this._addPointerStart(obj, handler, id);
11500
11501                 } else if (type === 'touchmove') {
11502                         this._addPointerMove(obj, handler, id);
11503
11504                 } else if (type === 'touchend') {
11505                         this._addPointerEnd(obj, handler, id);
11506                 }
11507
11508                 return this;
11509         },
11510
11511         removePointerListener: function (obj, type, id) {
11512                 var handler = obj['_leaflet_' + type + id];
11513
11514                 if (type === 'touchstart') {
11515                         obj.removeEventListener(this.POINTER_DOWN, handler, false);
11516
11517                 } else if (type === 'touchmove') {
11518                         obj.removeEventListener(this.POINTER_MOVE, handler, false);
11519
11520                 } else if (type === 'touchend') {
11521                         obj.removeEventListener(this.POINTER_UP, handler, false);
11522                         obj.removeEventListener(this.POINTER_CANCEL, handler, false);
11523                 }
11524
11525                 return this;
11526         },
11527
11528         _addPointerStart: function (obj, handler, id) {
11529                 var onDown = L.bind(function (e) {
11530                         if (e.pointerType !== 'mouse' && e.MSPOINTER_TYPE_MOUSE && e.pointerType !== e.MSPOINTER_TYPE_MOUSE) {
11531                                 // In IE11, some touch events needs to fire for form controls, or
11532                                 // the controls will stop working. We keep a whitelist of tag names that
11533                                 // need these events. For other target tags, we prevent default on the event.
11534                                 if (this.TAG_WHITE_LIST.indexOf(e.target.tagName) < 0) {
11535                                         L.DomEvent.preventDefault(e);
11536                                 } else {
11537                                         return;
11538                                 }
11539                         }
11540
11541                         this._handlePointer(e, handler);
11542                 }, this);
11543
11544                 obj['_leaflet_touchstart' + id] = onDown;
11545                 obj.addEventListener(this.POINTER_DOWN, onDown, false);
11546
11547                 // need to keep track of what pointers and how many are active to provide e.touches emulation
11548                 if (!this._pointerDocListener) {
11549                         var pointerUp = L.bind(this._globalPointerUp, this);
11550
11551                         // we listen documentElement as any drags that end by moving the touch off the screen get fired there
11552                         document.documentElement.addEventListener(this.POINTER_DOWN, L.bind(this._globalPointerDown, this), true);
11553                         document.documentElement.addEventListener(this.POINTER_MOVE, L.bind(this._globalPointerMove, this), true);
11554                         document.documentElement.addEventListener(this.POINTER_UP, pointerUp, true);
11555                         document.documentElement.addEventListener(this.POINTER_CANCEL, pointerUp, true);
11556
11557                         this._pointerDocListener = true;
11558                 }
11559         },
11560
11561         _globalPointerDown: function (e) {
11562                 this._pointers[e.pointerId] = e;
11563                 this._pointersCount++;
11564         },
11565
11566         _globalPointerMove: function (e) {
11567                 if (this._pointers[e.pointerId]) {
11568                         this._pointers[e.pointerId] = e;
11569                 }
11570         },
11571
11572         _globalPointerUp: function (e) {
11573                 delete this._pointers[e.pointerId];
11574                 this._pointersCount--;
11575         },
11576
11577         _handlePointer: function (e, handler) {
11578                 e.touches = [];
11579                 for (var i in this._pointers) {
11580                         e.touches.push(this._pointers[i]);
11581                 }
11582                 e.changedTouches = [e];
11583
11584                 handler(e);
11585         },
11586
11587         _addPointerMove: function (obj, handler, id) {
11588                 var onMove = L.bind(function (e) {
11589                         // don't fire touch moves when mouse isn't down
11590                         if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; }
11591
11592                         this._handlePointer(e, handler);
11593                 }, this);
11594
11595                 obj['_leaflet_touchmove' + id] = onMove;
11596                 obj.addEventListener(this.POINTER_MOVE, onMove, false);
11597         },
11598
11599         _addPointerEnd: function (obj, handler, id) {
11600                 var onUp = L.bind(function (e) {
11601                         this._handlePointer(e, handler);
11602                 }, this);
11603
11604                 obj['_leaflet_touchend' + id] = onUp;
11605                 obj.addEventListener(this.POINTER_UP, onUp, false);
11606                 obj.addEventListener(this.POINTER_CANCEL, onUp, false);
11607         }
11608 });
11609
11610
11611
11612 /*
11613  * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
11614  */
11615
11616 // @namespace Map
11617 // @section Interaction Options
11618 L.Map.mergeOptions({
11619         // @section Touch interaction options
11620         // @option touchZoom: Boolean|String = *
11621         // Whether the map can be zoomed by touch-dragging with two fingers. If
11622         // passed `'center'`, it will zoom to the center of the view regardless of
11623         // where the touch events (fingers) were. Enabled for touch-capable web
11624         // browsers except for old Androids.
11625         touchZoom: L.Browser.touch && !L.Browser.android23,
11626
11627         // @option bounceAtZoomLimits: Boolean = true
11628         // Set it to false if you don't want the map to zoom beyond min/max zoom
11629         // and then bounce back when pinch-zooming.
11630         bounceAtZoomLimits: true
11631 });
11632
11633 L.Map.TouchZoom = L.Handler.extend({
11634         addHooks: function () {
11635                 L.DomUtil.addClass(this._map._container, 'leaflet-touch-zoom');
11636                 L.DomEvent.on(this._map._container, 'touchstart', this._onTouchStart, this);
11637         },
11638
11639         removeHooks: function () {
11640                 L.DomUtil.removeClass(this._map._container, 'leaflet-touch-zoom');
11641                 L.DomEvent.off(this._map._container, 'touchstart', this._onTouchStart, this);
11642         },
11643
11644         _onTouchStart: function (e) {
11645                 var map = this._map;
11646                 if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }
11647
11648                 var p1 = map.mouseEventToContainerPoint(e.touches[0]),
11649                     p2 = map.mouseEventToContainerPoint(e.touches[1]);
11650
11651                 this._centerPoint = map.getSize()._divideBy(2);
11652                 this._startLatLng = map.containerPointToLatLng(this._centerPoint);
11653                 if (map.options.touchZoom !== 'center') {
11654                         this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
11655                 }
11656
11657                 this._startDist = p1.distanceTo(p2);
11658                 this._startZoom = map.getZoom();
11659
11660                 this._moved = false;
11661                 this._zooming = true;
11662
11663                 map._stop();
11664
11665                 L.DomEvent
11666                     .on(document, 'touchmove', this._onTouchMove, this)
11667                     .on(document, 'touchend', this._onTouchEnd, this);
11668
11669                 L.DomEvent.preventDefault(e);
11670         },
11671
11672         _onTouchMove: function (e) {
11673                 if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }
11674
11675                 var map = this._map,
11676                     p1 = map.mouseEventToContainerPoint(e.touches[0]),
11677                     p2 = map.mouseEventToContainerPoint(e.touches[1]),
11678                     scale = p1.distanceTo(p2) / this._startDist;
11679
11680
11681                 this._zoom = map.getScaleZoom(scale, this._startZoom);
11682
11683                 if (!map.options.bounceAtZoomLimits && (
11684                         (this._zoom < map.getMinZoom() && scale < 1) ||
11685                         (this._zoom > map.getMaxZoom() && scale > 1))) {
11686                         this._zoom = map._limitZoom(this._zoom);
11687                 }
11688
11689                 if (map.options.touchZoom === 'center') {
11690                         this._center = this._startLatLng;
11691                         if (scale === 1) { return; }
11692                 } else {
11693                         // Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
11694                         var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
11695                         if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
11696                         this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
11697                 }
11698
11699                 if (!this._moved) {
11700                         map._moveStart(true);
11701                         this._moved = true;
11702                 }
11703
11704                 L.Util.cancelAnimFrame(this._animRequest);
11705
11706                 var moveFn = L.bind(map._move, map, this._center, this._zoom, {pinch: true, round: false});
11707                 this._animRequest = L.Util.requestAnimFrame(moveFn, this, true);
11708
11709                 L.DomEvent.preventDefault(e);
11710         },
11711
11712         _onTouchEnd: function () {
11713                 if (!this._moved || !this._zooming) {
11714                         this._zooming = false;
11715                         return;
11716                 }
11717
11718                 this._zooming = false;
11719                 L.Util.cancelAnimFrame(this._animRequest);
11720
11721                 L.DomEvent
11722                     .off(document, 'touchmove', this._onTouchMove)
11723                     .off(document, 'touchend', this._onTouchEnd);
11724
11725                 // Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
11726                 if (this._map.options.zoomAnimation) {
11727                         this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
11728                 } else {
11729                         this._map._resetView(this._center, this._map._limitZoom(this._zoom));
11730                 }
11731         }
11732 });
11733
11734 // @section Handlers
11735 // @property touchZoom: Handler
11736 // Touch zoom handler.
11737 L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom);
11738
11739
11740
11741 /*
11742  * L.Map.Tap is used to enable mobile hacks like quick taps and long hold.
11743  */
11744
11745 // @namespace Map
11746 // @section Interaction Options
11747 L.Map.mergeOptions({
11748         // @section Touch interaction options
11749         // @option tap: Boolean = true
11750         // Enables mobile hacks for supporting instant taps (fixing 200ms click
11751         // delay on iOS/Android) and touch holds (fired as `contextmenu` events).
11752         tap: true,
11753
11754         // @option tapTolerance: Number = 15
11755         // The max number of pixels a user can shift his finger during touch
11756         // for it to be considered a valid tap.
11757         tapTolerance: 15
11758 });
11759
11760 L.Map.Tap = L.Handler.extend({
11761         addHooks: function () {
11762                 L.DomEvent.on(this._map._container, 'touchstart', this._onDown, this);
11763         },
11764
11765         removeHooks: function () {
11766                 L.DomEvent.off(this._map._container, 'touchstart', this._onDown, this);
11767         },
11768
11769         _onDown: function (e) {
11770                 if (!e.touches) { return; }
11771
11772                 L.DomEvent.preventDefault(e);
11773
11774                 this._fireClick = true;
11775
11776                 // don't simulate click or track longpress if more than 1 touch
11777                 if (e.touches.length > 1) {
11778                         this._fireClick = false;
11779                         clearTimeout(this._holdTimeout);
11780                         return;
11781                 }
11782
11783                 var first = e.touches[0],
11784                     el = first.target;
11785
11786                 this._startPos = this._newPos = new L.Point(first.clientX, first.clientY);
11787
11788                 // if touching a link, highlight it
11789                 if (el.tagName && el.tagName.toLowerCase() === 'a') {
11790                         L.DomUtil.addClass(el, 'leaflet-active');
11791                 }
11792
11793                 // simulate long hold but setting a timeout
11794                 this._holdTimeout = setTimeout(L.bind(function () {
11795                         if (this._isTapValid()) {
11796                                 this._fireClick = false;
11797                                 this._onUp();
11798                                 this._simulateEvent('contextmenu', first);
11799                         }
11800                 }, this), 1000);
11801
11802                 this._simulateEvent('mousedown', first);
11803
11804                 L.DomEvent.on(document, {
11805                         touchmove: this._onMove,
11806                         touchend: this._onUp
11807                 }, this);
11808         },
11809
11810         _onUp: function (e) {
11811                 clearTimeout(this._holdTimeout);
11812
11813                 L.DomEvent.off(document, {
11814                         touchmove: this._onMove,
11815                         touchend: this._onUp
11816                 }, this);
11817
11818                 if (this._fireClick && e && e.changedTouches) {
11819
11820                         var first = e.changedTouches[0],
11821                             el = first.target;
11822
11823                         if (el && el.tagName && el.tagName.toLowerCase() === 'a') {
11824                                 L.DomUtil.removeClass(el, 'leaflet-active');
11825                         }
11826
11827                         this._simulateEvent('mouseup', first);
11828
11829                         // simulate click if the touch didn't move too much
11830                         if (this._isTapValid()) {
11831                                 this._simulateEvent('click', first);
11832                         }
11833                 }
11834         },
11835
11836         _isTapValid: function () {
11837                 return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
11838         },
11839
11840         _onMove: function (e) {
11841                 var first = e.touches[0];
11842                 this._newPos = new L.Point(first.clientX, first.clientY);
11843                 this._simulateEvent('mousemove', first);
11844         },
11845
11846         _simulateEvent: function (type, e) {
11847                 var simulatedEvent = document.createEvent('MouseEvents');
11848
11849                 simulatedEvent._simulated = true;
11850                 e.target._simulatedClick = true;
11851
11852                 simulatedEvent.initMouseEvent(
11853                         type, true, true, window, 1,
11854                         e.screenX, e.screenY,
11855                         e.clientX, e.clientY,
11856                         false, false, false, false, 0, null);
11857
11858                 e.target.dispatchEvent(simulatedEvent);
11859         }
11860 });
11861
11862 // @section Handlers
11863 // @property tap: Handler
11864 // Mobile touch hacks (quick tap and touch hold) handler.
11865 if (L.Browser.touch && !L.Browser.pointer) {
11866         L.Map.addInitHook('addHandler', 'tap', L.Map.Tap);
11867 }
11868
11869
11870
11871 /*
11872  * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
11873  * (zoom to a selected bounding box), enabled by default.
11874  */
11875
11876 // @namespace Map
11877 // @section Interaction Options
11878 L.Map.mergeOptions({
11879         // @option boxZoom: Boolean = true
11880         // Whether the map can be zoomed to a rectangular area specified by
11881         // dragging the mouse while pressing the shift key.
11882         boxZoom: true
11883 });
11884
11885 L.Map.BoxZoom = L.Handler.extend({
11886         initialize: function (map) {
11887                 this._map = map;
11888                 this._container = map._container;
11889                 this._pane = map._panes.overlayPane;
11890         },
11891
11892         addHooks: function () {
11893                 L.DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
11894         },
11895
11896         removeHooks: function () {
11897                 L.DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
11898         },
11899
11900         moved: function () {
11901                 return this._moved;
11902         },
11903
11904         _resetState: function () {
11905                 this._moved = false;
11906         },
11907
11908         _onMouseDown: function (e) {
11909                 if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }
11910
11911                 this._resetState();
11912
11913                 L.DomUtil.disableTextSelection();
11914                 L.DomUtil.disableImageDrag();
11915
11916                 this._startPoint = this._map.mouseEventToContainerPoint(e);
11917
11918                 L.DomEvent.on(document, {
11919                         contextmenu: L.DomEvent.stop,
11920                         mousemove: this._onMouseMove,
11921                         mouseup: this._onMouseUp,
11922                         keydown: this._onKeyDown
11923                 }, this);
11924         },
11925
11926         _onMouseMove: function (e) {
11927                 if (!this._moved) {
11928                         this._moved = true;
11929
11930                         this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._container);
11931                         L.DomUtil.addClass(this._container, 'leaflet-crosshair');
11932
11933                         this._map.fire('boxzoomstart');
11934                 }
11935
11936                 this._point = this._map.mouseEventToContainerPoint(e);
11937
11938                 var bounds = new L.Bounds(this._point, this._startPoint),
11939                     size = bounds.getSize();
11940
11941                 L.DomUtil.setPosition(this._box, bounds.min);
11942
11943                 this._box.style.width  = size.x + 'px';
11944                 this._box.style.height = size.y + 'px';
11945         },
11946
11947         _finish: function () {
11948                 if (this._moved) {
11949                         L.DomUtil.remove(this._box);
11950                         L.DomUtil.removeClass(this._container, 'leaflet-crosshair');
11951                 }
11952
11953                 L.DomUtil.enableTextSelection();
11954                 L.DomUtil.enableImageDrag();
11955
11956                 L.DomEvent.off(document, {
11957                         contextmenu: L.DomEvent.stop,
11958                         mousemove: this._onMouseMove,
11959                         mouseup: this._onMouseUp,
11960                         keydown: this._onKeyDown
11961                 }, this);
11962         },
11963
11964         _onMouseUp: function (e) {
11965                 if ((e.which !== 1) && (e.button !== 1)) { return; }
11966
11967                 this._finish();
11968
11969                 if (!this._moved) { return; }
11970                 // Postpone to next JS tick so internal click event handling
11971                 // still see it as "moved".
11972                 setTimeout(L.bind(this._resetState, this), 0);
11973
11974                 var bounds = new L.LatLngBounds(
11975                         this._map.containerPointToLatLng(this._startPoint),
11976                         this._map.containerPointToLatLng(this._point));
11977
11978                 this._map
11979                         .fitBounds(bounds)
11980                         .fire('boxzoomend', {boxZoomBounds: bounds});
11981         },
11982
11983         _onKeyDown: function (e) {
11984                 if (e.keyCode === 27) {
11985                         this._finish();
11986                 }
11987         }
11988 });
11989
11990 // @section Handlers
11991 // @property boxZoom: Handler
11992 // Box (shift-drag with mouse) zoom handler.
11993 L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom);
11994
11995
11996
11997 /*
11998  * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
11999  */
12000
12001 // @namespace Map
12002 // @section Keyboard Navigation Options
12003 L.Map.mergeOptions({
12004         // @option keyboard: Boolean = true
12005         // Makes the map focusable and allows users to navigate the map with keyboard
12006         // arrows and `+`/`-` keys.
12007         keyboard: true,
12008
12009         // @option keyboardPanDelta: Number = 80
12010         // Amount of pixels to pan when pressing an arrow key.
12011         keyboardPanDelta: 80
12012 });
12013
12014 L.Map.Keyboard = L.Handler.extend({
12015
12016         keyCodes: {
12017                 left:    [37],
12018                 right:   [39],
12019                 down:    [40],
12020                 up:      [38],
12021                 zoomIn:  [187, 107, 61, 171],
12022                 zoomOut: [189, 109, 54, 173]
12023         },
12024
12025         initialize: function (map) {
12026                 this._map = map;
12027
12028                 this._setPanDelta(map.options.keyboardPanDelta);
12029                 this._setZoomDelta(map.options.zoomDelta);
12030         },
12031
12032         addHooks: function () {
12033                 var container = this._map._container;
12034
12035                 // make the container focusable by tabbing
12036                 if (container.tabIndex <= 0) {
12037                         container.tabIndex = '0';
12038                 }
12039
12040                 L.DomEvent.on(container, {
12041                         focus: this._onFocus,
12042                         blur: this._onBlur,
12043                         mousedown: this._onMouseDown
12044                 }, this);
12045
12046                 this._map.on({
12047                         focus: this._addHooks,
12048                         blur: this._removeHooks
12049                 }, this);
12050         },
12051
12052         removeHooks: function () {
12053                 this._removeHooks();
12054
12055                 L.DomEvent.off(this._map._container, {
12056                         focus: this._onFocus,
12057                         blur: this._onBlur,
12058                         mousedown: this._onMouseDown
12059                 }, this);
12060
12061                 this._map.off({
12062                         focus: this._addHooks,
12063                         blur: this._removeHooks
12064                 }, this);
12065         },
12066
12067         _onMouseDown: function () {
12068                 if (this._focused) { return; }
12069
12070                 var body = document.body,
12071                     docEl = document.documentElement,
12072                     top = body.scrollTop || docEl.scrollTop,
12073                     left = body.scrollLeft || docEl.scrollLeft;
12074
12075                 this._map._container.focus();
12076
12077                 window.scrollTo(left, top);
12078         },
12079
12080         _onFocus: function () {
12081                 this._focused = true;
12082                 this._map.fire('focus');
12083         },
12084
12085         _onBlur: function () {
12086                 this._focused = false;
12087                 this._map.fire('blur');
12088         },
12089
12090         _setPanDelta: function (panDelta) {
12091                 var keys = this._panKeys = {},
12092                     codes = this.keyCodes,
12093                     i, len;
12094
12095                 for (i = 0, len = codes.left.length; i < len; i++) {
12096                         keys[codes.left[i]] = [-1 * panDelta, 0];
12097                 }
12098                 for (i = 0, len = codes.right.length; i < len; i++) {
12099                         keys[codes.right[i]] = [panDelta, 0];
12100                 }
12101                 for (i = 0, len = codes.down.length; i < len; i++) {
12102                         keys[codes.down[i]] = [0, panDelta];
12103                 }
12104                 for (i = 0, len = codes.up.length; i < len; i++) {
12105                         keys[codes.up[i]] = [0, -1 * panDelta];
12106                 }
12107         },
12108
12109         _setZoomDelta: function (zoomDelta) {
12110                 var keys = this._zoomKeys = {},
12111                     codes = this.keyCodes,
12112                     i, len;
12113
12114                 for (i = 0, len = codes.zoomIn.length; i < len; i++) {
12115                         keys[codes.zoomIn[i]] = zoomDelta;
12116                 }
12117                 for (i = 0, len = codes.zoomOut.length; i < len; i++) {
12118                         keys[codes.zoomOut[i]] = -zoomDelta;
12119                 }
12120         },
12121
12122         _addHooks: function () {
12123                 L.DomEvent.on(document, 'keydown', this._onKeyDown, this);
12124         },
12125
12126         _removeHooks: function () {
12127                 L.DomEvent.off(document, 'keydown', this._onKeyDown, this);
12128         },
12129
12130         _onKeyDown: function (e) {
12131                 if (e.altKey || e.ctrlKey || e.metaKey) { return; }
12132
12133                 var key = e.keyCode,
12134                     map = this._map,
12135                     offset;
12136
12137                 if (key in this._panKeys) {
12138
12139                         if (map._panAnim && map._panAnim._inProgress) { return; }
12140
12141                         offset = this._panKeys[key];
12142                         if (e.shiftKey) {
12143                                 offset = L.point(offset).multiplyBy(3);
12144                         }
12145
12146                         map.panBy(offset);
12147
12148                         if (map.options.maxBounds) {
12149                                 map.panInsideBounds(map.options.maxBounds);
12150                         }
12151
12152                 } else if (key in this._zoomKeys) {
12153                         map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);
12154
12155                 } else if (key === 27) {
12156                         map.closePopup();
12157
12158                 } else {
12159                         return;
12160                 }
12161
12162                 L.DomEvent.stop(e);
12163         }
12164 });
12165
12166 // @section Handlers
12167 // @section Handlers
12168 // @property keyboard: Handler
12169 // Keyboard navigation handler.
12170 L.Map.addInitHook('addHandler', 'keyboard', L.Map.Keyboard);
12171
12172
12173
12174 /*
12175  * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
12176  */
12177
12178
12179 /* @namespace Marker
12180  * @section Interaction handlers
12181  *
12182  * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
12183  *
12184  * ```js
12185  * marker.dragging.disable();
12186  * ```
12187  *
12188  * @property dragging: Handler
12189  * Marker dragging handler (by both mouse and touch).
12190  */
12191
12192 L.Handler.MarkerDrag = L.Handler.extend({
12193         initialize: function (marker) {
12194                 this._marker = marker;
12195         },
12196
12197         addHooks: function () {
12198                 var icon = this._marker._icon;
12199
12200                 if (!this._draggable) {
12201                         this._draggable = new L.Draggable(icon, icon, true);
12202                 }
12203
12204                 this._draggable.on({
12205                         dragstart: this._onDragStart,
12206                         drag: this._onDrag,
12207                         dragend: this._onDragEnd
12208                 }, this).enable();
12209
12210                 L.DomUtil.addClass(icon, 'leaflet-marker-draggable');
12211         },
12212
12213         removeHooks: function () {
12214                 this._draggable.off({
12215                         dragstart: this._onDragStart,
12216                         drag: this._onDrag,
12217                         dragend: this._onDragEnd
12218                 }, this).disable();
12219
12220                 if (this._marker._icon) {
12221                         L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable');
12222                 }
12223         },
12224
12225         moved: function () {
12226                 return this._draggable && this._draggable._moved;
12227         },
12228
12229         _onDragStart: function () {
12230                 // @section Dragging events
12231                 // @event dragstart: Event
12232                 // Fired when the user starts dragging the marker.
12233
12234                 // @event movestart: Event
12235                 // Fired when the marker starts moving (because of dragging).
12236
12237                 this._oldLatLng = this._marker.getLatLng();
12238                 this._marker
12239                     .closePopup()
12240                     .fire('movestart')
12241                     .fire('dragstart');
12242         },
12243
12244         _onDrag: function (e) {
12245                 var marker = this._marker,
12246                     shadow = marker._shadow,
12247                     iconPos = L.DomUtil.getPosition(marker._icon),
12248                     latlng = marker._map.layerPointToLatLng(iconPos);
12249
12250                 // update shadow position
12251                 if (shadow) {
12252                         L.DomUtil.setPosition(shadow, iconPos);
12253                 }
12254
12255                 marker._latlng = latlng;
12256                 e.latlng = latlng;
12257                 e.oldLatLng = this._oldLatLng;
12258
12259                 // @event drag: Event
12260                 // Fired repeatedly while the user drags the marker.
12261                 marker
12262                     .fire('move', e)
12263                     .fire('drag', e);
12264         },
12265
12266         _onDragEnd: function (e) {
12267                 // @event dragend: DragEndEvent
12268                 // Fired when the user stops dragging the marker.
12269
12270                 // @event moveend: Event
12271                 // Fired when the marker stops moving (because of dragging).
12272                 delete this._oldLatLng;
12273                 this._marker
12274                     .fire('moveend')
12275                     .fire('dragend', e);
12276         }
12277 });
12278
12279
12280
12281 /*
12282  * @class Control
12283  * @aka L.Control
12284  * @inherits Class
12285  *
12286  * L.Control is a base class for implementing map controls. Handles positioning.
12287  * All other controls extend from this class.
12288  */
12289
12290 L.Control = L.Class.extend({
12291         // @section
12292         // @aka Control options
12293         options: {
12294                 // @option position: String = 'topright'
12295                 // The position of the control (one of the map corners). Possible values are `'topleft'`,
12296                 // `'topright'`, `'bottomleft'` or `'bottomright'`
12297                 position: 'topright'
12298         },
12299
12300         initialize: function (options) {
12301                 L.setOptions(this, options);
12302         },
12303
12304         /* @section
12305          * Classes extending L.Control will inherit the following methods:
12306          *
12307          * @method getPosition: string
12308          * Returns the position of the control.
12309          */
12310         getPosition: function () {
12311                 return this.options.position;
12312         },
12313
12314         // @method setPosition(position: string): this
12315         // Sets the position of the control.
12316         setPosition: function (position) {
12317                 var map = this._map;
12318
12319                 if (map) {
12320                         map.removeControl(this);
12321                 }
12322
12323                 this.options.position = position;
12324
12325                 if (map) {
12326                         map.addControl(this);
12327                 }
12328
12329                 return this;
12330         },
12331
12332         // @method getContainer: HTMLElement
12333         // Returns the HTMLElement that contains the control.
12334         getContainer: function () {
12335                 return this._container;
12336         },
12337
12338         // @method addTo(map: Map): this
12339         // Adds the control to the given map.
12340         addTo: function (map) {
12341                 this.remove();
12342                 this._map = map;
12343
12344                 var container = this._container = this.onAdd(map),
12345                     pos = this.getPosition(),
12346                     corner = map._controlCorners[pos];
12347
12348                 L.DomUtil.addClass(container, 'leaflet-control');
12349
12350                 if (pos.indexOf('bottom') !== -1) {
12351                         corner.insertBefore(container, corner.firstChild);
12352                 } else {
12353                         corner.appendChild(container);
12354                 }
12355
12356                 return this;
12357         },
12358
12359         // @method remove: this
12360         // Removes the control from the map it is currently active on.
12361         remove: function () {
12362                 if (!this._map) {
12363                         return this;
12364                 }
12365
12366                 L.DomUtil.remove(this._container);
12367
12368                 if (this.onRemove) {
12369                         this.onRemove(this._map);
12370                 }
12371
12372                 this._map = null;
12373
12374                 return this;
12375         },
12376
12377         _refocusOnMap: function (e) {
12378                 // if map exists and event is not a keyboard event
12379                 if (this._map && e && e.screenX > 0 && e.screenY > 0) {
12380                         this._map.getContainer().focus();
12381                 }
12382         }
12383 });
12384
12385 L.control = function (options) {
12386         return new L.Control(options);
12387 };
12388
12389 /* @section Extension methods
12390  * @uninheritable
12391  *
12392  * Every control should extend from `L.Control` and (re-)implement the following methods.
12393  *
12394  * @method onAdd(map: Map): HTMLElement
12395  * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
12396  *
12397  * @method onRemove(map: Map)
12398  * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
12399  */
12400
12401 /* @namespace Map
12402  * @section Methods for Layers and Controls
12403  */
12404 L.Map.include({
12405         // @method addControl(control: Control): this
12406         // Adds the given control to the map
12407         addControl: function (control) {
12408                 control.addTo(this);
12409                 return this;
12410         },
12411
12412         // @method removeControl(control: Control): this
12413         // Removes the given control from the map
12414         removeControl: function (control) {
12415                 control.remove();
12416                 return this;
12417         },
12418
12419         _initControlPos: function () {
12420                 var corners = this._controlCorners = {},
12421                     l = 'leaflet-',
12422                     container = this._controlContainer =
12423                             L.DomUtil.create('div', l + 'control-container', this._container);
12424
12425                 function createCorner(vSide, hSide) {
12426                         var className = l + vSide + ' ' + l + hSide;
12427
12428                         corners[vSide + hSide] = L.DomUtil.create('div', className, container);
12429                 }
12430
12431                 createCorner('top', 'left');
12432                 createCorner('top', 'right');
12433                 createCorner('bottom', 'left');
12434                 createCorner('bottom', 'right');
12435         },
12436
12437         _clearControlPos: function () {
12438                 L.DomUtil.remove(this._controlContainer);
12439         }
12440 });
12441
12442
12443
12444 /*
12445  * @class Control.Zoom
12446  * @aka L.Control.Zoom
12447  * @inherits Control
12448  *
12449  * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
12450  */
12451
12452 L.Control.Zoom = L.Control.extend({
12453         // @section
12454         // @aka Control.Zoom options
12455         options: {
12456                 position: 'topleft',
12457
12458                 // @option zoomInText: String = '+'
12459                 // The text set on the 'zoom in' button.
12460                 zoomInText: '+',
12461
12462                 // @option zoomInTitle: String = 'Zoom in'
12463                 // The title set on the 'zoom in' button.
12464                 zoomInTitle: 'Zoom in',
12465
12466                 // @option zoomOutText: String = '-'
12467                 // The text set on the 'zoom out' button.
12468                 zoomOutText: '-',
12469
12470                 // @option zoomOutTitle: String = 'Zoom out'
12471                 // The title set on the 'zoom out' button.
12472                 zoomOutTitle: 'Zoom out'
12473         },
12474
12475         onAdd: function (map) {
12476                 var zoomName = 'leaflet-control-zoom',
12477                     container = L.DomUtil.create('div', zoomName + ' leaflet-bar'),
12478                     options = this.options;
12479
12480                 this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
12481                         zoomName + '-in',  container, this._zoomIn);
12482                 this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
12483                         zoomName + '-out', container, this._zoomOut);
12484
12485                 this._updateDisabled();
12486                 map.on('zoomend zoomlevelschange', this._updateDisabled, this);
12487
12488                 return container;
12489         },
12490
12491         onRemove: function (map) {
12492                 map.off('zoomend zoomlevelschange', this._updateDisabled, this);
12493         },
12494
12495         disable: function () {
12496                 this._disabled = true;
12497                 this._updateDisabled();
12498                 return this;
12499         },
12500
12501         enable: function () {
12502                 this._disabled = false;
12503                 this._updateDisabled();
12504                 return this;
12505         },
12506
12507         _zoomIn: function (e) {
12508                 if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
12509                         this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12510                 }
12511         },
12512
12513         _zoomOut: function (e) {
12514                 if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
12515                         this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
12516                 }
12517         },
12518
12519         _createButton: function (html, title, className, container, fn) {
12520                 var link = L.DomUtil.create('a', className, container);
12521                 link.innerHTML = html;
12522                 link.href = '#';
12523                 link.title = title;
12524
12525                 /*
12526                  * Will force screen readers like VoiceOver to read this as "Zoom in - button"
12527                  */
12528                 link.setAttribute('role', 'button');
12529                 link.setAttribute('aria-label', title);
12530
12531                 L.DomEvent
12532                     .on(link, 'mousedown dblclick', L.DomEvent.stopPropagation)
12533                     .on(link, 'click', L.DomEvent.stop)
12534                     .on(link, 'click', fn, this)
12535                     .on(link, 'click', this._refocusOnMap, this);
12536
12537                 return link;
12538         },
12539
12540         _updateDisabled: function () {
12541                 var map = this._map,
12542                     className = 'leaflet-disabled';
12543
12544                 L.DomUtil.removeClass(this._zoomInButton, className);
12545                 L.DomUtil.removeClass(this._zoomOutButton, className);
12546
12547                 if (this._disabled || map._zoom === map.getMinZoom()) {
12548                         L.DomUtil.addClass(this._zoomOutButton, className);
12549                 }
12550                 if (this._disabled || map._zoom === map.getMaxZoom()) {
12551                         L.DomUtil.addClass(this._zoomInButton, className);
12552                 }
12553         }
12554 });
12555
12556 // @namespace Map
12557 // @section Control options
12558 // @option zoomControl: Boolean = true
12559 // Whether a [zoom control](#control-zoom) is added to the map by default.
12560 L.Map.mergeOptions({
12561         zoomControl: true
12562 });
12563
12564 L.Map.addInitHook(function () {
12565         if (this.options.zoomControl) {
12566                 this.zoomControl = new L.Control.Zoom();
12567                 this.addControl(this.zoomControl);
12568         }
12569 });
12570
12571 // @namespace Control.Zoom
12572 // @factory L.control.zoom(options: Control.Zoom options)
12573 // Creates a zoom control
12574 L.control.zoom = function (options) {
12575         return new L.Control.Zoom(options);
12576 };
12577
12578
12579
12580 /*
12581  * @class Control.Attribution
12582  * @aka L.Control.Attribution
12583  * @inherits Control
12584  *
12585  * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
12586  */
12587
12588 L.Control.Attribution = L.Control.extend({
12589         // @section
12590         // @aka Control.Attribution options
12591         options: {
12592                 position: 'bottomright',
12593
12594                 // @option prefix: String = 'Leaflet'
12595                 // The HTML text shown before the attributions. Pass `false` to disable.
12596                 prefix: '<a href="http://leafletjs.com" title="A JS library for interactive maps">Leaflet</a>'
12597         },
12598
12599         initialize: function (options) {
12600                 L.setOptions(this, options);
12601
12602                 this._attributions = {};
12603         },
12604
12605         onAdd: function (map) {
12606                 map.attributionControl = this;
12607                 this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
12608                 if (L.DomEvent) {
12609                         L.DomEvent.disableClickPropagation(this._container);
12610                 }
12611
12612                 // TODO ugly, refactor
12613                 for (var i in map._layers) {
12614                         if (map._layers[i].getAttribution) {
12615                                 this.addAttribution(map._layers[i].getAttribution());
12616                         }
12617                 }
12618
12619                 this._update();
12620
12621                 return this._container;
12622         },
12623
12624         // @method setPrefix(prefix: String): this
12625         // Sets the text before the attributions.
12626         setPrefix: function (prefix) {
12627                 this.options.prefix = prefix;
12628                 this._update();
12629                 return this;
12630         },
12631
12632         // @method addAttribution(text: String): this
12633         // Adds an attribution text (e.g. `'Vector data &copy; Mapbox'`).
12634         addAttribution: function (text) {
12635                 if (!text) { return this; }
12636
12637                 if (!this._attributions[text]) {
12638                         this._attributions[text] = 0;
12639                 }
12640                 this._attributions[text]++;
12641
12642                 this._update();
12643
12644                 return this;
12645         },
12646
12647         // @method removeAttribution(text: String): this
12648         // Removes an attribution text.
12649         removeAttribution: function (text) {
12650                 if (!text) { return this; }
12651
12652                 if (this._attributions[text]) {
12653                         this._attributions[text]--;
12654                         this._update();
12655                 }
12656
12657                 return this;
12658         },
12659
12660         _update: function () {
12661                 if (!this._map) { return; }
12662
12663                 var attribs = [];
12664
12665                 for (var i in this._attributions) {
12666                         if (this._attributions[i]) {
12667                                 attribs.push(i);
12668                         }
12669                 }
12670
12671                 var prefixAndAttribs = [];
12672
12673                 if (this.options.prefix) {
12674                         prefixAndAttribs.push(this.options.prefix);
12675                 }
12676                 if (attribs.length) {
12677                         prefixAndAttribs.push(attribs.join(', '));
12678                 }
12679
12680                 this._container.innerHTML = prefixAndAttribs.join(' | ');
12681         }
12682 });
12683
12684 // @namespace Map
12685 // @section Control options
12686 // @option attributionControl: Boolean = true
12687 // Whether a [attribution control](#control-attribution) is added to the map by default.
12688 L.Map.mergeOptions({
12689         attributionControl: true
12690 });
12691
12692 L.Map.addInitHook(function () {
12693         if (this.options.attributionControl) {
12694                 new L.Control.Attribution().addTo(this);
12695         }
12696 });
12697
12698 // @namespace Control.Attribution
12699 // @factory L.control.attribution(options: Control.Attribution options)
12700 // Creates an attribution control.
12701 L.control.attribution = function (options) {
12702         return new L.Control.Attribution(options);
12703 };
12704
12705
12706
12707 /*
12708  * @class Control.Scale
12709  * @aka L.Control.Scale
12710  * @inherits Control
12711  *
12712  * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
12713  *
12714  * @example
12715  *
12716  * ```js
12717  * L.control.scale().addTo(map);
12718  * ```
12719  */
12720
12721 L.Control.Scale = L.Control.extend({
12722         // @section
12723         // @aka Control.Scale options
12724         options: {
12725                 position: 'bottomleft',
12726
12727                 // @option maxWidth: Number = 100
12728                 // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
12729                 maxWidth: 100,
12730
12731                 // @option metric: Boolean = True
12732                 // Whether to show the metric scale line (m/km).
12733                 metric: true,
12734
12735                 // @option imperial: Boolean = True
12736                 // Whether to show the imperial scale line (mi/ft).
12737                 imperial: true
12738
12739                 // @option updateWhenIdle: Boolean = false
12740                 // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
12741         },
12742
12743         onAdd: function (map) {
12744                 var className = 'leaflet-control-scale',
12745                     container = L.DomUtil.create('div', className),
12746                     options = this.options;
12747
12748                 this._addScales(options, className + '-line', container);
12749
12750                 map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12751                 map.whenReady(this._update, this);
12752
12753                 return container;
12754         },
12755
12756         onRemove: function (map) {
12757                 map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
12758         },
12759
12760         _addScales: function (options, className, container) {
12761                 if (options.metric) {
12762                         this._mScale = L.DomUtil.create('div', className, container);
12763                 }
12764                 if (options.imperial) {
12765                         this._iScale = L.DomUtil.create('div', className, container);
12766                 }
12767         },
12768
12769         _update: function () {
12770                 var map = this._map,
12771                     y = map.getSize().y / 2;
12772
12773                 var maxMeters = map.distance(
12774                                 map.containerPointToLatLng([0, y]),
12775                                 map.containerPointToLatLng([this.options.maxWidth, y]));
12776
12777                 this._updateScales(maxMeters);
12778         },
12779
12780         _updateScales: function (maxMeters) {
12781                 if (this.options.metric && maxMeters) {
12782                         this._updateMetric(maxMeters);
12783                 }
12784                 if (this.options.imperial && maxMeters) {
12785                         this._updateImperial(maxMeters);
12786                 }
12787         },
12788
12789         _updateMetric: function (maxMeters) {
12790                 var meters = this._getRoundNum(maxMeters),
12791                     label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
12792
12793                 this._updateScale(this._mScale, label, meters / maxMeters);
12794         },
12795
12796         _updateImperial: function (maxMeters) {
12797                 var maxFeet = maxMeters * 3.2808399,
12798                     maxMiles, miles, feet;
12799
12800                 if (maxFeet > 5280) {
12801                         maxMiles = maxFeet / 5280;
12802                         miles = this._getRoundNum(maxMiles);
12803                         this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
12804
12805                 } else {
12806                         feet = this._getRoundNum(maxFeet);
12807                         this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
12808                 }
12809         },
12810
12811         _updateScale: function (scale, text, ratio) {
12812                 scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
12813                 scale.innerHTML = text;
12814         },
12815
12816         _getRoundNum: function (num) {
12817                 var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
12818                     d = num / pow10;
12819
12820                 d = d >= 10 ? 10 :
12821                     d >= 5 ? 5 :
12822                     d >= 3 ? 3 :
12823                     d >= 2 ? 2 : 1;
12824
12825                 return pow10 * d;
12826         }
12827 });
12828
12829
12830 // @factory L.control.scale(options?: Control.Scale options)
12831 // Creates an scale control with the given options.
12832 L.control.scale = function (options) {
12833         return new L.Control.Scale(options);
12834 };
12835
12836
12837
12838 /*
12839  * @class Control.Layers
12840  * @aka L.Control.Layers
12841  * @inherits Control
12842  *
12843  * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](http://leafletjs.com/examples/layers-control.html)). Extends `Control`.
12844  *
12845  * @example
12846  *
12847  * ```js
12848  * var baseLayers = {
12849  *      "Mapbox": mapbox,
12850  *      "OpenStreetMap": osm
12851  * };
12852  *
12853  * var overlays = {
12854  *      "Marker": marker,
12855  *      "Roads": roadsLayer
12856  * };
12857  *
12858  * L.control.layers(baseLayers, overlays).addTo(map);
12859  * ```
12860  *
12861  * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
12862  *
12863  * ```js
12864  * {
12865  *     "<someName1>": layer1,
12866  *     "<someName2>": layer2
12867  * }
12868  * ```
12869  *
12870  * The layer names can contain HTML, which allows you to add additional styling to the items:
12871  *
12872  * ```js
12873  * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
12874  * ```
12875  */
12876
12877
12878 L.Control.Layers = L.Control.extend({
12879         // @section
12880         // @aka Control.Layers options
12881         options: {
12882                 // @option collapsed: Boolean = true
12883                 // If `true`, the control will be collapsed into an icon and expanded on mouse hover or touch.
12884                 collapsed: true,
12885                 position: 'topright',
12886
12887                 // @option autoZIndex: Boolean = true
12888                 // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
12889                 autoZIndex: true,
12890
12891                 // @option hideSingleBase: Boolean = false
12892                 // If `true`, the base layers in the control will be hidden when there is only one.
12893                 hideSingleBase: false,
12894
12895                 // @option sortLayers: Boolean = false
12896                 // Whether to sort the layers. When `false`, layers will keep the order
12897                 // in which they were added to the control.
12898                 sortLayers: false,
12899
12900                 // @option sortFunction: Function = *
12901                 // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
12902                 // that will be used for sorting the layers, when `sortLayers` is `true`.
12903                 // The function receives both the `L.Layer` instances and their names, as in
12904                 // `sortFunction(layerA, layerB, nameA, nameB)`.
12905                 // By default, it sorts layers alphabetically by their name.
12906                 sortFunction: function (layerA, layerB, nameA, nameB) {
12907                         return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
12908                 }
12909         },
12910
12911         initialize: function (baseLayers, overlays, options) {
12912                 L.setOptions(this, options);
12913
12914                 this._layers = [];
12915                 this._lastZIndex = 0;
12916                 this._handlingClick = false;
12917
12918                 for (var i in baseLayers) {
12919                         this._addLayer(baseLayers[i], i);
12920                 }
12921
12922                 for (i in overlays) {
12923                         this._addLayer(overlays[i], i, true);
12924                 }
12925         },
12926
12927         onAdd: function (map) {
12928                 this._initLayout();
12929                 this._update();
12930
12931                 this._map = map;
12932                 map.on('zoomend', this._checkDisabledLayers, this);
12933
12934                 return this._container;
12935         },
12936
12937         onRemove: function () {
12938                 this._map.off('zoomend', this._checkDisabledLayers, this);
12939
12940                 for (var i = 0; i < this._layers.length; i++) {
12941                         this._layers[i].layer.off('add remove', this._onLayerChange, this);
12942                 }
12943         },
12944
12945         // @method addBaseLayer(layer: Layer, name: String): this
12946         // Adds a base layer (radio button entry) with the given name to the control.
12947         addBaseLayer: function (layer, name) {
12948                 this._addLayer(layer, name);
12949                 return (this._map) ? this._update() : this;
12950         },
12951
12952         // @method addOverlay(layer: Layer, name: String): this
12953         // Adds an overlay (checkbox entry) with the given name to the control.
12954         addOverlay: function (layer, name) {
12955                 this._addLayer(layer, name, true);
12956                 return (this._map) ? this._update() : this;
12957         },
12958
12959         // @method removeLayer(layer: Layer): this
12960         // Remove the given layer from the control.
12961         removeLayer: function (layer) {
12962                 layer.off('add remove', this._onLayerChange, this);
12963
12964                 var obj = this._getLayer(L.stamp(layer));
12965                 if (obj) {
12966                         this._layers.splice(this._layers.indexOf(obj), 1);
12967                 }
12968                 return (this._map) ? this._update() : this;
12969         },
12970
12971         // @method expand(): this
12972         // Expand the control container if collapsed.
12973         expand: function () {
12974                 L.DomUtil.addClass(this._container, 'leaflet-control-layers-expanded');
12975                 this._form.style.height = null;
12976                 var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
12977                 if (acceptableHeight < this._form.clientHeight) {
12978                         L.DomUtil.addClass(this._form, 'leaflet-control-layers-scrollbar');
12979                         this._form.style.height = acceptableHeight + 'px';
12980                 } else {
12981                         L.DomUtil.removeClass(this._form, 'leaflet-control-layers-scrollbar');
12982                 }
12983                 this._checkDisabledLayers();
12984                 return this;
12985         },
12986
12987         // @method collapse(): this
12988         // Collapse the control container if expanded.
12989         collapse: function () {
12990                 L.DomUtil.removeClass(this._container, 'leaflet-control-layers-expanded');
12991                 return this;
12992         },
12993
12994         _initLayout: function () {
12995                 var className = 'leaflet-control-layers',
12996                     container = this._container = L.DomUtil.create('div', className),
12997                     collapsed = this.options.collapsed;
12998
12999                 // makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
13000                 container.setAttribute('aria-haspopup', true);
13001
13002                 L.DomEvent.disableClickPropagation(container);
13003                 if (!L.Browser.touch) {
13004                         L.DomEvent.disableScrollPropagation(container);
13005                 }
13006
13007                 var form = this._form = L.DomUtil.create('form', className + '-list');
13008
13009                 if (collapsed) {
13010                         this._map.on('click', this.collapse, this);
13011
13012                         if (!L.Browser.android) {
13013                                 L.DomEvent.on(container, {
13014                                         mouseenter: this.expand,
13015                                         mouseleave: this.collapse
13016                                 }, this);
13017                         }
13018                 }
13019
13020                 var link = this._layersLink = L.DomUtil.create('a', className + '-toggle', container);
13021                 link.href = '#';
13022                 link.title = 'Layers';
13023
13024                 if (L.Browser.touch) {
13025                         L.DomEvent
13026                             .on(link, 'click', L.DomEvent.stop)
13027                             .on(link, 'click', this.expand, this);
13028                 } else {
13029                         L.DomEvent.on(link, 'focus', this.expand, this);
13030                 }
13031
13032                 // work around for Firefox Android issue https://github.com/Leaflet/Leaflet/issues/2033
13033                 L.DomEvent.on(form, 'click', function () {
13034                         setTimeout(L.bind(this._onInputClick, this), 0);
13035                 }, this);
13036
13037                 // TODO keyboard accessibility
13038
13039                 if (!collapsed) {
13040                         this.expand();
13041                 }
13042
13043                 this._baseLayersList = L.DomUtil.create('div', className + '-base', form);
13044                 this._separator = L.DomUtil.create('div', className + '-separator', form);
13045                 this._overlaysList = L.DomUtil.create('div', className + '-overlays', form);
13046
13047                 container.appendChild(form);
13048         },
13049
13050         _getLayer: function (id) {
13051                 for (var i = 0; i < this._layers.length; i++) {
13052
13053                         if (this._layers[i] && L.stamp(this._layers[i].layer) === id) {
13054                                 return this._layers[i];
13055                         }
13056                 }
13057         },
13058
13059         _addLayer: function (layer, name, overlay) {
13060                 layer.on('add remove', this._onLayerChange, this);
13061
13062                 this._layers.push({
13063                         layer: layer,
13064                         name: name,
13065                         overlay: overlay
13066                 });
13067
13068                 if (this.options.sortLayers) {
13069                         this._layers.sort(L.bind(function (a, b) {
13070                                 return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
13071                         }, this));
13072                 }
13073
13074                 if (this.options.autoZIndex && layer.setZIndex) {
13075                         this._lastZIndex++;
13076                         layer.setZIndex(this._lastZIndex);
13077                 }
13078         },
13079
13080         _update: function () {
13081                 if (!this._container) { return this; }
13082
13083                 L.DomUtil.empty(this._baseLayersList);
13084                 L.DomUtil.empty(this._overlaysList);
13085
13086                 var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;
13087
13088                 for (i = 0; i < this._layers.length; i++) {
13089                         obj = this._layers[i];
13090                         this._addItem(obj);
13091                         overlaysPresent = overlaysPresent || obj.overlay;
13092                         baseLayersPresent = baseLayersPresent || !obj.overlay;
13093                         baseLayersCount += !obj.overlay ? 1 : 0;
13094                 }
13095
13096                 // Hide base layers section if there's only one layer.
13097                 if (this.options.hideSingleBase) {
13098                         baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
13099                         this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
13100                 }
13101
13102                 this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
13103
13104                 return this;
13105         },
13106
13107         _onLayerChange: function (e) {
13108                 if (!this._handlingClick) {
13109                         this._update();
13110                 }
13111
13112                 var obj = this._getLayer(L.stamp(e.target));
13113
13114                 // @namespace Map
13115                 // @section Layer events
13116                 // @event baselayerchange: LayersControlEvent
13117                 // Fired when the base layer is changed through the [layer control](#control-layers).
13118                 // @event overlayadd: LayersControlEvent
13119                 // Fired when an overlay is selected through the [layer control](#control-layers).
13120                 // @event overlayremove: LayersControlEvent
13121                 // Fired when an overlay is deselected through the [layer control](#control-layers).
13122                 // @namespace Control.Layers
13123                 var type = obj.overlay ?
13124                         (e.type === 'add' ? 'overlayadd' : 'overlayremove') :
13125                         (e.type === 'add' ? 'baselayerchange' : null);
13126
13127                 if (type) {
13128                         this._map.fire(type, obj);
13129                 }
13130         },
13131
13132         // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe)
13133         _createRadioElement: function (name, checked) {
13134
13135                 var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
13136                                 name + '"' + (checked ? ' checked="checked"' : '') + '/>';
13137
13138                 var radioFragment = document.createElement('div');
13139                 radioFragment.innerHTML = radioHtml;
13140
13141                 return radioFragment.firstChild;
13142         },
13143
13144         _addItem: function (obj) {
13145                 var label = document.createElement('label'),
13146                     checked = this._map.hasLayer(obj.layer),
13147                     input;
13148
13149                 if (obj.overlay) {
13150                         input = document.createElement('input');
13151                         input.type = 'checkbox';
13152                         input.className = 'leaflet-control-layers-selector';
13153                         input.defaultChecked = checked;
13154                 } else {
13155                         input = this._createRadioElement('leaflet-base-layers', checked);
13156                 }
13157
13158                 input.layerId = L.stamp(obj.layer);
13159
13160                 L.DomEvent.on(input, 'click', this._onInputClick, this);
13161
13162                 var name = document.createElement('span');
13163                 name.innerHTML = ' ' + obj.name;
13164
13165                 // Helps from preventing layer control flicker when checkboxes are disabled
13166                 // https://github.com/Leaflet/Leaflet/issues/2771
13167                 var holder = document.createElement('div');
13168
13169                 label.appendChild(holder);
13170                 holder.appendChild(input);
13171                 holder.appendChild(name);
13172
13173                 var container = obj.overlay ? this._overlaysList : this._baseLayersList;
13174                 container.appendChild(label);
13175
13176                 this._checkDisabledLayers();
13177                 return label;
13178         },
13179
13180         _onInputClick: function () {
13181                 var inputs = this._form.getElementsByTagName('input'),
13182                     input, layer, hasLayer;
13183                 var addedLayers = [],
13184                     removedLayers = [];
13185
13186                 this._handlingClick = true;
13187
13188                 for (var i = inputs.length - 1; i >= 0; i--) {
13189                         input = inputs[i];
13190                         layer = this._getLayer(input.layerId).layer;
13191                         hasLayer = this._map.hasLayer(layer);
13192
13193                         if (input.checked && !hasLayer) {
13194                                 addedLayers.push(layer);
13195
13196                         } else if (!input.checked && hasLayer) {
13197                                 removedLayers.push(layer);
13198                         }
13199                 }
13200
13201                 // Bugfix issue 2318: Should remove all old layers before readding new ones
13202                 for (i = 0; i < removedLayers.length; i++) {
13203                         this._map.removeLayer(removedLayers[i]);
13204                 }
13205                 for (i = 0; i < addedLayers.length; i++) {
13206                         this._map.addLayer(addedLayers[i]);
13207                 }
13208
13209                 this._handlingClick = false;
13210
13211                 this._refocusOnMap();
13212         },
13213
13214         _checkDisabledLayers: function () {
13215                 var inputs = this._form.getElementsByTagName('input'),
13216                     input,
13217                     layer,
13218                     zoom = this._map.getZoom();
13219
13220                 for (var i = inputs.length - 1; i >= 0; i--) {
13221                         input = inputs[i];
13222                         layer = this._getLayer(input.layerId).layer;
13223                         input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
13224                                          (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);
13225
13226                 }
13227         },
13228
13229         _expand: function () {
13230                 // Backward compatibility, remove me in 1.1.
13231                 return this.expand();
13232         },
13233
13234         _collapse: function () {
13235                 // Backward compatibility, remove me in 1.1.
13236                 return this.collapse();
13237         }
13238
13239 });
13240
13241
13242 // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
13243 // Creates an attribution control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
13244 L.control.layers = function (baseLayers, overlays, options) {
13245         return new L.Control.Layers(baseLayers, overlays, options);
13246 };
13247
13248
13249
13250 }(window, document));
13251 //# sourceMappingURL=leaflet-src.map